Updated VGMStream to r1050-3644-gc9e2016f

First new build on an Apple Silicon build machine
CQTexperiment
Christopher Snowhill 2021-03-26 06:00:56 -07:00
parent d21aa09771
commit 81a4d92d72
17 changed files with 1467 additions and 850 deletions

File diff suppressed because it is too large Load Diff

View File

@ -345,7 +345,7 @@ typedef struct { //todo simplify
ogg_vorbis_codec_data* init_ogg_vorbis(STREAMFILE* sf, off_t start, off_t size, ogg_vorbis_io* io);
void decode_ogg_vorbis(ogg_vorbis_codec_data* data, sample_t* outbuf, int32_t samples_to_do, int channels);
void reset_ogg_vorbis(VGMSTREAM* vgmstream);
void reset_ogg_vorbis(ogg_vorbis_codec_data* data);
void seek_ogg_vorbis(ogg_vorbis_codec_data* data, int32_t num_sample);
void free_ogg_vorbis(ogg_vorbis_codec_data* data);
@ -353,6 +353,7 @@ int ogg_vorbis_get_comment(ogg_vorbis_codec_data* data, const char** comment);
void ogg_vorbis_get_info(ogg_vorbis_codec_data* data, int* p_channels, int* p_sample_rate);
void ogg_vorbis_get_samples(ogg_vorbis_codec_data* data, int* p_samples);
void ogg_vorbis_set_disable_reordering(ogg_vorbis_codec_data* data, int set);
void ogg_vorbis_set_force_seek(ogg_vorbis_codec_data* data, int set);
STREAMFILE* ogg_vorbis_get_streamfile(ogg_vorbis_codec_data* data);

View File

@ -933,12 +933,14 @@ void ffmpeg_set_force_seek(ffmpeg_codec_data* data) {
const char* ffmpeg_get_metadata_value(ffmpeg_codec_data* data, const char* key) {
AVDictionary* avd;
AVDictionaryEntry* avde;
AVDictionaryEntry* avde = NULL;
if (!data || !data->codec)
return NULL;
avd = data->formatCtx->streams[data->streamIndex]->metadata;
avd = data->formatCtx->streams[data->streamIndex]->metadata; /* per stream (like Ogg) */
if (!avd)
avd = data->formatCtx->metadata; /* per format (like Flac) */
if (!avd)
return NULL;

View File

@ -42,7 +42,10 @@ hca_codec_data* init_hca(STREAMFILE* sf) {
clHCA_clear(data->handle);
status = clHCA_DecodeHeader(data->handle, header_buffer, header_size); /* parse header */
if (status < 0) goto fail;
if (status < 0) {
VGM_LOG("HCA: unsupported header found, %i\n", status);
goto fail;
}
status = clHCA_getInfo(data->handle, &data->info); /* extract header info */
if (status < 0) goto fail;
@ -117,6 +120,8 @@ void decode_hca(hca_codec_data* data, sample_t* outbuf, int32_t samples_to_do) {
break;
}
data->current_block++;
/* decode frame */
status = clHCA_DecodeBlock(data->handle, (void*)(data->data_buffer), blockSize);
if (status < 0) {
@ -127,7 +132,6 @@ void decode_hca(hca_codec_data* data, sample_t* outbuf, int32_t samples_to_do) {
/* extract samples */
clHCA_ReadSamples16(data->handle, data->sample_buffer);
data->current_block++;
data->samples_consumed = 0;
data->samples_filled += data->info.samplesPerBlock;
}
@ -175,6 +179,13 @@ void free_hca(hca_codec_data* data) {
}
/* ************************************************************************* */
/* Test a single HCA key and assign an score for comparison. Multiple keys could potentially result
* in "playable" results (mostly silent with random clips), so it also checks the resulting PCM.
* Currently wrong keys should be detected during decoding+un-xor test, so this score may not
* be needed anymore, but keep around in case CRI breaks those tests in the future. */
/* arbitrary scale to simplify score comparisons */
#define HCA_KEY_SCORE_SCALE 10
/* ignores beginning frames (~10 is not uncommon, Dragalia Lost vocal layers have lots) */
@ -211,7 +222,8 @@ int test_hca_key(hca_codec_data* data, unsigned long long keycode) {
/* read and test frame */
bytes = read_streamfile(data->data_buffer, offset, blockSize, data->streamfile);
if (bytes != blockSize) {
total_score = -1;
/* normally this shouldn't happen, but pre-fetch ACB stop with frames in half, so just keep score */
//total_score = -1;
break;
}

View File

@ -13,24 +13,27 @@ struct ogg_vorbis_codec_data {
int ovf_init;
int bitstream;
int disable_reordering; /* Xiph Ogg must reorder channels on output, but some pre-ordered games don't need it */
int force_seek; /* Ogg with wrong granules can't seek correctly */
int32_t discard;
ogg_vorbis_io io;
vorbis_comment *comment;
vorbis_comment* comment;
int comment_number;
vorbis_info *info;
vorbis_info* info;
};
static void pcm_convert_float_to_16(int channels, sample_t *outbuf, int samples_to_do, float **pcm, int disable_ordering);
static void pcm_convert_float_to_16(int channels, sample_t* outbuf, int start_sample, int samples_to_do, float** pcm, int disable_ordering);
static size_t ov_read_func(void *ptr, size_t size, size_t nmemb, void *datasource);
static int ov_seek_func(void *datasource, ogg_int64_t offset, int whence);
static long ov_tell_func(void *datasource);
static int ov_close_func(void *datasource);
static size_t ov_read_func(void* ptr, size_t size, size_t nmemb, void* datasource);
static int ov_seek_func(void* datasource, ogg_int64_t offset, int whence);
static long ov_tell_func(void* datasource);
static int ov_close_func(void* datasource);
ogg_vorbis_codec_data* init_ogg_vorbis(STREAMFILE *sf, off_t start, off_t size, ogg_vorbis_io *io) {
ogg_vorbis_codec_data *data = NULL;
ogg_vorbis_codec_data* init_ogg_vorbis(STREAMFILE* sf, off_t start, off_t size, ogg_vorbis_io* io) {
ogg_vorbis_codec_data* data = NULL;
ov_callbacks callbacks = {0};
//todo clean up
@ -105,7 +108,7 @@ fail:
return NULL;
}
static size_t ov_read_func(void *ptr, size_t size, size_t nmemb, void *datasource) {
static size_t ov_read_func(void* ptr, size_t size, size_t nmemb, void* datasource) {
ogg_vorbis_io *io = datasource;
size_t bytes_read, items_read;
@ -129,8 +132,8 @@ static size_t ov_read_func(void *ptr, size_t size, size_t nmemb, void *datasourc
return items_read;
}
static int ov_seek_func(void *datasource, ogg_int64_t offset, int whence) {
ogg_vorbis_io *io = datasource;
static int ov_seek_func(void* datasource, ogg_int64_t offset, int whence) {
ogg_vorbis_io* io = datasource;
ogg_int64_t base_offset, new_offset;
switch (whence) {
@ -148,7 +151,6 @@ static int ov_seek_func(void *datasource, ogg_int64_t offset, int whence) {
break;
}
new_offset = base_offset + offset;
if (new_offset < 0 || new_offset > io->size) {
return -1; /* *must* return -1 if stream is unseekable */
@ -158,12 +160,12 @@ static int ov_seek_func(void *datasource, ogg_int64_t offset, int whence) {
}
}
static long ov_tell_func(void *datasource) {
ogg_vorbis_io *io = datasource;
static long ov_tell_func(void* datasource) {
ogg_vorbis_io* io = datasource;
return io->offset;
}
static int ov_close_func(void *datasource) {
static int ov_close_func(void* datasource) {
/* needed as setting ov_close_func in ov_callbacks to NULL doesn't seem to work
* (closing the streamfile is done in free_ogg_vorbis) */
return 0;
@ -171,10 +173,10 @@ static int ov_close_func(void *datasource) {
/* ********************************************** */
void decode_ogg_vorbis(ogg_vorbis_codec_data *data, sample_t *outbuf, int32_t samples_to_do, int channels) {
void decode_ogg_vorbis(ogg_vorbis_codec_data* data, sample_t* outbuf, int32_t samples_to_do, int channels) {
int samples_done = 0;
long rc;
float **pcm_channels; /* pointer to Xiph's double array buffer */
long start, rc;
float** pcm_channels; /* pointer to Xiph's double array buffer */
while (samples_done < samples_to_do) {
rc = ov_read_float(
@ -184,10 +186,23 @@ void decode_ogg_vorbis(ogg_vorbis_codec_data *data, sample_t *outbuf, int32_t sa
&data->bitstream); /* bitstream */
if (rc <= 0) goto fail; /* rc is samples done */
pcm_convert_float_to_16(channels, outbuf, rc, pcm_channels, data->disable_reordering);
if (data->discard) {
start = data->discard;
if (start > rc)
start = rc;
outbuf += rc * channels;
samples_done += rc;
data->discard -= start;
if (start == rc) /* consume all */
continue;
}
else {
start = 0;
}
pcm_convert_float_to_16(channels, outbuf, start, rc, pcm_channels, data->disable_reordering);
outbuf += (rc - start) * channels;
samples_done += (rc - start);
#if 0 // alt decoding
@ -217,8 +232,8 @@ fail:
}
/* vorbis encodes channels in non-standard order, so we remap during conversion to fix this oddity.
* (feels a bit weird as one would think you could leave as-is and set the player's output
* order, but that isn't possible and remapping is what FFmpeg and every other plugin does). */
* (feels a bit weird as one would think you could leave as-is and set the player's output order,
* but that isn't possible and remapping like this is what FFmpeg and every other plugin does). */
static const int xiph_channel_map[8][8] = {
{ 0 }, /* 1ch: FC > same */
{ 0, 1 }, /* 2ch: FL FR > same */
@ -231,7 +246,7 @@ static const int xiph_channel_map[8][8] = {
};
/* converts from internal Vorbis format to standard PCM and remaps (mostly from Xiph's decoder_example.c) */
static void pcm_convert_float_to_16(int channels, sample_t * outbuf, int samples_to_do, float ** pcm, int disable_ordering) {
static void pcm_convert_float_to_16(int channels, sample_t* outbuf, int start_sample, int samples_to_do, float** pcm, int disable_ordering) {
int ch, s, ch_map;
sample_t *ptr;
float *channel;
@ -244,7 +259,7 @@ static void pcm_convert_float_to_16(int channels, sample_t * outbuf, int samples
(channels > 8) ? ch : xiph_channel_map[channels - 1][ch]; /* put Vorbis' ch to other outbuf's ch */
ptr = outbuf + ch;
channel = pcm[ch_map];
for (s = 0; s < samples_to_do; s++) {
for (s = start_sample; s < samples_to_do; s++) {
int val = (int)floor(channel[s] * 32767.0f + 0.5f); /* use floorf? doesn't seem any faster */
if (val > 32767) val = 32767;
else if (val < -32768) val = -32768;
@ -257,23 +272,34 @@ static void pcm_convert_float_to_16(int channels, sample_t * outbuf, int samples
/* ********************************************** */
void reset_ogg_vorbis(VGMSTREAM *vgmstream) {
ogg_vorbis_codec_data *data = vgmstream->codec_data;
void reset_ogg_vorbis(ogg_vorbis_codec_data* data) {
if (!data) return;
/* this seek cleans internal buffers */
ov_pcm_seek(&data->ogg_vorbis_file, 0);
/* this raw seek cleans internal buffers, and it's preferable to
* ov_pcm_seek as doesn't get confused by wrong granules */
ov_raw_seek(&data->ogg_vorbis_file, 0);
//;VGM_ASSERT(res != 0, "OGG: bad reset=%i\n", res);
data->discard = 0;
}
void seek_ogg_vorbis(ogg_vorbis_codec_data *data, int32_t num_sample) {
void seek_ogg_vorbis(ogg_vorbis_codec_data* data, int32_t num_sample) {
if (!data) return;
/* this seek crosslaps to avoid possible clicks, so seeking to 0 will
* decode a bit differently than ov_pcm_seek */
/* special seek for games with bad granule positions (since ov_*_seek uses granules to seek) */
if (data->force_seek) {
reset_ogg_vorbis(data);
data->discard = num_sample;
return;
}
/* this seek crosslaps to avoid possible clicks, so seeking to 0 + discarding
* will decode a bit differently than ov_pcm_seek */
ov_pcm_seek_lap(&data->ogg_vorbis_file, num_sample);
//VGM_ASSERT(res != 0, "OGG: bad seek=%i\n", res); /* not seen, in theory could give error */
}
void free_ogg_vorbis(ogg_vorbis_codec_data *data) {
void free_ogg_vorbis(ogg_vorbis_codec_data* data) {
if (!data) return;
if (data->ovf_init)
@ -285,7 +311,7 @@ void free_ogg_vorbis(ogg_vorbis_codec_data *data) {
/* ********************************************** */
int ogg_vorbis_get_comment(ogg_vorbis_codec_data *data, const char **comment) {
int ogg_vorbis_get_comment(ogg_vorbis_codec_data* data, const char** comment) {
if (!data) return 0;
/* dumb reset */
@ -302,7 +328,7 @@ int ogg_vorbis_get_comment(ogg_vorbis_codec_data *data, const char **comment) {
return 1;
}
void ogg_vorbis_get_info(ogg_vorbis_codec_data *data, int *p_channels, int *p_sample_rate) {
void ogg_vorbis_get_info(ogg_vorbis_codec_data* data, int* p_channels, int* p_sample_rate) {
if (!data) {
if (p_channels) *p_channels = 0;
if (p_sample_rate) *p_sample_rate = 0;
@ -313,7 +339,7 @@ void ogg_vorbis_get_info(ogg_vorbis_codec_data *data, int *p_channels, int *p_sa
if (p_sample_rate) *p_sample_rate = (int)data->info->rate;
}
void ogg_vorbis_get_samples(ogg_vorbis_codec_data *data, int *p_samples) {
void ogg_vorbis_get_samples(ogg_vorbis_codec_data* data, int* p_samples) {
if (!data) {
if (p_samples) *p_samples = 0;
return;
@ -322,13 +348,19 @@ void ogg_vorbis_get_samples(ogg_vorbis_codec_data *data, int *p_samples) {
if (p_samples) *p_samples = ov_pcm_total(&data->ogg_vorbis_file,-1);
}
void ogg_vorbis_set_disable_reordering(ogg_vorbis_codec_data *data, int set) {
void ogg_vorbis_set_disable_reordering(ogg_vorbis_codec_data* data, int set) {
if (!data) return;
data->disable_reordering = set;
}
STREAMFILE* ogg_vorbis_get_streamfile(ogg_vorbis_codec_data *data) {
void ogg_vorbis_set_force_seek(ogg_vorbis_codec_data* data, int set) {
if (!data) return;
data->force_seek = set;
}
STREAMFILE* ogg_vorbis_get_streamfile(ogg_vorbis_codec_data* data) {
if (!data) return NULL;
return data->io.streamfile;
}

View File

@ -13,8 +13,8 @@
* - https://github.com/PCSX2/pcsx2/blob/master/pcsx2/VUops.cpp
*
* Codec has no apparent name, but most functions mention "St" (stream?) and "Sac" (sound audio
* container?) and handler lib may be "Csd". Looks inspired by MPEG (much simplified) with bits
* from other codecs (per-file codebook and 1024 samples).
* compression?) and main lib is called "Sac" (Sac.cpp/Sac.dsm), also set in a "Csd" ELF. Looks inspired
* by MPEG (much simplified) with bits from other codecs (per-file codebook and 1024 samples).
*
* Original decoder is mainly implemented in the PS2's VU1, a coprocessor specialized in vector/SIMD
* and parallel instructions. As VU1 works with many 128 bit registers (typically x4 floats) algorithm
@ -134,7 +134,7 @@ static inline int16_t clamp_s16(int16_t value, int16_t min, int16_t max) {
/* converts 4 huffman codes to 4 spectrums coefs */
//SUB_1188
//SUB_1188 (Pass1_Start?)
static void unpack_code4(REG_VF* spectrum, const REG_VF* spc1, const REG_VF* spc2, const REG_VF* code, const REG_VF* idx, int out_pos) {
const REG_VF* ST = SCALE_TABLE;
REG_VF tbc1, tbc2, out;
@ -282,8 +282,8 @@ static void transform_dot_product(REG_VF* mac, const REG_VF* spectrum, const REG
MADD (_xyzw, mac, &spectrum[pos_i+7], &TT[pos_t+7]);
}
/* take spectrum coefs and, ahem, transform somehow, possibly using a SIMD'd FFT/DCT table. */
//SUB_1410
/* take spectrum coefs and, ahem, transform somehow, possibly using a SIMD'd DCT table. */
//SUB_1410 (Idct_Start?)
static void transform(REG_VF* wave, const REG_VF* spectrum) {
const REG_VF* TT = TRANSFORM_TABLE;
int i, j;
@ -330,7 +330,7 @@ static void transform(REG_VF* wave, const REG_VF* spectrum) {
/* process and apply window/overlap. Similar to MP3's synth granule function. */
//SUB_1690
//SUB_1690 (Pass3_Start?)
static void process(REG_VF* wave, REG_VF* hist) {
const REG_VF* ST = SYNTH_TABLE;
int i, j;
@ -918,7 +918,7 @@ static void finalize_output(tac_handle_t* h) {
REG_VF* wave_l = h->wave[0];
REG_VF* wave_r = h->wave[1];
/* Combine joint stereo channels that encode diffs in L/R. In pseudo-mono files R has */
/* Combine joint stereo channels that encode diffs in L/R ("MS stereo"). In pseudo-mono files R has */
/* all samples as 0 (R only saves 28 huffman codes, signalling no coefs per 1+27 bands) */
for (i = 0; i < TAC_TOTAL_POINTS * 8; i++) {
REG_VF samples_l, samples_r;

View File

@ -31,7 +31,7 @@ typedef struct {
uint16_t frame_last; /* valid samples in final frame - 1 (lower = outputs less, 0 = outputs 1), even for non-looped files */
uint32_t loop_offset; /* points to a block; file size if not looped */
uint32_t file_size; /* block aligned; actual file size can be a bit smaller if last block is truncated */
uint32_t joint_stereo; /* usually 0 and rarely 1 */
uint32_t joint_stereo; /* usually 0 and rarely 1 ("MSStereoMode") */
uint32_t empty; /* always null */
} tac_header_t;

View File

@ -71,7 +71,7 @@ static const REG_VF ANTIALIASING_TABLE[64/4] = {
/* Seemingly scale factor table (needs an extra index, see end).
* ELF seems to store only xy, zw is just mirrored. */
static const REG_VF SCALE_TABLE[2048/4 + 4/4] = {
static const REG_VF SCALE_TABLE[2048/4 + 4/4] = { //SacPass1TableVU?
/* part 1 */
{ .i = {0x00000000,0x3F7F8000,0x00000000,0x3F7F8000} }, //+0.000000000f, +0.998046875f, +0.000000000f, +0.998046875f
{ .i = {0x3A000000,0x3F5744FD,0x3A000000,0x3F5744FD} }, //+0.000488281f, +0.840896428f, +0.000488281f, +0.840896428f
@ -592,8 +592,8 @@ static const REG_VF SCALE_TABLE[2048/4 + 4/4] = {
{ .i = {0x00000000,0x00000000,0x00000000,0x00000000} }, //+0.000000000f, +0.000000000f, +0.000000000f, +0.000000000f
};
/* FFT?/DCT? table (Hanning window?) */
static const REG_VF TRANSFORM_TABLE[1024/4] = {
/* IDCT? table (Hanning window?) */
static const REG_VF TRANSFORM_TABLE[1024/4] = { //SacIdctTableVU?
{ .i = {0x3F3504F3,0x3F7FB10F,0x3F7EC46D,0x3F7D3AAC} }, //+0.707106769f, +0.998795450f, +0.995184720f, +0.989176512f
{ .i = {0x3F7B14BE,0x3F7853F8,0x3F74FA0B,0x3F710908} }, //+0.980785251f, +0.970031261f, +0.956940353f, +0.941544056f
{ .i = {0x3F6C835E,0x3F676BD8,0x3F61C597,0x3F5B941A} }, //+0.923879504f, +0.903989315f, +0.881921232f, +0.857728601f
@ -854,7 +854,7 @@ static const REG_VF TRANSFORM_TABLE[1024/4] = {
/* standard MPEG1 filter bank (synth_window)
* Seems divided into 2 parts, or at least loaded to VU1 memory in 2 steps */
static const REG_VF WINDOW_TABLE[512/4] = {
static const REG_VF WINDOW_TABLE[512/4] = { //SacPass3TableVU?
/* part 1 */
{ .i = {0x00000000,0xB7800074,0xB7800074,0xB7800074} }, //+0.000000000f, -0.000015259f, -0.000015259f, -0.000015259f
{ .i = {0xB7800074,0xB7800074,0xB7800074,0xB8000074} }, //-0.000015259f, -0.000015259f, -0.000015259f, -0.000030518f

View File

@ -219,7 +219,7 @@ void reset_codec(VGMSTREAM* vgmstream) {
#ifdef VGM_USE_VORBIS
if (vgmstream->coding_type == coding_OGG_VORBIS) {
reset_ogg_vorbis(vgmstream);
reset_ogg_vorbis(vgmstream->codec_data);
}
if (vgmstream->coding_type == coding_VORBIS_custom) {

View File

@ -83,6 +83,7 @@ static const char* extension_list[] = {
"atsl4",
"atx",
"aud",
"audio", //txth/reserved [Grimm Echoes (Android)]
"aus",
"awb",
"awc",

View File

@ -4,7 +4,7 @@
#ifdef VGM_USE_FFMPEG
static int read_pos_file(uint8_t* buf, size_t bufsize, STREAMFILE* sf);
static int find_ogg_loops(ffmpeg_codec_data* data, int32_t* p_loop_start, int32_t* p_loop_end);
static int find_meta_loops(ffmpeg_codec_data* data, int32_t* p_loop_start, int32_t* p_loop_end);
/* parses any format supported by FFmpeg and not handled elsewhere:
* - MP3 (.mp3, .mus): Marc Ecko's Getting Up (PC)
@ -58,9 +58,9 @@ VGMSTREAM* init_vgmstream_ffmpeg(STREAMFILE* sf) {
}
}
/* try to read Ogg loop tags (abridged) */
if (loop_flag == 0 && read_u32be(0x00, sf) == 0x4F676753) { /* "OggS" */
loop_flag = find_ogg_loops(data, &loop_start, &loop_end);
/* try to read Ogg/Flac loop tags (abridged) */
if (!loop_flag && (is_id32be(0x00, sf, "OggS") || is_id32be(0x00, sf, "fLaC"))) {
loop_flag = find_meta_loops(data, &loop_start, &loop_end);
}
/* hack for AAC files (will return 0 samples if not an actual file) */
@ -155,22 +155,30 @@ fail:
}
/* loop tag handling could be unified with ogg_vorbis.c, but that one has a extra features too */
static int find_ogg_loops(ffmpeg_codec_data* data, int32_t* p_loop_start, int32_t* p_loop_end) {
/* loop tag handling could be unified with ogg_vorbis.c, but that one has a extra features too.
* Also has support for flac meta loops, that can be used by stuff like Platformer Game Engine
* or ZDoom/Duke Nukem 3D source ports (maybe RPG Maker too). */
//todo call ffmpeg_get_next_tag and iterate like ogg_vorbis.c
//todo also save title
static int find_meta_loops(ffmpeg_codec_data* data, int32_t* p_loop_start, int32_t* p_loop_end) {
char* endptr;
const char* value;
int loop_flag = 0;
int32_t loop_start = -1, loop_end = -1;
// Try to detect the loop flags based on current file metadata
// Try to detect the loop flags based on current file metadata (ignores case)
value = ffmpeg_get_metadata_value(data, "LoopStart");
if (value != NULL) {
if (!value)
value = ffmpeg_get_metadata_value(data, "LOOP_START"); /* ZDoom/DN3D */
if (value) {
loop_start = strtol(value, &endptr, 10);
loop_flag = 1;
}
value = ffmpeg_get_metadata_value(data, "LoopEnd");
if (value != NULL) {
if (!value)
value = ffmpeg_get_metadata_value(data, "LOOP_END"); /* ZDoom/DN3D */
if (value) {
loop_end = strtol(value, &endptr, 10);
loop_flag = 1;
}

View File

@ -11,6 +11,7 @@ typedef struct {
int flags;
int channels;
int layers;
int sample_rate;
int32_t num_samples;
int32_t loop_start;
@ -33,8 +34,7 @@ typedef struct {
/* ********************************************************************************** */
static layered_layout_data* build_layered_fsb5_celt(STREAMFILE* sf, fsb5_header* fsb5);
static layered_layout_data* build_layered_fsb5_atrac9(STREAMFILE* sf, fsb5_header* fsb5, off_t configs_offset, size_t configs_size);
static layered_layout_data* build_layered_fsb5(STREAMFILE* sf, fsb5_header* fsb5);
/* FSB5 - Firelight's FMOD Studio SoundBank format */
VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
@ -50,23 +50,24 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
if (!check_extensions(sf,"fsb,snd"))
goto fail;
if (read_32bitBE(0x00,sf) != 0x46534235) /* "FSB5" */
if (!is_id32be(0x00,sf, "FSB5"))
goto fail;
/* 0x00 is rare (seen in Tales from Space Vita) */
fsb5.version = read_32bitLE(0x04,sf);
if (fsb5.version != 0x00 && fsb5.version != 0x01) goto fail;
fsb5.version = read_u32le(0x04,sf);
if (fsb5.version != 0x00 && fsb5.version != 0x01)
goto fail;
fsb5.total_subsongs = read_32bitLE(0x08,sf);
fsb5.sample_header_size = read_32bitLE(0x0C,sf);
fsb5.name_table_size = read_32bitLE(0x10,sf);
fsb5.sample_data_size = read_32bitLE(0x14,sf);
fsb5.codec = read_32bitLE(0x18,sf);
fsb5.total_subsongs = read_u32le(0x08,sf);
fsb5.sample_header_size = read_u32le(0x0C,sf);
fsb5.name_table_size = read_u32le(0x10,sf);
fsb5.sample_data_size = read_u32le(0x14,sf);
fsb5.codec = read_u32le(0x18,sf);
/* version 0x01 - 0x1c(4): zero, 0x24(16): hash, 0x34(8): unk
* version 0x00 has an extra field (always 0?) at 0x1c */
if (fsb5.version == 0x01) {
/* found by tests and assumed to be flags, no games known */
fsb5.flags = read_32bitLE(0x20,sf);
fsb5.flags = read_u32le(0x20,sf);
}
fsb5.base_header_size = (fsb5.version==0x00) ? 0x40 : 0x3C;
@ -87,8 +88,8 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
off_t data_offset = 0;
uint32_t sample_mode1, sample_mode2; /* maybe one uint64? */
sample_mode1 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+0x00,sf);
sample_mode2 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+0x04,sf);
sample_mode1 = read_u32le(fsb5.sample_header_offset+0x00,sf);
sample_mode2 = read_u32le(fsb5.sample_header_offset+0x04,sf);
stream_header_size += 0x08;
/* get samples */
@ -133,7 +134,7 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
uint32_t extraflag, extraflag_type, extraflag_size, extraflag_end;
do {
extraflag = read_32bitLE(extraflag_offset,sf);
extraflag = read_u32le(extraflag_offset,sf);
extraflag_type = (extraflag >> 25) & 0x7F; /* bits 32..26 (7) */
extraflag_size = (extraflag >> 1) & 0xFFFFFF; /* bits 25..1 (24)*/
extraflag_end = (extraflag & 0x01); /* bit 0 (1) */
@ -142,15 +143,15 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
if (i + 1 == target_subsong) {
switch(extraflag_type) {
case 0x01: /* channels */
fsb5.channels = read_8bit(extraflag_offset+0x04,sf);
fsb5.channels = read_u8(extraflag_offset+0x04,sf);
break;
case 0x02: /* sample rate */
fsb5.sample_rate = read_32bitLE(extraflag_offset+0x04,sf);
fsb5.sample_rate = read_s32le(extraflag_offset+0x04,sf);
break;
case 0x03: /* loop info */
fsb5.loop_start = read_32bitLE(extraflag_offset+0x04,sf);
fsb5.loop_start = read_s32le(extraflag_offset+0x04,sf);
if (extraflag_size > 0x04) { /* probably not needed */
fsb5.loop_end = read_32bitLE(extraflag_offset+0x08,sf);
fsb5.loop_end = read_s32le(extraflag_offset+0x08,sf);
fsb5.loop_end += 1; /* correct compared to FMOD's tools */
}
//;VGM_LOG("FSB5: stream %i loop start=%i, loop end=%i, samples=%i\n", i, fsb5.loop_start, fsb5.loop_end, fsb5.num_samples);
@ -183,7 +184,7 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
case 0x05: /* unknown 32b */
/* rare, found in Tearaway (Vita) with value 0 in first stream and
* Shantae and the Seven Sirens (Mobile) with value 0x0003bd72 BE in #44 (Arena Town) */
VGM_LOG("FSB5: stream %i flag %x with value %08x\n", i, extraflag_type, read_32bitLE(extraflag_offset+0x04,sf));
VGM_LOG("FSB5: stream %i flag %x with value %08x\n", i, extraflag_type, read_u32le(extraflag_offset+0x04,sf));
break;
case 0x06: /* XMA seek table */
/* no need for it */
@ -204,14 +205,17 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
* 0x08: table_size (total_entries = seek_table_size / (4+4)), not counting this value; can be 0
* 0x0C: sample number (only some samples are saved in the table)
* 0x10: offset within data, pointing to a FSB vorbis block (with the 16b block size header)
* (xN entries)
*/
* (xN entries) */
break;
case 0x0d: /* peak volume float (optional setting when making fsb) */
break;
case 0x0f: /* OPUS data size not counting frames headers */
break;
case 0x0e: /* number of layered Vorbis channels [Invisible, Inc. (Switch)] */
case 0x0e: /* Vorbis intra-layers (multichannel FMOD ~2021) [Invisible, Inc. (Switch), Just Cause 4 (PC)] */
fsb5.layers = read_u32le(extraflag_offset+0x04,sf);
/* info only as decoding is standard Vorbis that handles Nch multichannel (channels is 1 here) */
fsb5.channels = fsb5.channels * fsb5.layers;
break;
default:
VGM_LOG("FSB5: stream %i unknown flag 0x%x at %x + 0x04 (size 0x%x)\n", i, extraflag_type, (uint32_t)extraflag_offset, extraflag_size);
break;
@ -240,8 +244,8 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
else {
off_t next_data_offset;
uint32_t next_sample_mode1, next_sample_mode2;
next_sample_mode1 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+stream_header_size+0x00,sf);
next_sample_mode2 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+stream_header_size+0x04,sf);
next_sample_mode1 = read_u32le(fsb5.sample_header_offset+stream_header_size+0x00,sf);
next_sample_mode2 = read_u32le(fsb5.sample_header_offset+stream_header_size+0x04,sf);
next_data_offset = (((next_sample_mode2 & 0x03) << 25) | ((next_sample_mode1 >> 7) & 0x1FFFFFF)) << 5;
fsb5.stream_size = next_data_offset - data_offset;
@ -254,17 +258,18 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
fsb5.sample_header_offset += stream_header_size;
}
/* target stream not found*/
if (!fsb5.stream_offset || !fsb5.stream_size) goto fail;
if (!fsb5.stream_offset || !fsb5.stream_size)
goto fail;
/* get stream name */
if (fsb5.name_table_size) {
off_t name_suboffset = fsb5.base_header_size + fsb5.sample_header_size + 0x04*(target_subsong-1);
fsb5.name_offset = fsb5.base_header_size + fsb5.sample_header_size + read_32bitLE(name_suboffset,sf);
fsb5.name_offset = fsb5.base_header_size + fsb5.sample_header_size + read_u32le(name_suboffset,sf);
}
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(fsb5.channels,fsb5.loop_flag);
vgmstream = allocate_vgmstream(fsb5.channels, fsb5.loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = fsb5.sample_rate;
@ -379,10 +384,10 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
#ifdef VGM_USE_CELT
case 0x0C: { /* FMOD_SOUND_FORMAT_CELT [BIT.TRIP Presents Runner2 (PC), Full Bore (PC)] */
int is_multistream = fsb5.channels > 2;
fsb5.layers = (fsb5.channels <= 2) ? 1 : (fsb5.channels+1) / 2;
if (is_multistream) {
vgmstream->layout_data = build_layered_fsb5_celt(sf, &fsb5);
if (fsb5.layers > 1) {
vgmstream->layout_data = build_layered_fsb5(sf, &fsb5);
if (!vgmstream->layout_data) goto fail;
vgmstream->coding_type = coding_CELT_FSB;
vgmstream->layout_type = layout_layered;
@ -399,22 +404,18 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
#ifdef VGM_USE_ATRAC9
case 0x0D: {/* FMOD_SOUND_FORMAT_AT9 */
int is_multistream;
off_t configs_offset = fsb5.extradata_offset;
size_t configs_size = fsb5.extradata_size;
/* skip frame size in newer FSBs [Day of the Tentacle Remastered (Vita), Tearaway Unfolded (PS4)] */
if (configs_size >= 0x08 && (uint8_t)read_8bit(configs_offset, sf) != 0xFE) { /* ATRAC9 sync */
configs_offset += 0x04;
configs_size -= 0x04;
if (fsb5.extradata_size >= 0x08
&& read_u8(fsb5.extradata_offset, sf) != 0xFE) { /* not ATRAC9 sync */
fsb5.extradata_offset += 0x04;
fsb5.extradata_size -= 0x04;
}
is_multistream = (configs_size / 0x04) > 1;
fsb5.layers = (fsb5.extradata_size / 0x04);
if (is_multistream) {
/* multichannel made of various streams [Little Big Planet (Vita)] */
vgmstream->layout_data = build_layered_fsb5_atrac9(sf, &fsb5, configs_offset, configs_size);
if (fsb5.layers > 1) {
/* multichannel made of various layers [Little Big Planet (Vita)] */
vgmstream->layout_data = build_layered_fsb5(sf, &fsb5);
if (!vgmstream->layout_data) goto fail;
vgmstream->coding_type = coding_ATRAC9;
vgmstream->layout_type = layout_layered;
@ -424,7 +425,7 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
atrac9_config cfg = {0};
cfg.channels = vgmstream->channels;
cfg.config_data = read_32bitBE(configs_offset,sf);
cfg.config_data = read_u32be(fsb5.extradata_offset,sf);
//cfg.encoder_delay = 0x100; //todo not used? num_samples seems to count all data
vgmstream->codec_data = init_atrac9(&cfg);
@ -441,9 +442,9 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
uint8_t buf[0x100];
int bytes, format, average_bps, block_align;
format = read_16bitBE(fsb5.extradata_offset+0x00,sf);
block_align = (uint16_t)read_16bitBE(fsb5.extradata_offset+0x02,sf);
average_bps = (uint32_t)read_32bitBE(fsb5.extradata_offset+0x04,sf);
format = read_u16be(fsb5.extradata_offset+0x00,sf);
block_align = read_u16be(fsb5.extradata_offset+0x02,sf);
average_bps = read_u32be(fsb5.extradata_offset+0x04,sf);
/* rest: seek entries + mini seek table? */
/* XWMA encoder only does up to 6ch (doesn't use FSB multistreams for more) */
@ -460,15 +461,14 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
case 0x0F: {/* FMOD_SOUND_FORMAT_VORBIS [Shantae Half Genie Hero (PC), Pokemon Go (iOS)] */
vorbis_custom_config cfg = {0};
cfg.channels = vgmstream->channels;
cfg.sample_rate = vgmstream->sample_rate;
cfg.setup_id = read_32bitLE(fsb5.extradata_offset,sf);
cfg.channels = fsb5.channels;
cfg.sample_rate = fsb5.sample_rate;
cfg.setup_id = read_u32le(fsb5.extradata_offset,sf);
vgmstream->layout_type = layout_none;
vgmstream->coding_type = coding_VORBIS_custom;
vgmstream->codec_data = init_vorbis_custom(sf, fsb5.stream_offset, VORBIS_FSB, &cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_VORBIS_custom;
vgmstream->layout_type = layout_none;
break;
}
#endif
@ -509,32 +509,63 @@ fail:
}
static layered_layout_data* build_layered_fsb5_celt(STREAMFILE* sf, fsb5_header* fsb5) {
static layered_layout_data* build_layered_fsb5(STREAMFILE* sf, fsb5_header* fsb5) {
layered_layout_data* data = NULL;
STREAMFILE* temp_sf = NULL;
int i, layers = (fsb5->channels+1) / 2;
size_t interleave;
if (read_32bitBE(fsb5->stream_offset+0x00,sf) != 0x17C30DF3) /* FSB CELT frame ID */
goto fail;
interleave = 0x04+0x04+read_32bitLE(fsb5->stream_offset+0x04,sf); /* frame size */
//todo unknown interleave for max quality odd channel streams (found in test files)
/* FSB5 odd channels use 2ch+2ch...+1ch streams, and the last only goes up to 0x17a, and other
* streams only use that max (doesn't happen for smaller frames, even channels, or FSB4)
* however streams other than the last seem to be padded with 0s somehow and wont work */
if (interleave > 0x17a && (fsb5->channels % 2 == 1))
interleave = 0x17a;
size_t interleave, config = 0;
int i, layer_channels;
/* init layout */
data = init_layout_layered(layers);
data = init_layout_layered(fsb5->layers);
if (!data) goto fail;
/* open each layer subfile (1/2ch CELT streams: 2ch+2ch..+1ch or 2ch+2ch..+2ch) */
for (i = 0; i < layers; i++) {
int layer_channels = (i+1 == layers && fsb5->channels % 2 == 1)
? 1 : 2; /* last layer can be 1/2ch */
for (i = 0; i < fsb5->layers; i++) {
switch (fsb5->codec) {
case 0x0C: { /* CELT */
/* 2ch+2ch..+1ch or 2ch+2ch..+2ch = check last layer */
layer_channels = (i+1 == fsb5->layers && fsb5->channels % 2 == 1) ? 1 : 2;
if (read_u32be(fsb5->stream_offset+0x00,sf) != 0x17C30DF3) /* FSB CELT frame ID */
goto fail;
interleave = 0x04+0x04+read_u32le(fsb5->stream_offset+0x04,sf); /* frame size */
//todo unknown interleave for max quality odd channel streams (found in test files)
/* FSB5 odd channels use 2ch+2ch...+1ch streams, and the last only goes up to 0x17a, and other
* streams only use that max (doesn't happen for smaller frames, even channels, or FSB4)
* however streams other than the last seem to be padded with 0s somehow and wont work */
if (interleave > 0x17a && (fsb5->channels % 2 == 1))
interleave = 0x17a;
break;
}
case 0x0D: { /* ATRAC9 */
int channel_index;
size_t frame_size;
/* 2ch+2ch..+1/2ch */
config = read_u32be(fsb5->extradata_offset + 0x04*i, sf); /* ATRAC9 config */
channel_index = ((config >> 17) & 0x7);
frame_size = (((config >> 5) & 0x7FF) + 1) * (1 << ((config >> 3) & 0x2)); /* frame size * superframe index */
if (channel_index > 2)
goto fail; /* only 1/2ch expected */
layer_channels = (channel_index==0) ? 1 : 2;
interleave = frame_size;
//todo in test files with 2ch+..+1ch interleave is off (uses some strange padding)
break;
}
case 0x0F: { /* VORBIS */
layer_channels = fsb5->channels;
interleave = 0;
break;
}
default:
goto fail;
}
/* build the layer VGMSTREAM */
data->layers[i] = allocate_vgmstream(layer_channels, fsb5->loop_flag);
@ -545,94 +576,40 @@ static layered_layout_data* build_layered_fsb5_celt(STREAMFILE* sf, fsb5_header*
data->layers[i]->loop_start_sample = fsb5->loop_start;
data->layers[i]->loop_end_sample = fsb5->loop_end;
switch (fsb5->codec) {
#ifdef VGM_USE_CELT
data->layers[i]->codec_data = init_celt_fsb(layer_channels, CELT_0_11_0);
if (!data->layers[i]->codec_data) goto fail;
data->layers[i]->coding_type = coding_CELT_FSB;
data->layers[i]->layout_type = layout_none;
#else
goto fail;
case 0x0C: { /* CELT */
data->layers[i]->codec_data = init_celt_fsb(layer_channels, CELT_0_11_0);
if (!data->layers[i]->codec_data) goto fail;
data->layers[i]->coding_type = coding_CELT_FSB;
data->layers[i]->layout_type = layout_none;
break;
}
#endif
temp_sf = setup_fsb5_streamfile(sf, fsb5->stream_offset, fsb5->stream_size, layers, i, interleave);
if (!temp_sf) goto fail;
if (!vgmstream_open_stream(data->layers[i], temp_sf, 0x00))
goto fail;
close_streamfile(temp_sf);
temp_sf = NULL;
}
/* setup layered VGMSTREAMs */
if (!setup_layout_layered(data))
goto fail;
return data;
fail:
close_streamfile(temp_sf);
free_layout_layered(data);
return NULL;
}
static layered_layout_data* build_layered_fsb5_atrac9(STREAMFILE* sf, fsb5_header* fsb5, off_t configs_offset, size_t configs_size) {
layered_layout_data* data = NULL;
STREAMFILE* temp_sf = NULL;
int i, layers = (configs_size / 0x04);
size_t interleave = 0;
/* init layout */
data = init_layout_layered(layers);
if (!data) goto fail;
/* open each layer subfile (2ch+2ch..+1/2ch) */
for (i = 0; i < layers; i++) {
uint32_t config = read_32bitBE(configs_offset + 0x04*i, sf);
int channel_index, layer_channels;
size_t frame_size;
/* parse ATRAC9 config (see VGAudio docs) */
channel_index = ((config >> 17) & 0x7);
frame_size = (((config >> 5) & 0x7FF) + 1) * (1 << ((config >> 3) & 0x2)); /* frame size * superframe index */
if (channel_index > 2)
goto fail; /* only 1/2ch expected */
layer_channels = (channel_index==0) ? 1 : 2;
if (interleave == 0)
interleave = frame_size;
//todo in test files with 2ch+..+1ch interleave is off (uses some strange padding)
/* build the layer VGMSTREAM */
data->layers[i] = allocate_vgmstream(layer_channels, fsb5->loop_flag);
if (!data->layers[i]) goto fail;
data->layers[i]->sample_rate = fsb5->sample_rate;
data->layers[i]->num_samples = fsb5->num_samples;
data->layers[i]->loop_start_sample = fsb5->loop_start;
data->layers[i]->loop_end_sample = fsb5->loop_end;
#ifdef VGM_USE_ATRAC9
{
atrac9_config cfg = {0};
case 0x0D: { /* ATRAC9 */
atrac9_config cfg = {0};
cfg.channels = layer_channels;
cfg.config_data = config;
//cfg.encoder_delay = 0x100; //todo not used? num_samples seems to count all data
cfg.channels = layer_channels;
cfg.config_data = config;
//cfg.encoder_delay = 0x100; //todo not used? num_samples seems to count all data
data->layers[i]->codec_data = init_atrac9(&cfg);
if (!data->layers[i]->codec_data) goto fail;
data->layers[i]->coding_type = coding_ATRAC9;
data->layers[i]->layout_type = layout_none;
}
#else
goto fail;
data->layers[i]->codec_data = init_atrac9(&cfg);
if (!data->layers[i]->codec_data) goto fail;
data->layers[i]->coding_type = coding_ATRAC9;
data->layers[i]->layout_type = layout_none;
break;
}
#endif
temp_sf = setup_fsb5_streamfile(sf, fsb5->stream_offset, fsb5->stream_size, layers, i, interleave);
default:
goto fail;
}
temp_sf = setup_fsb5_streamfile(sf, fsb5->stream_offset, fsb5->stream_size, fsb5->layers, i, interleave);
if (!temp_sf) goto fail;
if (!vgmstream_open_stream(data->layers[i], temp_sf, 0x00))

View File

@ -10,11 +10,11 @@ static void find_hca_key(hca_codec_data* hca_data, uint64_t* p_keycode, uint16_t
/* CRI HCA - streamed audio from CRI ADX2/Atom middleware */
VGMSTREAM * init_vgmstream_hca(STREAMFILE* sf) {
VGMSTREAM* init_vgmstream_hca(STREAMFILE* sf) {
return init_vgmstream_hca_subkey(sf, 0x0000);
}
VGMSTREAM * init_vgmstream_hca_subkey(STREAMFILE* sf, uint16_t subkey) {
VGMSTREAM* init_vgmstream_hca_subkey(STREAMFILE* sf, uint16_t subkey) {
VGMSTREAM * vgmstream = NULL;
hca_codec_data* hca_data = NULL;
clHCA_stInfo* hca_info;
@ -122,6 +122,9 @@ fail:
static inline void test_key(hca_codec_data* hca_data, uint64_t key, uint16_t subkey, int* best_score, uint64_t* best_keycode) {
int score;
//;VGM_LOG("HCA: test key=%08x%08x, subkey=%04x\n",
// (uint32_t)((key >> 32) & 0xFFFFFFFF), (uint32_t)(key & 0xFFFFFFFF), subkey);
if (subkey) {
key = key * ( ((uint64_t)subkey << 16u) | ((uint16_t)~subkey + 2u) );
}
@ -135,6 +138,9 @@ static inline void test_key(hca_codec_data* hca_data, uint64_t key, uint16_t sub
if (score < 0)
return;
//;VGM_LOG("HCA: ok key=%08x%08x, subkey=%04x, score=%i\n",
// (uint32_t)((key >> 32) & 0xFFFFFFFF), (uint32_t)(key & 0xFFFFFFFF), subkey, score);
/* update if something better is found */
if (*best_score <= 0 || (score < *best_score && score > 0)) {
*best_score = score;

View File

@ -193,6 +193,7 @@ static const hcakey_info hcakey_list[] = {
{4988006236073}, // 000004895C56FFA9
// Castle & Dragon (iOS/Android)
// Gunbit (Android)
{20140528}, // 00000000013351F0
// Uta no Prince sama Shining Live (iOS/Android)
@ -373,9 +374,6 @@ static const hcakey_info hcakey_list[] = {
/* Re:Zero - Lost in Memories (Android) */
{1611432018519751642}, // 165CF4E2138F7BDA
/* D4DJ Groovy Mix (Android) [base files] */
{393410674916959300}, // 0575ACECA945A444
/* Toji no Miko: Kizamishi Issen no Tomoshibi (Android) */
{62057514034227932}, // 00DC78FAEFA76ADC
@ -391,6 +389,224 @@ static const hcakey_info hcakey_list[] = {
/* Sakura Kakumei (iOS/Android) */
{382759}, // 000000000005D727
/* Uma Musume (Android) */
{75923756697503}, // 0000450D608C479F
/* D4DJ Groovy Mix (Android) [base files] */
{393410674916959300}, // 0575ACECA945A444
/* D4DJ Groovy Mix (Android) [music_* files, per-song later mixed with subkey] */
{0x59f449354d063308},
{0x33848be13a2884a3},
{0xf7e53533d82d48dd},
{0x244a92885ab77b7c},
{0xce0796d2a956dc5a},
{0x73667711348f833f},
{0x8032f83cbf0076a1},
{0x7a708e291692abb9},
{0x9ebb560685327081},
{0x065c2f8500bc12c8},
{0x73621a0d321e60c2},
{0xfcce3164db70522d},
{0xf4093992cadd3708},
{0xf965a1086b3179c3},
{0x54aaada4a1b8deef},
{0xd4d2a706a06377ef},
{0x0de4959221bc2675},
{0x2ecdf66c680f3a45},
{0x24c0b49097e9ebff},
{0xc28331aab2612584},
{0x6750f4d05183bc01},
{0xda65af760e02c6ee},
{0x217782495c8b2972},
{0xbf7712e175c0b265},
{0x3804d53c43293080},
{0xd0ed8e940d8ed705},
{0xf74cf8d4a5d008ce},
{0xeb8aac34dc178f65},
{0xbf5902d516db6ed5},
{0xad071dce0c070e65},
{0x56ecfc7ef4c65be8},
{0xf42d31b5ecd1aec1},
{0x0a8ee7a3a20ce822},
{0xb9cedc6d6738d481},
{0xdc2680acfd1b9b64},
{0x9fbd8a172d5ba3e3},
{0xb65d86624a857788},
{0x8f5f05c835f7280e},
{0x55912db4388961ac},
{0x4ba9a9471f49b74e},
{0x6ba36cadf1e045cf},
{0x230c9509bbc3df0d},
{0x7148dda3afa76439},
{0xa6cefd4472568948},
{0xfe31517282d40690},
{0x0a6a15cc9722257d},
{0x447d08ca3148599d},
{0xb30acd0a43754e5c},
{0xc05f8e4ea8c3e487},
{0x8463554672bfb716},
{0x0d40ccba5e10385a},
{0xeeaf8d2458ccdb36},
{0x0d80d3dcc7c75cea},
{0x8efa09c6df3991a4},
{0x6867cc75639ee0c3},
{0xdf30ed86c3d00ffb},
{0xf7e11ec9c94402f1},
{0xdb03ecca6a0151e2},
{0x212bbee264be5b06},
{0x87025d78a57af15b},
{0x6139edfb8889032d},
{0xe67f4da6012c5d24},
{0x05e3eba376e0b3dd},
{0xf7edc5d72fdd6ceb},
{0x031e072678ad18a3},
{0x290fbc93e184af1e},
{0xfd3ea450350d666f},
{0x037d1452c192b1e6},
{0x15f82c1617013c36},
{0x5d1f3fdbbb036f8d},
{0x5089e16d7a676ab1},
{0x15bb78c31db0a0b6},
{0xe4a1737fa3d34ccb},
{0xd2cb7692d690b3a7},
{0x1bbad843d5971358},
{0x7ed1fa0b6ec8f9b3},
{0x529969b7e1e9ac18},
{0x2f3528a4b9eaa0f7},
{0x90fefcd350bd2cb8},
{0xee8da2806a13eecf},
{0xcd3fb92065d9f373},
{0x67b89634319c1d36},
{0x114245b98dcb75bf},
{0xaff9df030e63e5ba},
{0x0aebfdf85aae4424},
{0x4a4462cb0375001e},
{0xfd9fa5bcb347c01b},
{0x7ce69eed81f01019},
{0xcb3d9329d40490b2},
{0xe0b8bb03c74bb3d0},
{0xd9a00c9bc93014a8},
{0x84dc42f5a05f77cf},
{0x420d4dd413053980},
{0xff7640b46d72b337},
{0x711ef85045b8c26e},
{0xe553dba6592293d8},
{0x9ebbaf63ffe9d9ef},
{0x00e978d394512cfd},
{0x6f9735c02faf6aae},
{0x8258ddd6a1d0849b},
{0xe5e83d31e64273f8},
{0x35128087963cd5be},
{0x9de6ace9a0e62f44},
{0xd4c36ab962153420},
{0xe77aa2f3c90a4e84},
{0x9f6881f6d7a91658},
{0x6c6c1fd51e28a1e7},
{0x867d47a7d8376402},
{0x79c5f00d243e3097},
{0x8f0e96b4f71f724f},
{0xcccb5077d978def4},
{0x8ad213dddedc9022},
{0x6aa0ff881da270e7},
{0xf616642579ba5850},
{0x5205a666f976d42f},
{0xa139c29e97fcefb4},
{0xdb402bd08d522f34},
{0x2f2c0ff3ff235bd6},
{0xa0316b536c8b7540},
{0x260a354b925afeaf},
{0x567d295828f1b08a},
{0xc24049b9f7ed3105},
{0x8815d2dffd77a71e},
{0x2b4a83e7d54d0554},
{0xf6b0dc07ea8ebeb7},
{0xbb7be9c7c620f504},
{0x7465c7c473e53a40},
{0xa4481f97a8d4d01c},
{0x0046fd87a21859ac},
{0xf1db3c1d9542063a},
{0xaba147637d52efbe},
{0x298a0fa05c3f355f},
{0x465e30321a4091f2},
{0xc40c398f7e80d184},
{0xa76262c2557be76f},
{0xaef2954dc3657336},
{0xa3711cc06f9b86c2},
{0xcb60232f2f27ace5},
{0x4c7d7c251c6bfa95},
{0xf877dea1180b9b90},
{0x9ce13dcb2fb389cc},
{0xcbf4f1324081e0a6},
{0x444dda6d55d76095},
{0xb2bd99fa559b9062},
{0x1ed521f6dd691255},
{0xdfad847a86a126bb},
{0x2a47feac8dc3ca9c},
{0xc352bbf3d519256e},
{0xfdec74b23d8b494b},
{0x1dd21a1244ca12f1},
{0xaf9d7a05b0fc3d9e},
{0xa662be1601e49476},
{0xd2ce91dbfc209b10},
{0x57bdc58e4c06fc76},
{0xe350bffcdc9cb686},
{0xc7da8e6f0e2fe399},
{0x984363837811b08a},
{0xdcd2a403fb01e164},
{0xb2770dced3cfd9a7},
{0x0df31e26a7b036a2},
{0x3f25fe3395b3154c},
{0x889d47adc9595ffa},
{0xc04264e8f34ad5c0},
{0xc222e70e4a79f7c3},
{0x7c7dd6d9f3761102},
{0x904f50c5ce8ec6e4},
{0xb7bff4fbf66be43f},
{0x776c4aded0bca5d1},
{0x38ad99a045dc971f},
{0xb921c3992807dadd},
{0x68d576c631e61265},
{0xcede847721873fc2},
{0x40443974a0a86b8b},
{0x57111c24801b44a1},
{0x6a15a9610d10d210},
{0xb18fb83ee356fb94},
{0x59b1257242c40109},
{0x22ef086d7d6ce520},
{0x76254d1ef50c004c},
{0x7678588b0adf59df},
{0x4fffee4065d22bec},
{0x0dc128f2fd48bf4b},
{0xfb647d074e53fab6},
{0x55b7b25821375a02},
{0x5b877af6e52af19b},
{0xba26e58923a5da5d},
{0x52d065d9ccdb8696},
{0xf0c624dc0385adae},
{0xb7a5297198a73155},
{0xda08e9d3961c93f2},
{0x8328668369631cc1},
{0xb140168a47d55b92},
{0x6699616be2c50115},
{0xcee66d585d689851},
{0x5771a2c76f36c898},
{0x5e91a3790c32e2b3},
{0xe4e11a71fe620e3a},
{0x1bb363adcf4eb3f8},
{0xa691936caf4d91d0},
{0x94466db0d3c10f4b},
{0x47f52330df2ead11},
{0x33848be13a2884a3},
{0xc9f159f60b065f91},
{0xdd9ca800a7123d6f},
{0xa090c8ebf8463d05},
{0xa5c1adeb7919845f},
{0x58d97e6f3d1aee86},
{0x71b5fa3761d6726d},
{0x1980271cfe0da9bd},
{0x945cdb3cf1f29e52},
{0x7f0feac6be7def5b},
/* Dragalia Lost (iOS/Android) */
{2967411924141, subkeys_dgl, sizeof(subkeys_dgl) / sizeof(subkeys_dgl[0]) }, // 000002B2E7889CAD

View File

@ -441,6 +441,7 @@ VGMSTREAM* init_vgmstream_ogg_vorbis_callbacks(STREAMFILE* sf, ov_callbacks* cal
size_t stream_size = ovmi->stream_size ?
ovmi->stream_size :
get_streamfile_size(sf) - start;
int force_seek = 0;
int disable_reordering = ovmi->disable_reordering;
@ -539,12 +540,20 @@ VGMSTREAM* init_vgmstream_ogg_vorbis_callbacks(STREAMFILE* sf, ov_callbacks* cal
loop_end_found = 1;
}
}
else if (strstr(comment,"LOOPMS=") == comment) { /* Sonic Robo Blast 2 */
/* Convert from milliseconds to samples. */
/* (x ms) * (y samples/s) / (1000 ms/s) */
loop_start = atol(strrchr(comment,'=')+1) * sample_rate / 1000;
else if (strstr(comment,"LOOPMS=") == comment) { /* Sonic Robo Blast 2 (PC) */
loop_start = atol(strrchr(comment,'=')+1) * sample_rate / 1000; /* ms to samples */
loop_flag = (loop_start >= 0);
}
else if (strstr(comment,"COMMENT=- loopTime ") == comment) { /* Aristear Remain (PC) */
loop_start = atol(strrchr(comment,' ')+1) / 1000.0f * sample_rate; /* ms to samples */
loop_flag = (loop_start >= 0);
/* files have all page granule positions -1 except a few close to loop. This throws off
* libvorbis seeking (that uses granules), so we need manual fix = slower. Could be detected
* by checking granules in the first new OggS pages (other games from same dev don't use
* loopTime not have wrong granules though) */
force_seek = 1;
}
/* Hatsune Miku Project DIVA games, though only 'Arcade Future Tone' has >4ch files
* ENCODER tag is common but ogg_vorbis_encode looks unique enough
@ -562,10 +571,11 @@ VGMSTREAM* init_vgmstream_ogg_vorbis_callbacks(STREAMFILE* sf, ov_callbacks* cal
}
ogg_vorbis_set_disable_reordering(data, disable_reordering);
ogg_vorbis_set_force_seek(data, force_seek);
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels,loop_flag);
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->codec_data = data;

View File

@ -633,6 +633,22 @@ static VGMSTREAM* init_subfile(txth_header* txth) {
if (txth->num_samples)
vgmstream->num_samples = txth->num_samples;
/* load some fields for possible calcs */
if (!txth->channels)
txth->channels = vgmstream->channels;
if (!txth->sample_rate)
txth->sample_rate = vgmstream->sample_rate;
if (!txth->interleave)
txth->interleave = vgmstream->interleave_block_size;
if (!txth->interleave_last)
txth->interleave_last = vgmstream->interleave_last_block_size;
//if (!txth->loop_flag) //?
// txth->loop_flag = vgmstream->loop_flag;
/* sometimes headers set loop start but getting loop_end before subfile init is hard */
if (!txth->loop_end_sample && txth->loop_flag)
txth->loop_end_sample = vgmstream->num_samples;
/* other init */
if (txth->loop_flag) {
vgmstream_force_loop(vgmstream, txth->loop_flag, txth->loop_start_sample, txth->loop_end_sample);
}
@ -647,19 +663,6 @@ static VGMSTREAM* init_subfile(txth_header* txth) {
//todo: other combos with subsongs + subfile?
/* load some fields for possible calcs */
if (!txth->channels)
txth->channels = vgmstream->channels;
if (!txth->sample_rate)
txth->sample_rate = vgmstream->sample_rate;
if (!txth->interleave)
txth->interleave = vgmstream->interleave_block_size;
if (!txth->interleave_last)
txth->interleave_last = vgmstream->interleave_last_block_size;
//if (!txth->loop_flag) //?
// txth->loop_flag = vgmstream->loop_flag;
close_streamfile(sf_sub);
return vgmstream;
@ -786,12 +789,12 @@ static void set_body_chunk(txth_header* txth) {
}
}
static int parse_keyval(STREAMFILE* sf, txth_header* txth, const char * key, char * val);
static int parse_num(STREAMFILE* sf, txth_header* txth, const char * val, uint32_t * out_value);
static int parse_string(STREAMFILE* sf, txth_header* txth, const char * val, char * str);
static int parse_coef_table(STREAMFILE* sf, txth_header* txth, const char * val, uint8_t * out_value, size_t out_size);
static int parse_name_table(txth_header* txth, char * val);
static int is_string(const char * val, const char * cmp);
static int parse_keyval(STREAMFILE* sf, txth_header* txth, const char* key, char* val);
static int parse_num(STREAMFILE* sf, txth_header* txth, const char* val, uint32_t* out_value);
static int parse_string(STREAMFILE* sf, txth_header* txth, const char* val, char* str);
static int parse_coef_table(STREAMFILE* sf, txth_header* txth, const char* val, uint8_t* out_value, size_t out_size);
static int parse_name_table(txth_header* txth, char* val);
static int is_string(const char* val, const char* cmp);
static int get_bytes_to_samples(txth_header* txth, uint32_t bytes);
static int get_padding_size(txth_header* txth, int discard_empty);
@ -818,24 +821,29 @@ static int parse_txth(txth_header* txth) {
}
/* read lines */
while (txt_offset < file_size) {
{
char line[TXT_LINE_MAX];
char key[TXT_LINE_MAX] = {0}, val[TXT_LINE_MAX] = {0}; /* at least as big as a line to avoid overflows (I hope) */
int ok, bytes_read, line_ok;
char key[TXT_LINE_MAX];
char val[TXT_LINE_MAX];
/* at least as big as a line to avoid overflows (I hope) */
bytes_read = read_line(line, sizeof(line), txt_offset, txth->sf_text, &line_ok);
if (!line_ok) goto fail;
//;VGM_LOG("TXTH: line=%s\n",line);
while (txt_offset < file_size) {
int ok, bytes_read, line_ok;
txt_offset += bytes_read;
bytes_read = read_line(line, sizeof(line), txt_offset, txth->sf_text, &line_ok);
if (!line_ok) goto fail;
//;VGM_LOG("TXTH: line=%s\n",line);
/* get key/val (ignores lead spaces, stops at space/comment/separator) */
ok = sscanf(line, " %[^ \t#=] = %[^\t#\r\n] ", key,val);
if (ok != 2) /* ignore line if no key=val (comment or garbage) */
continue;
txt_offset += bytes_read;
if (!parse_keyval(txth->sf, txth, key, val)) /* read key/val */
goto fail;
/* get key/val (ignores lead spaces, stops at space/comment/separator) */
ok = sscanf(line, " %[^ \t#=] = %[^\t#\r\n] ", key,val);
if (ok != 2) /* ignore line if no key=val (comment or garbage) */
continue;
if (!parse_keyval(txth->sf, txth, key, val)) /* read key/val */
goto fail;
}
}
if (!txth->loop_flag_set)
@ -852,7 +860,7 @@ fail:
return 0;
}
static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char * key, char * val) {
static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, char* val) {
//;VGM_LOG("TXTH: key=%s, val=%s\n", key, val);
/* CODEC */
@ -1197,6 +1205,8 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char * key, ch
/* HEADER/BODY CONFIG */
else if (is_string(key,"header_file")) {
/* first remove old head if needed */
if (txth->sf_head_opened) {
close_streamfile(txth->sf_head);
txth->sf_head = NULL;
@ -1205,7 +1215,10 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char * key, ch
if (is_string(val,"null")) { /* reset */
if (!txth->streamfile_is_txth) {
txth->sf_head = txth->sf;
txth->sf_head = txth->sf; /* base non-txth file */
}
else {
goto fail; /* .txth, nothing to fall back */
}
}
else if (val[0]=='*' && val[1]=='.') { /* basename + extension */
@ -1222,6 +1235,8 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char * key, ch
}
}
else if (is_string(key,"body_file")) {
/* first remove old body if needed */
if (txth->sf_body_opened) {
close_streamfile(txth->sf_body);
txth->sf_body = NULL;
@ -1230,7 +1245,10 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char * key, ch
if (is_string(val,"null")) { /* reset */
if (!txth->streamfile_is_txth) {
txth->sf_body = txth->sf;
txth->sf_body = txth->sf; /* base non-txth file */
}
else {
goto fail; /* .txth, nothing to fall back */
}
}
else if (val[0]=='*' && val[1]=='.') { /* basename + extension */
@ -1316,10 +1334,11 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char * key, ch
return 1;
fail:
VGM_LOG("TXTH: error parsing key=%s, val=%s\n", key, val);
return 0;
}
static int is_substring(const char * val, const char * cmp, int inline_field) {
static int is_substring(const char* val, const char* cmp, int inline_field) {
char chr;
int len = strlen(cmp);
/* "val" must contain "cmp" entirely */
@ -1339,7 +1358,7 @@ static int is_substring(const char * val, const char * cmp, int inline_field) {
return len;
}
static int is_string(const char * val, const char * cmp) {
static int is_string(const char* val, const char* cmp) {
int len = is_substring(val, cmp, 0);
if (!len) return 0;
@ -1353,11 +1372,11 @@ static int is_string(const char * val, const char * cmp) {
return len;
}
static int is_string_field(const char * val, const char * cmp) {
static int is_string_field(const char* val, const char* cmp) {
return is_substring(val, cmp, 1);
}
static uint16_t get_string_wchar(const char * val, int pos, int *csize) {
static uint16_t get_string_wchar(const char* val, int pos, int* csize) {
uint16_t wchar = 0;
if ((val[pos] & 0x80) && val[pos+1] != '\0') {
@ -1379,10 +1398,11 @@ static uint16_t get_string_wchar(const char * val, int pos, int *csize) {
return wchar;
}
static int is_string_match(const char * text, const char * pattern) {
int t_pos = 0, p_pos = 0;
static int is_string_match(const char* text, const char* pattern) {
int t_pos = 0, p_pos = 0, t_len = 0, p_len = 0;
int p_size, t_size;
uint16_t p_char, t_char;
//;VGM_LOG("TXTH: match '%s' vs '%s'\n", text,pattern);
/* compares 2 strings (case insensitive, to a point) allowing wildcards
@ -1390,7 +1410,7 @@ static int is_string_match(const char * text, const char * pattern) {
*
* does some bleh UTF-8 handling, consuming dual bytes if needed (codepages set char's eighth bit).
* as such it's slower than standard funcs, but it's not like we need it to be ultra fast.
* */
*/
while (text[t_pos] != '\0' && pattern[p_pos] != '\0') {
//;VGM_LOG("TXTH: compare '%s' vs '%s'\n", (text+t_pos), (pattern+p_pos));
@ -1402,10 +1422,28 @@ static int is_string_match(const char * text, const char * pattern) {
while(text[t_pos] != '\0') {
t_char = get_string_wchar(text, t_pos, &t_size);
//;VGM_LOG("TXTH: consume %i '%s'\n", t_size, (text+t_pos) );
//;VGM_LOG("TXTH: consume %i '%s'\n", t_size, (text+t_pos));
if (t_char == p_char)
break;
/* break from this wildcard (AKA possible match = stop consuming) only if:
* - rest of string has the same length (=could be a match)
* - there are more wildcards (=would consume other parts)
* otherwise current wildcard must keep consuming text (without this,
* sound_1_1.adx vs *_1.adx wouldn't match since the _ would stop too early)
*/
if (t_char == p_char) {
if (strchr(&pattern[p_pos], '*'))
break;
if (!t_len || !p_len) { /* lazy init helpful? */
t_len = strlen(text);
p_len = strlen(pattern);
}
//;VGM_LOG("TXTH: possible match '%s' vs '%s'\n", (text+t_pos), (pattern+p_pos));
/* not strcmp to allow case insensitive-ness, handled below */
if (t_len - t_pos == p_len - p_pos)
break;
}
t_pos += t_size;
}
}
@ -1415,21 +1453,24 @@ static int is_string_match(const char * text, const char * pattern) {
p_pos++;
t_pos += t_size;
}
else { /* must match 1:1 */
else { /* must match char 1:1 */
//;VGM_LOG("TXTH: test 1:1 '%s' vs '%s'\n", (text+t_pos), (pattern+p_pos));
t_char = get_string_wchar(text, t_pos, &t_size);
p_char = get_string_wchar(pattern, p_pos, &p_size);
if (p_char != t_char)
break;
p_pos += p_size;
t_pos += t_size;
p_pos += p_size;
}
}
//;VGM_LOG("TXTH: current '%s' vs '%s'\n", (text+t_pos), (pattern+p_pos));
//;VGM_LOG("TXTH: match '%s' vs '%s' = %s\n", text,pattern, (text[t_pos] == '\0' && pattern[p_pos] == '\0') ? "true" : "false");
/* either all chars consumed/matched and both pos point to null, or one didn't so string didn't match */
return text[t_pos] == '\0' && pattern[p_pos] == '\0';
}
static int parse_string(STREAMFILE* sf, txth_header* txth, const char * val, char * str) {
static int parse_string(STREAMFILE* sf, txth_header* txth, const char* val, char* str) {
int n = 0;
/* read string without trailing spaces */
@ -1438,7 +1479,7 @@ static int parse_string(STREAMFILE* sf, txth_header* txth, const char * val, cha
return n;
}
static int parse_coef_table(STREAMFILE* sf, txth_header* txth, const char * val, uint8_t * out_value, size_t out_size) {
static int parse_coef_table(STREAMFILE* sf, txth_header* txth, const char* val, uint8_t* out_value, size_t out_size) {
uint32_t byte;
int done = 0;
@ -1466,7 +1507,7 @@ fail:
return 0;
}
static int parse_name_table(txth_header* txth, char * name_list) {
static int parse_name_table(txth_header* txth, char* name_list) {
STREAMFILE* nameFile = NULL;
off_t txt_offset, file_size;
char fullname[PATH_LIMIT];
@ -1516,55 +1557,59 @@ static int parse_name_table(txth_header* txth, char * name_list) {
txth->name_values_count = 0;
/* read lines and find target filename, format is (filename): value1, ... valueN */
while (txt_offset < file_size) {
{
char line[TXT_LINE_MAX];
char key[TXT_LINE_MAX] = {0}, val[TXT_LINE_MAX] = {0};
int ok, bytes_read, line_ok;
char key[TXT_LINE_MAX];
char val[TXT_LINE_MAX];
bytes_read = read_line(line, sizeof(line), txt_offset, nameFile, &line_ok);
if (!line_ok) goto fail;
//;VGM_LOG("TXTH: line=%s\n",line);
while (txt_offset < file_size) {
int ok, bytes_read, line_ok;
txt_offset += bytes_read;
bytes_read = read_line(line, sizeof(line), txt_offset, nameFile, &line_ok);
if (!line_ok) goto fail;
//;VGM_LOG("TXTH: line=%s\n",line);
/* get key/val (ignores lead spaces, stops at space/comment/separator) */
ok = sscanf(line, " %[^ \t#:] : %[^\t#\r\n] ", key,val);
if (ok != 2) { /* ignore line if no key=val (comment or garbage) */
/* try again with " (empty): (val)) */
key[0] = '\0';
ok = sscanf(line, " : %[^\t#\r\n] ", val);
if (ok != 1)
continue;
}
txt_offset += bytes_read;
//;VGM_LOG("TXTH: compare name '%s'\n", key);
/* parse values if key (name) matches default ("") or filename with/without extension */
if (key[0]=='\0'
|| is_string_match(filename, key)
|| is_string_match(basename, key)
|| is_string_match(fullname, key)) {
int n;
char subval[TXT_LINE_MAX];
const char *current = val;
while (current[0] != '\0') {
ok = sscanf(current, " %[^\t#\r\n,]%n ", subval, &n);
/* get key/val (ignores lead spaces, stops at space/comment/separator) */
ok = sscanf(line, " %[^ \t#:] : %[^\t#\r\n] ", key,val);
if (ok != 2) { /* ignore line if no key=val (comment or garbage) */
/* try again with " (empty): (val)) */
key[0] = '\0';
ok = sscanf(line, " : %[^\t#\r\n] ", val);
if (ok != 1)
goto fail;
current += n;
if (current[0] == ',')
current++;
if (!parse_num(txth->sf_head,txth,subval, &txth->name_values[txth->name_values_count])) goto fail;
txth->name_values_count++;
if (txth->name_values_count >= 16) /* surely nobody needs that many */
goto fail;
continue;
}
//;VGM_LOG("TXTH: found name '%s'\n", key);
break; /* target found */
//;VGM_LOG("TXTH: compare name '%s'\n", key);
/* parse values if key (name) matches default ("") or filename with/without extension */
if (key[0]=='\0'
|| is_string_match(filename, key)
|| is_string_match(basename, key)
|| is_string_match(fullname, key)) {
int n;
char subval[TXT_LINE_MAX];
const char *current = val;
while (current[0] != '\0') {
ok = sscanf(current, " %[^\t#\r\n,]%n ", subval, &n);
if (ok != 1)
goto fail;
current += n;
if (current[0] == ',')
current++;
if (!parse_num(txth->sf_head,txth,subval, &txth->name_values[txth->name_values_count])) goto fail;
txth->name_values_count++;
if (txth->name_values_count >= 16) /* surely nobody needs that many */
goto fail;
}
//;VGM_LOG("TXTH: found name '%s'\n", key);
break; /* target found */
}
}
}
@ -1578,7 +1623,7 @@ fail:
}
static int parse_num(STREAMFILE* sf, txth_header* txth, const char * val, uint32_t * out_value) {
static int parse_num(STREAMFILE* sf, txth_header* txth, const char* val, uint32_t* out_value) {
/* out_value can be these, save before modifying */
uint32_t value_mul = txth->value_mul;
uint32_t value_div = txth->value_div;
@ -1624,8 +1669,10 @@ static int parse_num(STREAMFILE* sf, txth_header* txth, const char * val, uint32
int hex = (val[1]=='0' && val[2]=='x');
/* can happen when loading .txth and not setting body/head */
if (!sf)
if (!sf) {
VGM_LOG("TXTH: wrong header\n");
goto fail;
}
/* read exactly N fields in the expected format */
if (strchr(val,':') && strchr(val,'$')) {
@ -1641,8 +1688,10 @@ static int parse_num(STREAMFILE* sf, txth_header* txth, const char * val, uint32
/* adjust offset */
offset += txth->base_offset;
if (/*offset < 0 ||*/ offset > get_streamfile_size(sf))
if (/*offset < 0 ||*/ offset > get_streamfile_size(sf)) {
VGM_LOG("TXTH: wrong offset %x + %x\n", offset - txth->base_offset, txth->base_offset);
goto fail;
}
if (ed1 == 'B' && ed2 == 'E')
big_endian = 1;
@ -1652,11 +1701,12 @@ static int parse_num(STREAMFILE* sf, txth_header* txth, const char * val, uint32
if (subsong_spacing)
offset = offset + subsong_spacing * (txth->target_subsong - 1);
//;VGM_LOG("TXTH: read at offset %x + %x\n", offset - txth->base_offset, txth->base_offset);
switch(size) {
case 1: value = (uint8_t)read_8bit(offset,sf); break;
case 2: value = big_endian ? (uint16_t)read_16bitBE(offset,sf) : (uint16_t)read_16bitLE(offset,sf); break;
case 3: value = (big_endian ? (uint32_t)read_32bitBE(offset,sf) : (uint32_t)read_32bitLE(offset,sf)) & 0x00FFFFFF; break;
case 4: value = big_endian ? (uint32_t)read_32bitBE(offset,sf) : (uint32_t)read_32bitLE(offset,sf); break;
case 1: value = read_u8(offset,sf); break;
case 2: value = big_endian ? read_u16be(offset,sf) : read_u16le(offset,sf); break;
case 3: value = (big_endian ? read_u32be(offset,sf) : read_u32le(offset,sf)) & 0x00FFFFFF; break;
case 4: value = big_endian ? read_u32be(offset,sf) : read_u32le(offset,sf); break;
default: goto fail;
}
value_read = 1;
@ -1728,7 +1778,7 @@ static int parse_num(STREAMFILE* sf, txth_header* txth, const char * val, uint32
/* move to next field (if any) */
val += n;
//;VGM_LOG("TXTH: val='%s', n=%i, brackets=%i, result=%i\n", val, n, brackets, result);
//;VGM_LOG("TXTH: val='%s', n=%i, brackets=%i, result=0x%x\n", val, n, brackets, result);
}
/* unbalanced brackets */
@ -1750,6 +1800,7 @@ static int parse_num(STREAMFILE* sf, txth_header* txth, const char * val, uint32
//;VGM_LOG("TXTH: final result %u (0x%x)\n", result, result);
return 1;
fail:
VGM_LOG("TXTH: error parsing num '%s'\n", val);
return 0;
}

View File

@ -1262,7 +1262,7 @@ static inline int is_match(const char* str1, const char* str2) {
static void parse_params(txtp_entry* entry, char* params) {
/* parse params: #(commands) */
int n, nc, nm, mc;
char command[TXTP_LINE_MAX] = {0};
char command[TXTP_LINE_MAX];
play_config_t* tcfg = &entry->config;
entry->range_start = 0;
@ -1793,7 +1793,7 @@ fail:
static int is_substring(const char* val, const char* cmp) {
int n;
char subval[TXTP_LINE_MAX] = {0};
char subval[TXTP_LINE_MAX];
/* read string without trailing spaces or comments/commands */
if (sscanf(val, " %s%n[^ #\t\r\n]%n", subval, &n, &n) != 1)
@ -1897,42 +1897,48 @@ static txtp_header* parse_txtp(STREAMFILE* sf) {
/* read and parse lines */
while (txt_offset < file_size) {
{
char line[TXTP_LINE_MAX];
char key[TXTP_LINE_MAX] = {0}, val[TXTP_LINE_MAX] = {0}; /* at least as big as a line to avoid overflows (I hope) */
char filename[TXTP_LINE_MAX] = {0};
int ok, bytes_read, line_ok;
char key[TXTP_LINE_MAX];
char val[TXTP_LINE_MAX];
char filename[TXTP_LINE_MAX];
/* at least as big as a line to avoid overflows (I hope) */
bytes_read = read_line(line, sizeof(line), txt_offset, sf, &line_ok);
if (!line_ok) goto fail;
while (txt_offset < file_size) {
int ok, bytes_read, line_ok;
txt_offset += bytes_read;
bytes_read = read_line(line, sizeof(line), txt_offset, sf, &line_ok);
if (!line_ok) goto fail;
/* get key/val (ignores lead/trail spaces, # may be commands or comments) */
ok = sscanf(line, " %[^ \t#=] = %[^\t\r\n] ", key,val);
if (ok == 2) { /* key=val */
if (!parse_keyval(txtp, key, val)) /* read key/val */
txt_offset += bytes_read;
/* try key/val (ignores lead/trail spaces, # may be commands or comments) */
ok = sscanf(line, " %[^ \t#=] = %[^\t\r\n] ", key,val);
if (ok == 2) { /* key=val */
if (!parse_keyval(txtp, key, val)) /* read key/val */
goto fail;
continue;
}
/* must be a filename (only remove spaces from start/end, as filenames con contain mid spaces/#/etc) */
ok = sscanf(line, " %[^\t\r\n] ", filename);
if (ok != 1) /* not a filename either */
continue;
if (filename[0] == '#')
continue; /* simple comment */
/* filename with settings */
if (!add_entry(txtp, filename, 0))
goto fail;
continue;
}
/* must be a filename (only remove spaces from start/end, as filenames con contain mid spaces/#/etc) */
ok = sscanf(line, " %[^\t\r\n] ", filename);
if (ok != 1) /* not a filename either */
continue;
if (filename[0] == '#')
continue; /* simple comment */
/* filename with settings */
if (!add_entry(txtp, filename, 0))
goto fail;
}
/* mini-txth: if no entries are set try with filename, ex. from "song.ext#3.txtp" use "song.ext#3"
* (it's possible to have default "commands" inside the .txtp plus filename+settings) */
if (txtp->entry_count == 0) {
char filename[PATH_LIMIT] = {0};
char filename[PATH_LIMIT];
filename[0] = '\0';
get_streamfile_basename(sf, filename, sizeof(filename));
add_entry(txtp, filename, 0);