Compare commits

...

1 commit

Author SHA1 Message Date
Christopher Snowhill
dd585311df Apply new source files 2024-01-21 18:13:28 -08:00
118 changed files with 26704 additions and 24051 deletions

View file

@ -418,7 +418,8 @@ int decode_get_samples_per_frame(VGMSTREAM* vgmstream) {
case coding_XBOX_IMA_int:
case coding_FSB_IMA:
case coding_WWISE_IMA:
case coding_CD_IMA:
case coding_CD_IMA: /* (0x24 - 0x04) * 2 */
case coding_CRANKCASE_IMA: /* (0x23 - 0x3) * 2 */
return 64;
case coding_APPLE_IMA4:
return 64;
@ -654,6 +655,8 @@ int decode_get_frame_size(VGMSTREAM* vgmstream) {
case coding_WWISE_IMA:
case coding_CD_IMA:
return 0x24;
case coding_CRANKCASE_IMA:
return 0x23;
case coding_XBOX_IMA_mch:
case coding_FSB_IMA:
return 0x24 * vgmstream->channels;
@ -1264,6 +1267,12 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_
vgmstream->channels, vgmstream->samples_into_block, samples_to_do);
}
break;
case coding_CRANKCASE_IMA:
for (ch = 0; ch < vgmstream->channels; ch++) {
decode_crankcase_ima(&vgmstream->ch[ch], buffer+ch,
vgmstream->channels, vgmstream->samples_into_block, samples_to_do);
}
break;
case coding_WS:
for (ch = 0; ch < vgmstream->channels; ch++) {

View file

@ -450,5 +450,8 @@ int get_vgmstream_average_bitrate(VGMSTREAM* vgmstream) {
bitrate_info_t br = {0};
br.count_max = BITRATE_FILES_MAX;
if (vgmstream->coding_type == coding_SILENCE)
return 0;
return get_vgmstream_file_bitrate_main(vgmstream, &br, NULL);
}

View file

@ -33,7 +33,7 @@ atrac9_codec_data* init_atrac9(atrac9_config* cfg) {
data->handle = Atrac9GetHandle();
if (!data->handle) goto fail;
put_32bitBE(config_data, cfg->config_data);
put_u32be(config_data, cfg->config_data);
status = Atrac9InitDecoder(data->handle, config_data);
if (status < 0) goto fail;
@ -150,7 +150,7 @@ void reset_atrac9(atrac9_codec_data* data) {
data->handle = Atrac9GetHandle();
if (!data->handle) goto fail;
put_32bitBE(config_data, data->config.config_data);
put_u32be(config_data, data->config.config_data);
status = Atrac9InitDecoder(data->handle, config_data);
if (status < 0) goto fail;
}

View file

@ -330,7 +330,7 @@ circus_handle_t* circus_init(off_t start, uint8_t codec, uint8_t flags) {
handle->flags = flags; //(config >> 8) & 0xFF;
scale_index = (handle->flags & 0xF);
if (scale_index > 5) goto fail;
if (scale_index >= 5) goto fail;
handle->scales = scale_table[scale_index];
if (handle->codec == XPCM_CODEC_VQ_DEFLATE) {

View file

@ -45,6 +45,7 @@ void decode_ubi_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspaci
void decode_ubi_sce_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
void decode_h4m_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, uint16_t frame_format);
void decode_cd_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_crankcase_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
size_t ima_bytes_to_samples(size_t bytes, int channels);
size_t ms_ima_bytes_to_samples(size_t bytes, int block_align, int channels);
size_t xbox_ima_bytes_to_samples(size_t bytes, int channels);
@ -305,8 +306,9 @@ STREAMFILE* compresswave_get_streamfile(compresswave_codec_data* data);
/* ea_mt_decoder*/
typedef struct ea_mt_codec_data ea_mt_codec_data;
ea_mt_codec_data* init_ea_mt(int channels, int type);
ea_mt_codec_data* init_ea_mt(int channels, int pcm_blocks);
ea_mt_codec_data* init_ea_mt_loops(int channels, int pcm_blocks, int loop_sample, off_t* loop_offsets);
ea_mt_codec_data* init_ea_mt_cbx(int channels);
void decode_ea_mt(VGMSTREAM* vgmstream, sample * outbuf, int channelspacing, int32_t samples_to_do, int channel);
void reset_ea_mt(VGMSTREAM* vgmstream);
void flush_ea_mt(VGMSTREAM* vgmstream);

View file

@ -1,17 +1,8 @@
#include "coding.h"
#include "libs/utkdec.h"
#include "ea_mt_decoder_utk.h"
/* Decodes EA MicroTalk */
/* Decodes EA MicroTalk (speech codec) using utkencode lib (slightly modified for vgmstream).
* EA separates MT10:1 and MT5:1 (bigger frames), but apparently are the same
* with different encoding parameters. Later revisions may have PCM blocks (rare).
*
* Decoder by Andrew D'Addesio: https://github.com/daddesio/utkencode
* Info: http://wiki.niotso.org/UTK
*/
//#define UTK_MAKE_U32(a,b,c,d) ((a)|((b)<<8)|((c)<<16)|((d)<<24))
#define UTK_ROUND(x) ((x) >= 0.0f ? ((x)+0.5f) : ((x)-0.5f))
#define UTK_MIN(x,y) ((x)<(y)?(x):(y))
#define UTK_MAX(x,y) ((x)>(y)?(x):(y))
@ -26,21 +17,30 @@ struct ea_mt_codec_data {
off_t loop_offset;
int loop_sample;
int pcm_blocks;
int samples_filled;
int samples_used;
int samples_done;
int samples_discard;
void* utk_context;
void* ctx;
};
static size_t ea_mt_read_callback(void *dest, int size, void *arg);
static ea_mt_codec_data* init_ea_mt_internal(utk_type_t type, int channels, int loop_sample, off_t* loop_offsets);
ea_mt_codec_data* init_ea_mt(int channels, int pcm_blocks) {
return init_ea_mt_loops(channels, pcm_blocks, 0, NULL);
}
ea_mt_codec_data* init_ea_mt_loops(int channels, int pcm_blocks, int loop_sample, off_t *loop_offsets) {
return init_ea_mt_internal(pcm_blocks ? UTK_EA_PCM : UTK_EA, channels, loop_sample, loop_offsets);
}
ea_mt_codec_data* init_ea_mt_cbx(int channels) {
return init_ea_mt_internal(UTK_CBX, channels, 0, NULL);
}
static ea_mt_codec_data* init_ea_mt_internal(utk_type_t type, int channels, int loop_sample, off_t* loop_offsets) {
ea_mt_codec_data* data = NULL;
int i;
@ -48,16 +48,14 @@ ea_mt_codec_data* init_ea_mt_loops(int channels, int pcm_blocks, int loop_sample
if (!data) goto fail;
for (i = 0; i < channels; i++) {
data[i].utk_context = calloc(1, sizeof(UTKContext));
if (!data[i].utk_context) goto fail;
utk_init(data[i].utk_context);
data[i].ctx = utk_init(type);
if (!data[i].ctx) goto fail;
data[i].pcm_blocks = pcm_blocks;
data[i].loop_sample = loop_sample;
if (loop_offsets)
data[i].loop_offset = loop_offsets[i];
utk_set_callback(data[i].utk_context, data[i].buffer, UTK_BUFFER_SIZE, &data[i], &ea_mt_read_callback);
utk_set_callback(data[i].ctx, data[i].buffer, UTK_BUFFER_SIZE, &data[i], &ea_mt_read_callback);
}
return data;
@ -71,10 +69,9 @@ void decode_ea_mt(VGMSTREAM* vgmstream, sample_t* outbuf, int channelspacing, in
int i;
ea_mt_codec_data* data = vgmstream->codec_data;
ea_mt_codec_data* ch_data = &data[channel];
UTKContext* ctx = ch_data->utk_context;
int samples_done = 0;
float* fbuf = utk_get_samples(ch_data->ctx);
while (samples_done < samples_to_do) {
if (ch_data->samples_filled) {
@ -98,7 +95,7 @@ void decode_ea_mt(VGMSTREAM* vgmstream, sample_t* outbuf, int channelspacing, in
samples_to_get = samples_to_do - samples_done;
for (i = ch_data->samples_used; i < ch_data->samples_used + samples_to_get; i++) {
int pcm = UTK_ROUND(ctx->decompressed_frame[i]);
int pcm = UTK_ROUND(fbuf[i]);
outbuf[0] = (int16_t)UTK_CLAMP(pcm, -32768, 32767);
outbuf += channelspacing;
}
@ -119,19 +116,20 @@ void decode_ea_mt(VGMSTREAM* vgmstream, sample_t* outbuf, int channelspacing, in
/* offset is usually at loop_offset here, but not always (ex. loop_sample < 432) */
ch_data->offset = ch_data->loop_offset;
utk_set_ptr(ctx, 0, 0); /* reset the buffer reader */
utk_reset(ctx); /* decoder init (all fields must be reset, for some edge cases) */
utk_set_buffer(ch_data->ctx, 0, 0); /* reset the buffer reader */
utk_reset(ch_data->ctx); /* decoder init (all fields must be reset, for some edge cases) */
}
}
else {
/* new frame */
if (ch_data->pcm_blocks)
utk_rev3_decode_frame(ctx);
else
utk_decode_frame(ctx);
int samples = utk_decode_frame(ch_data->ctx);
if (samples < 0) {
VGM_LOG("wrong decode: %i\n", samples);
samples = 432;
}
ch_data->samples_used = 0;
ch_data->samples_filled = 432;
ch_data->samples_filled = samples;
}
}
}
@ -143,23 +141,20 @@ static void flush_ea_mt_offsets(VGMSTREAM* vgmstream, int is_start, int samples_
if (!data) return;
/* EA-MT frames are VBR (not byte-aligned?), so utk_decoder reads new buffer data automatically.
/* EA-MT frames are VBR and not byte-aligned, so utk_decoder reads new buffer data automatically.
* When decoding starts or a SCHl block changes, flush_ea_mt must be called to reset the state.
* A bit hacky but would need some restructuring otherwise. */
for (i = 0; i < vgmstream->channels; i++) {
UTKContext* ctx = data[i].utk_context;
data[i].streamfile = vgmstream->ch[i].streamfile; /* maybe should keep its own STREAMFILE? */
data[i].streamfile = vgmstream->ch[i].streamfile;
if (is_start)
data[i].offset = vgmstream->ch[i].channel_start_offset;
else
data[i].offset = vgmstream->ch[i].offset;
utk_set_ptr(ctx, 0, 0); /* reset the buffer reader */
utk_set_buffer(data[i].ctx, 0, 0); /* reset the buffer reader */
if (is_start) {
utk_reset(ctx);
ctx->parsed_header = 0;
utk_reset(data[i].ctx);
data[i].samples_done = 0;
}
@ -187,7 +182,7 @@ void free_ea_mt(ea_mt_codec_data* data, int channels) {
return;
for (i = 0; i < channels; i++) {
free(data[i].utk_context);
utk_free(data[i].ctx);
}
free(data);
}

View file

@ -1,469 +0,0 @@
#ifndef _EA_MT_DECODER_UTK_H_
#define _EA_MT_DECODER_UTK_H_
#include <stdint.h>
#include <string.h>
/* Note: This struct assumes a member alignment of 4 bytes.
** This matters when pitch_lag > 216 on the first subframe of any given frame. */
typedef struct UTKContext {
uint8_t *buffer;
size_t buffer_size;
void *arg;
size_t (*read_callback)(void *dest, int size, void *arg);
const uint8_t *ptr, *end;
int parsed_header;
unsigned int bits_value;
int bits_count;
int reduced_bw;
int multipulse_thresh;
float fixed_gains[64];
float rc[12];
float synth_history[12];
float adapt_cb[324];
float decompressed_frame[432];
} UTKContext;
enum {
MDL_NORMAL = 0,
MDL_LARGEPULSE = 1
};
static const float utk_rc_table[64] = {
+0.0f,
-.99677598476409912109375f, -.99032700061798095703125f, -.983879029750823974609375f, -.977430999279022216796875f,
-.970982015132904052734375f, -.964533984661102294921875f, -.958085000514984130859375f, -.9516370296478271484375f,
-.930754005908966064453125f, -.904959976673126220703125f, -.879167020320892333984375f, -.853372991085052490234375f,
-.827579021453857421875f, -.801786005496978759765625f, -.775991976261138916015625f, -.75019800662994384765625f,
-.724404990673065185546875f, -.6986110210418701171875f, -.6706349849700927734375f, -.61904799938201904296875f,
-.567460000514984130859375f, -.515873014926910400390625f, -.4642859995365142822265625f, -.4126980006694793701171875f,
-.361110985279083251953125f, -.309523999691009521484375f, -.257937014102935791015625f, -.20634900033473968505859375f,
-.1547619998455047607421875f, -.10317499935626983642578125f, -.05158700048923492431640625f,
+0.0f,
+.05158700048923492431640625f, +.10317499935626983642578125f, +.1547619998455047607421875f, +.20634900033473968505859375f,
+.257937014102935791015625f, +.309523999691009521484375f, +.361110985279083251953125f, +.4126980006694793701171875f,
+.4642859995365142822265625f, +.515873014926910400390625f, +.567460000514984130859375f, +.61904799938201904296875f,
+.6706349849700927734375f, +.6986110210418701171875f, +.724404990673065185546875f, +.75019800662994384765625f,
+.775991976261138916015625f, +.801786005496978759765625f, +.827579021453857421875f, +.853372991085052490234375f,
+.879167020320892333984375f, +.904959976673126220703125f, +.930754005908966064453125f, +.9516370296478271484375f,
+.958085000514984130859375f, +.964533984661102294921875f, +.970982015132904052734375f, +.977430999279022216796875f,
+.983879029750823974609375f, +.99032700061798095703125f, +.99677598476409912109375f
};
static const uint8_t utk_codebooks[2][256] = {
{ /* normal model */
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 21,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 25,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 22,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 0,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 21,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 26,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 22,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 2
}, { /* large-pulse model */
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 27,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 1,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 28,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 3,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 27,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 1,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 28,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 3
}
};
static const struct {
int next_model;
int code_size;
float pulse_value;
} utk_commands[29] = {
{MDL_LARGEPULSE, 8, 0.0f},
{MDL_LARGEPULSE, 7, 0.0f},
{MDL_NORMAL, 8, 0.0f},
{MDL_NORMAL, 7, 0.0f},
{MDL_NORMAL, 2, 0.0f},
{MDL_NORMAL, 2, -1.0f},
{MDL_NORMAL, 2, +1.0f},
{MDL_NORMAL, 3, -1.0f},
{MDL_NORMAL, 3, +1.0f},
{MDL_LARGEPULSE, 4, -2.0f},
{MDL_LARGEPULSE, 4, +2.0f},
{MDL_LARGEPULSE, 3, -2.0f},
{MDL_LARGEPULSE, 3, +2.0f},
{MDL_LARGEPULSE, 5, -3.0f},
{MDL_LARGEPULSE, 5, +3.0f},
{MDL_LARGEPULSE, 4, -3.0f},
{MDL_LARGEPULSE, 4, +3.0f},
{MDL_LARGEPULSE, 6, -4.0f},
{MDL_LARGEPULSE, 6, +4.0f},
{MDL_LARGEPULSE, 5, -4.0f},
{MDL_LARGEPULSE, 5, +4.0f},
{MDL_LARGEPULSE, 7, -5.0f},
{MDL_LARGEPULSE, 7, +5.0f},
{MDL_LARGEPULSE, 6, -5.0f},
{MDL_LARGEPULSE, 6, +5.0f},
{MDL_LARGEPULSE, 8, -6.0f},
{MDL_LARGEPULSE, 8, +6.0f},
{MDL_LARGEPULSE, 7, -6.0f},
{MDL_LARGEPULSE, 7, +6.0f}
};
static int utk_read_byte(UTKContext *ctx)
{
if (ctx->ptr < ctx->end)
return *ctx->ptr++;
if (ctx->read_callback) {
size_t bytes_copied = ctx->read_callback(ctx->buffer, ctx->buffer_size, ctx->arg);
if (bytes_copied > 0 && bytes_copied <= ctx->buffer_size) {
ctx->ptr = ctx->buffer;
ctx->end = ctx->buffer + bytes_copied;
return *ctx->ptr++;
}
}
return 0;
}
static int16_t utk_read_i16(UTKContext *ctx)
{
int x = utk_read_byte(ctx);
x = (x << 8) | utk_read_byte(ctx);
return x;
}
static int utk_read_bits(UTKContext *ctx, int count)
{
int ret = ctx->bits_value & ((1 << count) - 1);
ctx->bits_value >>= count;
ctx->bits_count -= count;
if (ctx->bits_count < 8) {
/* read another byte */
ctx->bits_value |= utk_read_byte(ctx) << ctx->bits_count;
ctx->bits_count += 8;
}
return ret;
}
static void utk_parse_header(UTKContext *ctx)
{
int i;
float multiplier;
ctx->reduced_bw = utk_read_bits(ctx, 1);
ctx->multipulse_thresh = 32 - utk_read_bits(ctx, 4);
ctx->fixed_gains[0] = 8.0f * (1 + utk_read_bits(ctx, 4));
multiplier = 1.04f + utk_read_bits(ctx, 6)*0.001f;
for (i = 1; i < 64; i++)
ctx->fixed_gains[i] = ctx->fixed_gains[i-1] * multiplier;
}
static void utk_decode_excitation(UTKContext *ctx, int use_multipulse, float *out, int stride)
{
int i;
if (use_multipulse) {
/* multi-pulse model: n pulses are coded explicitly; the rest are zero */
int model, cmd;
model = 0;
i = 0;
while (i < 108) {
cmd = utk_codebooks[model][ctx->bits_value & 0xff];
model = utk_commands[cmd].next_model;
utk_read_bits(ctx, utk_commands[cmd].code_size);
if (cmd > 3) {
/* insert a pulse with magnitude <= 6.0f */
out[i] = utk_commands[cmd].pulse_value;
i += stride;
} else if (cmd > 1) {
/* insert between 7 and 70 zeros */
int count = 7 + utk_read_bits(ctx, 6);
if (i + count * stride > 108)
count = (108 - i)/stride;
while (count > 0) {
out[i] = 0.0f;
i += stride;
count--;
}
} else {
/* insert a pulse with magnitude >= 7.0f */
int x = 7;
while (utk_read_bits(ctx, 1))
x++;
if (!utk_read_bits(ctx, 1))
x *= -1;
out[i] = (float)x;
i += stride;
}
}
} else {
/* RELP model: entire residual (excitation) signal is coded explicitly */
i = 0;
while (i < 108) {
if (!utk_read_bits(ctx, 1))
out[i] = 0.0f;
else if (!utk_read_bits(ctx, 1))
out[i] = -2.0f;
else
out[i] = 2.0f;
i += stride;
}
}
}
static void rc_to_lpc(const float *rc, float *lpc)
{
int i, j;
float tmp1[12];
float tmp2[12];
for (i = 10; i >= 0; i--)
tmp2[1+i] = rc[i];
tmp2[0] = 1.0f;
for (i = 0; i < 12; i++) {
float x = -tmp2[11] * rc[11];
for (j = 10; j >= 0; j--) {
x -= tmp2[j] * rc[j];
tmp2[j+1] = x * rc[j] + tmp2[j];
}
tmp1[i] = tmp2[0] = x;
for (j = 0; j < i; j++)
x -= tmp1[i-1-j] * lpc[j];
lpc[i] = x;
}
}
static void utk_lp_synthesis_filter(UTKContext *ctx, int offset, int num_blocks)
{
int i, j, k;
float lpc[12];
float *ptr = &ctx->decompressed_frame[offset];
rc_to_lpc(ctx->rc, lpc);
for (i = 0; i < num_blocks; i++) {
for (j = 0; j < 12; j++) {
float x = *ptr;
for (k = 0; k < j; k++)
x += lpc[k] * ctx->synth_history[k-j+12];
for (; k < 12; k++)
x += lpc[k] * ctx->synth_history[k-j];
ctx->synth_history[11-j] = x;
*ptr++ = x;
}
}
}
/*
** Public functions.
*/
static int utk_decode_frame(UTKContext *ctx)
{
int i, j;
int use_multipulse = 0;
float excitation[5+108+5];
float rc_delta[12];
if (!ctx->bits_count) {
ctx->bits_value = utk_read_byte(ctx);
ctx->bits_count = 8;
}
if (!ctx->parsed_header) {
utk_parse_header(ctx);
ctx->parsed_header = 1;
}
memset(&excitation[0], 0, 5*sizeof(float));
memset(&excitation[5+108], 0, 5*sizeof(float));
/* read the reflection coefficients */
for (i = 0; i < 12; i++) {
int idx;
if (i == 0) {
idx = utk_read_bits(ctx, 6);
if (idx < ctx->multipulse_thresh)
use_multipulse = 1;
} else if (i < 4) {
idx = utk_read_bits(ctx, 6);
} else {
idx = 16 + utk_read_bits(ctx, 5);
}
rc_delta[i] = (utk_rc_table[idx] - ctx->rc[i])*0.25f;
}
/* decode four subframes */
for (i = 0; i < 4; i++) {
int pitch_lag = utk_read_bits(ctx, 8);
float pitch_gain = (float)utk_read_bits(ctx, 4)/15.0f;
float fixed_gain = ctx->fixed_gains[utk_read_bits(ctx, 6)];
if (!ctx->reduced_bw) {
utk_decode_excitation(ctx, use_multipulse, &excitation[5], 1);
} else {
/* residual (excitation) signal is encoded at reduced bandwidth */
int align = utk_read_bits(ctx, 1);
int zero = utk_read_bits(ctx, 1);
utk_decode_excitation(ctx, use_multipulse, &excitation[5+align], 2);
if (zero) {
/* fill the remaining samples with zero
** (spectrum is duplicated into high frequencies) */
for (j = 0; j < 54; j++)
excitation[5+(1-align)+2*j] = 0.0f;
} else {
/* interpolate the remaining samples
** (spectrum is low-pass filtered) */
float *ptr = &excitation[5+(1-align)];
for (j = 0; j < 108; j += 2)
ptr[j] = ptr[j-5] * 0.01803267933428287506103515625f
- ptr[j-3] * 0.114591561257839202880859375f
+ ptr[j-1] * 0.597385942935943603515625f
+ ptr[j+1] * 0.597385942935943603515625f
- ptr[j+3] * 0.114591561257839202880859375f
+ ptr[j+5] * 0.01803267933428287506103515625f;
/* scale by 0.5f to give the sinc impulse response unit energy */
fixed_gain *= 0.5f;
}
}
for (j = 0; j < 108; j++)
ctx->decompressed_frame[108*i+j] = fixed_gain * excitation[5+j]
+ pitch_gain * ctx->adapt_cb[108*i+216-pitch_lag+j];
}
for (i = 0; i < 324; i++)
ctx->adapt_cb[i] = ctx->decompressed_frame[108+i];
for (i = 0; i < 4; i++) {
for (j = 0; j < 12; j++)
ctx->rc[j] += rc_delta[j];
utk_lp_synthesis_filter(ctx, 12*i, i < 3 ? 1 : 33);
}
return 0;
}
static void utk_init(UTKContext *ctx)
{
memset(ctx, 0, sizeof(*ctx));
}
static void utk_reset(UTKContext *ctx)
{
/* resets the internal state, leaving the external config/buffers
* untouched (could be reset externally or using utk_set_x) */
ctx->parsed_header = 0;
ctx->bits_value = 0;
ctx->bits_count = 0;
ctx->reduced_bw = 0;
ctx->multipulse_thresh = 0;
memset(ctx->fixed_gains, 0, sizeof(ctx->fixed_gains));
memset(ctx->rc, 0, sizeof(ctx->rc));
memset(ctx->synth_history, 0, sizeof(ctx->synth_history));
memset(ctx->adapt_cb, 0, sizeof(ctx->adapt_cb));
memset(ctx->decompressed_frame, 0, sizeof(ctx->decompressed_frame));
}
static void utk_set_callback(UTKContext *ctx, uint8_t *buffer, size_t buffer_size, void *arg, size_t (*read_callback)(void *, int , void *))
{
/* prepares for external reading */
ctx->buffer = buffer;
ctx->buffer_size = buffer_size;
ctx->arg = arg;
ctx->read_callback = read_callback;
/* reset the bit reader */
ctx->bits_count = 0;
}
static void utk_set_ptr(UTKContext *ctx, const uint8_t *ptr, const uint8_t *end)
{
/* sets the pointer to an external data buffer (can also be used to
* reset the buffered data if set to ptr/end 0) */
ctx->ptr = ptr;
ctx->end = end;
/* reset the bit reader */
ctx->bits_count = 0;
}
/*
** MicroTalk Revision 3 decoding function.
*/
static int utk_rev3_decode_frame(UTKContext *ctx)
{
int pcm_data_present = (utk_read_byte(ctx) == 0xee);
int i;
utk_decode_frame(ctx);
/* unread the last 8 bits and reset the bit reader */
ctx->ptr--;
ctx->bits_count = 0;
if (pcm_data_present) {
/* Overwrite n samples at a given offset in the decoded frame with
** raw PCM data. */
int offset = utk_read_i16(ctx);
int count = utk_read_i16(ctx);
/* sx.exe does not do any bounds checking or clamping of these two
** fields (see 004274D1 in sx.exe v3.01.01), which means a specially
** crafted MT5:1 file can crash sx.exe.
** We will throw an error instead. */
if (offset < 0 || offset > 432) {
return -1; /* invalid PCM offset */
}
if (count < 0 || count > 432 - offset) {
return -2; /* invalid PCM count */
}
for (i = 0; i < count; i++)
ctx->decompressed_frame[offset+i] = (float)utk_read_i16(ctx);
}
return 0;
}
#endif /* _EA_MT_DECODER_UTK_H_ */

View file

@ -33,7 +33,7 @@ static const float xa_coefs[16][2] = {
void decode_ea_xas_v1(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
uint8_t frame[0x4c] = {0};
off_t frame_offset;
int group, row, i, samples_done = 0, sample_count = 0;
int samples_done = 0, sample_count = 0;
size_t bytes_per_frame, samples_per_frame;
@ -55,11 +55,11 @@ void decode_ea_xas_v1(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspa
/* parse group headers */
for (group = 0; group < 4; group++) {
for (int group = 0; group < 4; group++) {
float coef1, coef2;
int16_t hist1, hist2;
uint8_t shift;
uint32_t group_header = (uint32_t)get_32bitLE(frame + group*0x4); /* always LE */
uint32_t group_header = get_u32le(frame + group*0x4); /* always LE */
coef1 = xa_coefs[group_header & 0x0F][0];
coef2 = xa_coefs[group_header & 0x0F][1];
@ -80,8 +80,8 @@ void decode_ea_xas_v1(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspa
sample_count++;
/* process nibbles per group */
for (row = 0; row < 15; row++) {
for (i = 0; i < 1*2; i++) {
for (int row = 0; row < 15; row++) {
for (int i = 0; i < 1 * 2; i++) {
uint8_t nibbles = frame[4*4 + row*0x04 + group + i/2];
int sample;
@ -116,7 +116,7 @@ void decode_ea_xas_v1(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspa
void decode_ea_xas_v0(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
uint8_t frame[0x13] = {0};
off_t frame_offset;
int i, frames_in, samples_done = 0, sample_count = 0;
int frames_in, samples_done = 0, sample_count = 0;
size_t bytes_per_frame, samples_per_frame;
@ -129,14 +129,14 @@ void decode_ea_xas_v0(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspa
frame_offset = stream->offset + bytes_per_frame * frames_in;
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
//todo see above
//TODO make expand function and fuse with above
/* process frame */
{
float coef1, coef2;
int16_t hist1, hist2;
uint8_t shift;
uint32_t frame_header = (uint32_t)get_32bitLE(frame); /* always LE */
uint32_t frame_header = get_u32le(frame); /* always LE */
coef1 = xa_coefs[frame_header & 0x0F][0];
coef2 = xa_coefs[frame_header & 0x0F][1];
@ -157,7 +157,7 @@ void decode_ea_xas_v0(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspa
sample_count++;
/* process nibbles */
for (i = 0; i < 0x0f*2; i++) {
for (int i = 0; i < 0x0f * 2; i++) {
uint8_t nibbles = frame[0x02 + 0x02 + i/2];
int sample;

View file

@ -35,8 +35,8 @@ static const int IMA_IndexTable[16] = {
/* Original IMA expansion, using shift+ADDs to avoid MULs (slow back then) */
static void std_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, int nibble_shift, int32_t * hist1, int32_t * step_index) {
int sample_nibble, sample_decoded, step, delta;
static void std_ima_expand_nibble_data(uint8_t byte, int shift, int32_t* hist1, int32_t* index) {
int code, sample, step, delta;
/* simplified through math from:
* - diff = (code + 1/2) * (step / 4)
@ -44,21 +44,26 @@ static void std_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset,
* > diff = (step * nibble / 4) + (step / 8)
* final diff = [signed] (step / 8) + (step / 4) + (step / 2) + (step) [when code = 4+2+1] */
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf; /* ADPCM code */
sample_decoded = *hist1; /* predictor value */
step = ADPCMTable[*step_index]; /* current step */
code = (byte >> shift) & 0xf;
sample = *hist1; /* predictor value */
step = ADPCMTable[*index]; /* current step */
delta = step >> 3;
if (sample_nibble & 1) delta += step >> 2;
if (sample_nibble & 2) delta += step >> 1;
if (sample_nibble & 4) delta += step;
if (sample_nibble & 8) delta = -delta;
sample_decoded += delta;
if (code & 1) delta += step >> 2;
if (code & 2) delta += step >> 1;
if (code & 4) delta += step;
if (code & 8) delta = -delta;
sample += delta;
*hist1 = clamp16(sample_decoded);
*step_index += IMA_IndexTable[sample_nibble];
if (*step_index < 0) *step_index=0;
if (*step_index > 88) *step_index=88;
*hist1 = clamp16(sample);
*index += IMA_IndexTable[code];
if (*index < 0) *index = 0;
if (*index > 88) *index = 88;
}
static void std_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, int nibble_shift, int32_t * hist1, int32_t * step_index) {
uint8_t byte = read_u8(byte_offset,stream->streamfile);
std_ima_expand_nibble_data(byte, nibble_shift, hist1, step_index);
}
/* Apple's IMA variation. Exactly the same except it uses 16b history (probably more sensitive to overflow/sign extend?) */
@ -1287,6 +1292,43 @@ void decode_cd_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacin
stream->adpcm_step_index = step_index;
}
/* Crankcase Audio IMA, from libs (internally CrankcaseAudio::ADPCM and revadpcm) */
void decode_crankcase_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
uint8_t frame[0x23] = {0};
int i, frames_in, sample_pos = 0, block_samples, frame_size;
int32_t hist1 = stream->adpcm_history1_32;
int step_index = stream->adpcm_step_index;
uint32_t frame_offset;
/* external interleave (fixed size), mono */
frame_size = 0x23;
block_samples = (frame_size - 0x3) * 2;
frames_in = first_sample / block_samples;
first_sample = first_sample % block_samples;
frame_offset = stream->offset + frame_size * frames_in;
read_streamfile(frame, frame_offset, frame_size, stream->streamfile); /* ignore EOF errors */
/* normal header (hist+step), mono */
if (first_sample == 0) {
hist1 = get_s16be(frame + 0x00);
step_index = get_u8(frame + 0x02); /* no reserved value at 0x03 unlike other IMAs (misaligned reads?) */
step_index = _clamp_s32(step_index, 0, 88);
}
/* decode nibbles (layout: straight in mono) */
for (i = first_sample; i < first_sample + samples_to_do; i++) {
int pos = 0x03 + (i/2);
int shift = (i & 1 ? 4:0); /* low first */
std_ima_expand_nibble_data(frame[pos], shift, &hist1, &step_index);
outbuf[sample_pos] = (short)(hist1); /* internally output to float using "sample / 32767.0" */
sample_pos += channelspacing;
}
stream->adpcm_history1_32 = hist1;
stream->adpcm_step_index = step_index;
}
/* ************************************************************* */

View file

@ -0,0 +1,607 @@
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "utkdec.h"
struct utk_context_t {
/* config */
utk_type_t type;
int parsed_header;
/* state */
struct bitreader_t {
const uint8_t* ptr;
uint32_t bits_value;
int bits_count;
/* extra (OG MT/CBX just loads ptr memory externally) */
const uint8_t* end;
void* arg;
uint8_t* buffer;
size_t buffer_size;
size_t (*read_callback)(void* dst, int size, void* arg);
} br;
bool reduced_bandwidth;
int multipulse_threshold;
float fixed_gains[64];
float rc_data[12];
float synth_history[12];
float subframes[324 + 432];
/* adapt_cb indexes may read from samples, join both + ptr to avoid
* struct aligment issues (typically doesn't matter but for completeness) */
float* adapt_cb; /* subframes + 0 */
float* samples; /* subframes + 324 */
};
/* bit mask; (1 << count) - 1 is probably faster now but OG code uses a table */
static const uint8_t mask_table[8] = {
0x01,0x03,0x07,0x0F,0x1F,0x3F,0x7F,0xFF
};
/* reflection coefficients, rounded that correspond to hex values in exes (actual float is longer)
* note this table is mirrored: for (i = 1 .. 32) t[64 - i] = -t[i]) */
static const float utk_rc_table[64] = {
/* 6b index start */
+0.000000f, -0.996776f, -0.990327f, -0.983879f,
-0.977431f, -0.970982f, -0.964534f, -0.958085f,
-0.951637f, -0.930754f, -0.904960f, -0.879167f,
-0.853373f, -0.827579f, -0.801786f, -0.775992f,
/* 5b index start */
-0.750198f, -0.724405f, -0.698611f, -0.670635f,
-0.619048f, -0.567460f, -0.515873f, -0.464286f,
-0.412698f, -0.361111f, -0.309524f, -0.257937f,
-0.206349f, -0.154762f, -0.103175f, -0.051587f,
+0.000000f, +0.051587f, +0.103175f, +0.154762f,
+0.206349f, +0.257937f, +0.309524f, +0.361111f,
+0.412698f, +0.464286f, +0.515873f, +0.567460f,
+0.619048f, +0.670635f, +0.698611f, +0.724405f,
+0.750198f, +0.775992f, +0.801786f, +0.827579f,
+0.853373f, +0.879167f, +0.904960f, +0.930754f,
+0.951637f, +0.958085f, +0.964534f, +0.970982f,
+0.977431f, +0.983879f, +0.990327f, +0.996776f,
};
static const uint8_t utk_codebooks[2][256] = {
/* normal model */
{
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 21,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 25,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 22,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 0,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 21,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 26,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 22,
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 2
},
/* large-pulse model */
{
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 27,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 1,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 28,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 3,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 27,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 1,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 28,
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 3
},
};
enum {
MDL_NORMAL = 0,
MDL_LARGEPULSE = 1
};
static const struct {
int next_model;
int code_size;
float pulse_value;
} utk_commands[29] = {
{MDL_LARGEPULSE, 8, 0.0f},
{MDL_LARGEPULSE, 7, 0.0f},
{MDL_NORMAL, 8, 0.0f},
{MDL_NORMAL, 7, 0.0f},
{MDL_NORMAL, 2, 0.0f},
{MDL_NORMAL, 2, -1.0f},
{MDL_NORMAL, 2, +1.0f},
{MDL_NORMAL, 3, -1.0f},
{MDL_NORMAL, 3, +1.0f},
{MDL_LARGEPULSE, 4, -2.0f},
{MDL_LARGEPULSE, 4, +2.0f},
{MDL_LARGEPULSE, 3, -2.0f},
{MDL_LARGEPULSE, 3, +2.0f},
{MDL_LARGEPULSE, 5, -3.0f},
{MDL_LARGEPULSE, 5, +3.0f},
{MDL_LARGEPULSE, 4, -3.0f},
{MDL_LARGEPULSE, 4, +3.0f},
{MDL_LARGEPULSE, 6, -4.0f},
{MDL_LARGEPULSE, 6, +4.0f},
{MDL_LARGEPULSE, 5, -4.0f},
{MDL_LARGEPULSE, 5, +4.0f},
{MDL_LARGEPULSE, 7, -5.0f},
{MDL_LARGEPULSE, 7, +5.0f},
{MDL_LARGEPULSE, 6, -5.0f},
{MDL_LARGEPULSE, 6, +5.0f},
{MDL_LARGEPULSE, 8, -6.0f},
{MDL_LARGEPULSE, 8, +6.0f},
{MDL_LARGEPULSE, 7, -6.0f},
{MDL_LARGEPULSE, 7, +6.0f}
};
/* In Lego Batman 2 gain[0] = 1.068 while other games (Lego Marvel, Lego SW) is 64.0f.
* The latter makes more sense and the former is audibly worse so it was probably a bug. */
static const float cbx_fixed_gains[64] = {
64.0f, 68.351997f, 72.999931f, 77.963921f,
83.265465f, 88.927513f, 94.974579f, 101.43285f,
108.33028f, 115.69673f, 123.5641f, 131.96646f,
140.94017f, 150.52409f, 160.75972f, 171.69138f,
183.36638f, 195.83528f, 209.15207f, 223.3744f,
238.56386f, 254.78619f, 272.11163f, 290.6152f,
310.37701f, 331.48264f, 354.02344f, 378.09702f,
403.80759f, 431.26648f, 460.59259f, 491.91287f,
525.36292f, 561.08759f, 599.24152f, 639.98993f,
683.50922f, 729.98779f, 779.62695f, 832.64154f,
889.26111f, 949.73083f, 1014.3125f, 1083.2858f,
1156.9491f, 1235.6216f, 1319.6438f, 1409.3795f,
1505.2173f, 1607.572f, 1716.8868f, 1833.6351f,
1958.3223f, 2091.488f, 2233.7092f, 2385.6013f,
2547.822f, 2721.0737f, 2906.1067f, 3103.7219f,
3314.7749f, 3540.1794f, 3780.9116f, 4038.0134f,
};
/* Bitreader in OG code can only read from set ptr; doesn't seem to check bounds though.
* Incidentally bitreader functions seem to be used only in MT and not in other EA stuff. */
static uint8_t read_byte(struct bitreader_t* br) {
if (br->ptr < br->end)
return *br->ptr++;
if (br->read_callback) {
size_t bytes_copied = br->read_callback(br->buffer, br->buffer_size, br->arg);
if (bytes_copied > 0 && bytes_copied <= br->buffer_size) {
br->ptr = br->buffer;
br->end = br->buffer + bytes_copied;
return *br->ptr++;
}
}
return 0;
}
static int16_t read_s16(struct bitreader_t* br) {
int x = read_byte(br);
x = (x << 8) | read_byte(br);
return x;
}
static void init_bits(struct bitreader_t* br) {
if (!br->bits_count) {
br->bits_value = read_byte(br);
br->bits_count = 8;
}
}
static uint8_t peek_bits(struct bitreader_t* br, int count) {
uint8_t mask = mask_table[count - 1];
return br->bits_value & mask;
}
/* assumes count <= 8, which is always true since sizes are known and don't depend on the bitstream. */
static uint8_t read_bits(struct bitreader_t* br, int count) {
uint8_t mask = mask_table[count - 1];
uint8_t ret = br->bits_value & mask;
br->bits_value >>= count;
br->bits_count -= count;
if (br->bits_count < 8) {
/* read another byte */
br->bits_value |= read_byte(br) << br->bits_count;
br->bits_count += 8;
}
return ret;
}
/* for clarity, as found in OG code (no return) */
static void consume_bits(struct bitreader_t* br, int count) {
read_bits(br, count);
}
static void parse_header(utk_context_t* ctx) {
if (ctx->type == UTK_CBX) {
/* CBX uses fixed parameters unlike EA-MT, probably encoder defaults for MT10:1
* equivalent to EA-MT with base_thre = 8, base_gain = 7, base_mult = 28 (plus rounding diffs).
* OG CBX code uses values/tables directly rather than config though */
ctx->reduced_bandwidth = true;
ctx->multipulse_threshold = 32 - 8;
ctx->fixed_gains[0] = cbx_fixed_gains[0];
for (int i = 1; i < 64; i++) {
ctx->fixed_gains[i] = cbx_fixed_gains[i];
}
}
else {
ctx->reduced_bandwidth = read_bits(&ctx->br, 1) == 1;
int base_thre = read_bits(&ctx->br, 4);
int base_gain = read_bits(&ctx->br, 4);
int base_mult = read_bits(&ctx->br, 6);
ctx->multipulse_threshold = 32 - base_thre;
ctx->fixed_gains[0] = 8.0f * (1 + base_gain);
float multiplier = 1.04f + base_mult * 0.001f;
for (int i = 1; i < 64; i++) {
ctx->fixed_gains[i] = ctx->fixed_gains[i-1] * multiplier;
}
}
}
static void decode_excitation(utk_context_t* ctx, bool use_multipulse, float* out, int stride) {
int i = 0;
if (use_multipulse) {
/* multi-pulse model: n pulses are coded explicitly; the rest are zero */
int model = 0;
while (i < 108) {
int huffman_code = peek_bits(&ctx->br, 8); /* variable-length, may consume less */
int cmd = utk_codebooks[model][huffman_code];
model = utk_commands[cmd].next_model;
consume_bits(&ctx->br, utk_commands[cmd].code_size);
if (cmd > 3) {
/* insert a pulse with magnitude <= 6.0f */
out[i] = utk_commands[cmd].pulse_value;
i += stride;
}
else if (cmd > 1) {
/* insert between 7 and 70 zeros */
int count = 7 + read_bits(&ctx->br, 6);
if (i + count * stride > 108)
count = (108 - i) / stride;
while (count > 0) {
out[i] = 0.0f;
i += stride;
count--;
}
}
else {
/* insert a pulse with magnitude >= 7.0f */
int x = 7;
while (read_bits(&ctx->br, 1)) {
x++;
}
if (!read_bits(&ctx->br, 1))
x *= -1;
out[i] = (float)x;
i += stride;
}
}
}
else {
/* RELP model: entire residual (excitation) signal is coded explicitly */
while (i < 108) {
int bits = 0;
float val = 0.0f;
/* peek + partial consume code (odd to use 2 codes for 0.0 but seen in multiple exes) */
int huffman_code = peek_bits(&ctx->br, 2); /* variable-length, may consume less */
switch (huffman_code) {
case 0: //code: 0
case 2: //code: 1 (maybe meant to be -0.0?)
val = 0.0f;
bits = 1;
break;
case 1: //code: 01
val = -2.0f;
bits = 2;
break;
case 3: //code: 11
val = 2.0f;
bits = 2;
break;
default:
break;
}
consume_bits(&ctx->br, bits);
out[i] = val;
i += stride;
}
}
}
static void rc_to_lpc(const float* rc_data, float* lpc) {
int j;
float tmp1[12];
float tmp2[12];
for (int i = 10; i >= 0; i--) {
tmp2[i + 1] = rc_data[i];
}
tmp2[0] = 1.0f;
for (int i = 0; i < 12; i++) {
float x = -(rc_data[11] * tmp2[11]);
for (j = 10; j >= 0; j--) {
x -= (rc_data[j] * tmp2[j]);
tmp2[j + 1] = x * rc_data[j] + tmp2[j];
}
tmp2[0] = x;
tmp1[i] = x;
for (j = 0; j < i; j++) {
x -= tmp1[i - 1 - j] * lpc[j];
}
lpc[i] = x;
}
}
static void lp_synthesis_filter(utk_context_t* ctx, int offset, int blocks) {
int i, j, k;
float lpc[12];
float* ptr = &ctx->samples[offset];
rc_to_lpc(ctx->rc_data, lpc);
for (i = 0; i < blocks; i++) {
/* OG: unrolled x12*12 */
for (j = 0; j < 12; j++) {
float x = *ptr;
for (k = 0; k < j; k++) {
x += lpc[k] * ctx->synth_history[k - j + 12];
}
for (; k < 12; k++) {
x += lpc[k] * ctx->synth_history[k - j + 0];
}
ctx->synth_history[11 - j] = x;
*ptr++ = x;
/* CBX only: samples are multiplied by 12582912.0, then coerce_int(sample[i]) on output
* to get final int16, as a pseudo-optimization; not sure if worth replicating */
}
}
}
/* OG sometimes inlines this (sx3, not B&B/CBX) */
static void interpolate_rest(float* excitation) {
for (int i = 0; i < 108; i += 2) {
float tmp1 = (excitation[i - 5] + excitation[i + 5]) * 0.01803268f;
float tmp2 = (excitation[i - 3] + excitation[i + 3]) * 0.11459156f;
float tmp3 = (excitation[i - 1] + excitation[i + 1]) * 0.59738597f;
excitation[i] = tmp1 - tmp2 + tmp3;
}
}
static void decode_frame_main(utk_context_t* ctx) {
bool use_multipulse = false;
float excitation[5 + 108 + 5]; /* extra +5*2 for interpolation */
float rc_delta[12];
/* OG code usually calls this init/parse header after creation rather than on frame decode,
* but use a flag for now since buffer can be set/reset after init */
init_bits(&ctx->br);
if (!ctx->parsed_header) {
parse_header(ctx);
ctx->parsed_header = 1;
}
/* read the reflection coefficients (OG unrolled) */
for (int i = 0; i < 12; i++) {
int idx;
if (i == 0) {
idx = read_bits(&ctx->br, 6);
if (idx < ctx->multipulse_threshold)
use_multipulse = true;
}
else if (i < 4) {
idx = read_bits(&ctx->br, 6);
}
else {
idx = 16 + read_bits(&ctx->br, 5);
}
rc_delta[i] = (utk_rc_table[idx] - ctx->rc_data[i]) * 0.25f;
}
/* decode four subframes */
for (int i = 0; i < 4; i++) {
int pitch_lag = read_bits(&ctx->br, 8);
int pitch_value = read_bits(&ctx->br, 4);
int gain_index = read_bits(&ctx->br, 6);
float pitch_gain = (float)pitch_value / 15.0f; /* may be compiled as: value * 0.6..67 (float or double) */
float fixed_gain = ctx->fixed_gains[gain_index];
if (!ctx->reduced_bandwidth) {
/* full bandwidth (probably MT5:1) */
decode_excitation(ctx, use_multipulse, &excitation[5 + 0], 1);
/* OG: CBX doesn't have this flag and removes the if (so not 100% same code as MT) */
}
else {
/* residual (excitation) signal is encoded at reduced bandwidth */
int align = read_bits(&ctx->br, 1);
int zero_flag = read_bits(&ctx->br, 1);
decode_excitation(ctx, use_multipulse, &excitation[5 + align], 2);
if (zero_flag) {
/* fill the remaining samples with zero (spectrum is duplicated into high frequencies) */
for (int j = 0; j < 54; j++) {
excitation[5 + (1 - align) + 2 * j] = 0.0f;
}
}
else {
/* 0'd first + last samples for interpolation */
memset(&excitation[0], 0, 5 * sizeof(float));
memset(&excitation[5 + 108], 0, 5 * sizeof(float));
/* interpolate the remaining samples (spectrum is low-pass filtered) */
interpolate_rest(&excitation[5 + (1 - align)]);
/* scale by 0.5f to give the sinc impulse response unit energy */
fixed_gain *= 0.5f;
}
}
/* OG: sometimes unrolled */
for (int j = 0; j < 108; j++) {
/* This has potential to read garbage from fixed_gains/samples (-39 ~ +648). The former
* seems avoided by the encoder but we'll clamp it just in case, while the later is common
* and seemingly used on purpose, so it's allowed via joining adapt_cb + samples bufs. */
int idx = 108 * i + 216 - pitch_lag + j;
if (idx < 0) /* OG: not done but shouldn't matter */
idx = 0;
float tmp1 = fixed_gain * excitation[5 + j];
float tmp2 = pitch_gain * ctx->adapt_cb[idx];
ctx->samples[108 * i + j] = tmp1 + tmp2;
}
}
/* OG: may be compiler-optimized to memcpy */
for (int i = 0; i < 324; i++) {
ctx->adapt_cb[i] = ctx->samples[108 + i];
}
/* OG: unrolled x4 */
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 12; j++) {
ctx->rc_data[j] += rc_delta[j];
}
int blocks = i < 3 ? 1 : 33;
lp_synthesis_filter(ctx, 12 * i, blocks);
}
}
static int decode_frame_pcm(utk_context_t* ctx) {
int pcm_data_present = (read_byte(&ctx->br) == 0xEE);
int i;
decode_frame_main(ctx);
/* unread the last 8 bits and reset the bit reader
* (a bit odd but should be safe in all cases, assuming ptr has been set) */
ctx->br.ptr--;
ctx->br.bits_count = 0;
if (pcm_data_present) {
/* Overwrite n samples at a given offset in the decoded frame with raw PCM data. */
int offset = read_s16(&ctx->br);
int count = read_s16(&ctx->br);
/* sx.exe does not do any bounds checking or clamping of these two
* fields (see 004274D1 in sx.exe v3.01.01), which means a specially
* crafted MT5:1 file can crash it. We will throw an error instead. */
if (offset < 0 || offset > 432) {
return -1; /* invalid PCM offset */
}
if (count < 0 || count > 432 - offset) {
return -2; /* invalid PCM count */
}
for (i = 0; i < count; i++) {
ctx->samples[offset+i] = (float)read_s16(&ctx->br);
}
}
return 432;
}
//
int utk_decode_frame(utk_context_t* ctx) {
if (ctx->type == UTK_EA_PCM) {
return decode_frame_pcm(ctx);
}
else {
decode_frame_main(ctx);
return 432;
}
}
utk_context_t* utk_init(utk_type_t type) {
utk_context_t* ctx = calloc(1, sizeof(utk_context_t));
if (!ctx) return NULL;
//memset(ctx, 0, sizeof(*ctx));
ctx->type = type;
ctx->adapt_cb = ctx->subframes + 0;
ctx->samples = ctx->subframes + 324;
return ctx;
}
void utk_free(utk_context_t* ctx) {
free(ctx);
}
void utk_reset(utk_context_t* ctx) {
/* resets the internal state, leaving the external config/buffers
* untouched (could be reset externally or using utk_set_x) */
ctx->parsed_header = 0;
ctx->br.bits_value = 0;
ctx->br.bits_count = 0;
ctx->reduced_bandwidth = 0;
ctx->multipulse_threshold = 0;
memset(ctx->fixed_gains, 0, sizeof(ctx->fixed_gains));
memset(ctx->rc_data, 0, sizeof(ctx->rc_data));
memset(ctx->synth_history, 0, sizeof(ctx->synth_history));
memset(ctx->subframes, 0, sizeof(ctx->subframes));
}
void utk_set_callback(utk_context_t* ctx, uint8_t* buffer, size_t buffer_size, void *arg, size_t (*read_callback)(void *, int , void *)) {
ctx->br.buffer = buffer;
ctx->br.buffer_size = buffer_size;
ctx->br.arg = arg;
ctx->br.read_callback = read_callback;
/* reset the bit reader */
ctx->br.bits_count = 0;
}
void utk_set_buffer(utk_context_t* ctx, const uint8_t* buf, size_t buf_size) {
ctx->br.ptr = buf;
ctx->br.end = buf + buf_size;
/* reset the bit reader */
ctx->br.bits_count = 0;
}
float* utk_get_samples(utk_context_t* ctx) {
return ctx->samples;
}

View file

@ -0,0 +1,48 @@
#ifndef _UTKDEK_H_
#define _UTKDEK_H_
#include <stdint.h>
/* Decodes Electronic Arts' MicroTalk (a multipulse CELP/RELP speech codec) using utkencode lib,
* slightly modified for vgmstream based on decompilation of EA and CBX code.
* Original by Andrew D'Addesio: https://github.com/daddesio/utkencode (UNLICENSE/public domain)
* Info: http://wiki.niotso.org/UTK
*
* EA classifies MT as MT10:1 (smaller frames) and MT5:1 (bigger frames), but both are the same
* with different encoding parameters. Later revisions may have PCM blocks (rare). This codec was
* also reused by Traveller Tales in CBX (same devs?) with minor modifications.
*
* TODO:
* - lazy/avoid peeking/overreading when no bits left (OG code does it though, shouldn't matter)
* - same with read_callback (doesn't affect anything but cleaner)
*/
typedef enum {
UTK_EA, // standard EA-MT (MT10 or MT5)
UTK_EA_PCM, // EA-MT with PCM blocks
UTK_CBX, // Traveller's Tales Chatterbox
} utk_type_t;
/* opaque struct */
typedef struct utk_context_t utk_context_t;
/* inits UTK (must be externally created + init here) */
utk_context_t* utk_init(utk_type_t type);
void utk_free(utk_context_t*);
/* reset/flush */
void utk_reset(utk_context_t* ctx);
/* loads current data (can also be used to reset buffered data if set to 0) */
void utk_set_buffer(utk_context_t* ctx, const uint8_t* buf, size_t buf_size);
/* prepares for external streaming (buf is where reads store data, arg is any external params for the callback) */
void utk_set_callback(utk_context_t* ctx, uint8_t* buf, size_t buf_size, void* arg, size_t (*read_callback)(void*, int , void*));
/* main decode; returns decoded samples on ok (always >0), < 0 on error */
int utk_decode_frame(utk_context_t* ctx);
/* get sample buf (shouldn't change between calls); sample type is PCM float (+-32768 but not clamped) */
float* utk_get_samples(utk_context_t* ctx);
#endif

View file

@ -391,7 +391,7 @@ static int ealayer3_parse_frame_v2(ealayer3_buffer_t* ib, ealayer3_frame_t* eaf)
}
VGM_ASSERT(eaf->v2_extended_flag && eaf->v2_common_size == 0, "EA EAL3: v2 empty frame\n"); /* seen in V2S */
VGM_ASSERT(eaf->v2_extended_flag && eaf->v2_offset_samples > 0, "EA EAL3: v2_offset_mode=%x with value=0x%x\n", eaf->v2_offset_mode, eaf->v2_offset_samples);
VGM_ASSERT(eaf->v2_extended_flag && eaf->v2_offset_samples > 0, "EA EAL3: v2_offset_mode=%x with value=0x%x\n", eaf->v2_offset_mode, eaf->v2_offset_samples); /* Dead Space 2 (PC) */
//VGM_ASSERT(eaf->v2_pcm_samples > 0, "EA EAL3: v2_pcm_samples 0x%x\n", eaf->v2_pcm_samples);
eaf->pcm_size = (eaf->v2_pcm_samples * sizeof(int16_t) * eaf->channels);
@ -668,7 +668,7 @@ static void ealayer3_copy_pcm_block(uint8_t* outbuf, off_t pcm_offset, int pcm_n
int pos = 0;
for (i = 0; i < pcm_number * channels_per_frame; i++) {
int16_t pcm_sample = get_s16be(pcm_block + pos);
put_16bitLE(outbuf + pos, pcm_sample);
put_s16le(outbuf + pos, pcm_sample);
pos += sizeof(sample);
}
@ -680,7 +680,7 @@ static void ealayer3_copy_pcm_block(uint8_t* outbuf, off_t pcm_offset, int pcm_n
int put_pos = sizeof(sample) * ch;
for (i = 0; i < pcm_number; i++) {
int16_t pcm_sample = get_s16be(pcm_block + get_pos);
put_16bitLE(outbuf + put_pos, pcm_sample);
put_s16le(outbuf + put_pos, pcm_sample);
get_pos += sizeof(sample);
put_pos += sizeof(sample) * channels_per_frame;

View file

@ -135,8 +135,8 @@ static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data
/* read + write PCM block samples (always LE) */
for (i = 0; i < eaf->pcm_number * data->channels_per_frame; i++) {
off_t pcm_offset = stream->offset + eaf->pre_size + eaf->mpeg_size + sizeof(sample)*i;
int16_t pcm_sample = read_16bitLE(pcm_offset,stream->streamfile);
put_16bitLE(ms->output_buffer + bytes_filled + sizeof(sample)*i, pcm_sample);
int16_t pcm_sample = read_s16le(pcm_offset,stream->streamfile);
put_s16le(ms->output_buffer + bytes_filled + sizeof(sample) * i, pcm_sample);
}
ms->samples_filled += eaf->pcm_number;

View file

@ -38,6 +38,8 @@ ogg_vorbis_codec_data* init_ogg_vorbis(STREAMFILE* sf, off_t start, off_t size,
ov_callbacks callbacks = {0};
//todo clean up
if (!sf)
return NULL;
callbacks.read_func = ov_read_func;
callbacks.seek_func = ov_seek_func;

View file

@ -175,8 +175,8 @@ static int build_header(uint8_t* buf, size_t bufsize, STREAMFILE* sf, off_t pack
if (0x07+packet_size-0x03 > bufsize) return 0;
put_8bit (buf+0x00, read_8bit(packet_offset,sf)); /* packet_type */
memcpy (buf+0x01, "vorbis", 6); /* id */
put_u8(buf+0x00, read_8bit(packet_offset,sf)); /* packet_type */
memcpy(buf+0x01, "vorbis", 6); /* id */
bytes = read_streamfile(buf+0x07,packet_offset+0x03, packet_size-0x03,sf); /* copy rest (all except id+"SK") */
if (packet_size-0x03 != bytes)
return 0;

View file

@ -740,8 +740,8 @@ static int ww2ogg_generate_vorbis_setup(bitstream_t* ow, bitstream_t* iw, vorbis
/* packet header */
put_8bit(ow->buf+0x00, 0x05); /* packet_type (setup) */
memcpy (ow->buf+0x01, "vorbis", 6); /* id */
put_u8(ow->buf+0x00, 0x05); /* packet_type (setup) */
memcpy(ow->buf+0x01, "vorbis", 6); /* id */
ow->b_off += (1+6) * 8; /* bit offset of output (Vorbis) setup, after fake type + id */
@ -1233,12 +1233,12 @@ static int load_wvc_array(uint8_t* buf, size_t bufsize, uint32_t codebook_id, ww
int codebook_count;
/* at the end of the WVC is an offset table, and we need to find codebook id (number) offset */
table_start = get_32bitLE(wvc + wvc_size - 4); /* last offset */
table_start = get_u32le(wvc + wvc_size - 4); /* last offset */
codebook_count = ((wvc_size - table_start) / 4) - 1;
if (codebook_id >= codebook_count) goto fail;
codebook_offset = get_32bitLE(wvc + table_start + codebook_id*4);
codebook_size = get_32bitLE(wvc + table_start + codebook_id*4 + 4) - codebook_offset;
codebook_offset = get_u32le(wvc + table_start + codebook_id*4);
codebook_size = get_u32le(wvc + table_start + codebook_id*4 + 4) - codebook_offset;
if (codebook_size > bufsize) goto fail;
memcpy(buf, wvc+codebook_offset, codebook_size);

View file

@ -23,7 +23,6 @@ static const char* extension_list[] = {
"208",
"2dx9",
"2pfs",
"3do",
"3ds", //txth/reserved [F1 2011 (3DS)]
"4", //for Game.com audio
@ -131,7 +130,9 @@ static const char* extension_list[] = {
"cads",
"caf",
"cat",
"cbd2",
"cbx",
"cd",
"cfn", //fake extension for CAF (renamed, to be removed?)
"chd", //txth/reserved [Donkey Konga (GC), Star Fox Assault (GC)]
@ -203,6 +204,7 @@ static const char* extension_list[] = {
"gcm",
"gcub",
"gcw",
"ged",
"genh",
"gin",
"gmd", //txth/semi [High Voltage games: Charlie and the Chocolate Factory (GC), Zathura (GC)]
@ -211,6 +213,7 @@ static const char* extension_list[] = {
"gsf",
"gsp",
"gtd",
"gwb",
"gwm",
"h4m",
@ -344,6 +347,7 @@ static const char* extension_list[] = {
//"m4a", //common
//"m4v", //common
//"mov", //common
"move",
//"mp+", //common [Moonshine Runners (PC)]
//"mp2", //common
//"mp3", //common
@ -364,6 +368,7 @@ static const char* extension_list[] = {
"msvp", //fake extension/header id for .msv
"mta2",
"mtaf",
"mtt", //txth/reserved [Splinter Cell: Pandora Tomorrow (PS2)]
"mul",
"mups",
"mus",
@ -380,6 +385,7 @@ static const char* extension_list[] = {
"nds",
"ndp", //fake extension/header id for .nds
"nlsd",
"no",
"nop",
"nps",
"npsf", //fake extension/header id for .nps (in bigfiles)
@ -392,6 +398,7 @@ static const char* extension_list[] = {
"nwa",
"nwav",
"nxa",
"nxopus",
//"ogg", //common
"ogg_",
@ -422,7 +429,7 @@ static const char* extension_list[] = {
"psb",
"psf",
"psh", //fake extension for .vsv (to be removed)
"psnd",
"psn",
"pwb",
"r",
@ -474,6 +481,7 @@ static const char* extension_list[] = {
"sbr",
"sbv",
"sig",
"slb", //txth/reserved [Vingt-et-un Systems PS2 games (Last Escort, etc]
"sm0",
"sm1",
"sm2",
@ -522,6 +530,7 @@ static const char* extension_list[] = {
"sps",
"spsd",
"spw",
"srsa",
"ss2",
"ssd", //txth/reserved [Zack & Wiki (Wii)]
"ssm",
@ -535,6 +544,7 @@ static const char* extension_list[] = {
"stream",
"strm",
"sts",
"stv", //txth/reserved [Socio Art Logic PS2 games (Zero no Tsukaima games, Cambrian QTS, Shirogane no Soleil, etc)]
"sts_cp3",
"stx",
"svag",
@ -628,6 +638,7 @@ static const char* extension_list[] = {
"wd",
"wem",
"wii",
"wiive", //txth/semi [Rubik World (Wii)]
"wic", //txth/reserved [Road Rash (SAT)-videos]
"wip", //txth/reserved [Colin McRae DiRT (PC)]
"wlv", //txth/reserved [ToeJam & Earl III: Mission to Earth (DC)]
@ -831,6 +842,7 @@ static const coding_info coding_info_list[] = {
{coding_UBI_SCE_IMA, "Ubisoft 4-bit SCE IMA ADPCM"},
{coding_H4M_IMA, "Hudson HVQM4 4-bit IMA ADPCM"},
{coding_CD_IMA, "Crystal Dynamics 4-bit IMA ADPCM"},
{coding_CRANKCASE_IMA, "CrankcaseAudio REV 4-bit IMA ADPCM"},
{coding_MSADPCM, "Microsoft 4-bit ADPCM"},
{coding_MSADPCM_int, "Microsoft 4-bit ADPCM (mono/interleave)"},
@ -1061,7 +1073,7 @@ static const meta_info meta_info_list[] = {
{meta_RSTM_ROCKSTAR, "Rockstar Games RSTM Header"},
{meta_ACM, "InterPlay ACM Header"},
{meta_MUS_ACM, "InterPlay MUS ACM header"},
{meta_PS2_KCES, "Konami KCES Header"},
{meta_VIG_KCES, "Konami .VIG Header"},
{meta_HXD, "Tecmo HXD Header"},
{meta_VSV, "Square Enix .vsv Header"},
{meta_RIFF_WAVE_labl, "RIFF WAVE header (labl looping)"},
@ -1093,7 +1105,7 @@ static const meta_info meta_info_list[] = {
{meta_MUS_KROME, "Krome .MUS header"},
{meta_WII_SNG, "SNG DSP Header"},
{meta_RSD, "Radical RSD header"},
{meta_DC_ASD, "ASD Header"},
{meta_ASD_NAXAT, "Naxat .ASD header"},
{meta_SPSD, "Sega Naomi SPSD header"},
{meta_FFXI_BGW, "Square Enix .BGW header"},
{meta_FFXI_SPW, "Square Enix .SPW header"},
@ -1119,8 +1131,8 @@ static const meta_info meta_info_list[] = {
{meta_MUL, "Crystal Dynamics .MUL header"},
{meta_THP, "Nintendo THP header"},
{meta_STS, "Alfa System .STS header"},
{meta_PS2_P2BT, "Pop'n'Music 7 Header"},
{meta_PS2_GBTS, "Pop'n'Music 9 Header"},
{meta_P2BT_MOVE_VISA, "Konami P2BT/MOVE/VISA header"},
{meta_GBTS, "Konami GBTS header"},
{meta_NGC_DSP_IADP, "IADP Header"},
{meta_RIFF_WAVE_MWV, "RIFF WAVE header (ctrl looping)"},
{meta_FFCC_STR, "Final Fantasy: Crystal Chronicles STR header"},
@ -1133,7 +1145,7 @@ static const meta_info meta_info_list[] = {
{meta_ADS_MIDWAY, "Midway ADS header"},
{meta_PS2_MCG, "Gunvari MCG Header"},
{meta_ZSD, "Konami ZSD header"},
{meta_REDSPARK, "RedSpark Header"},
{meta_REDSPARK, "RedSpark header"},
{meta_IVAUD, "Rockstar .ivaud header"},
{meta_DSP_WII_WSD, ".WSD header"},
{meta_WII_NDP, "Icon Games NDP header"},
@ -1208,7 +1220,7 @@ static const meta_info meta_info_list[] = {
{meta_RAW_SNDS, "PC .snds raw header"},
{meta_PS2_WMUS, "assumed The Warriors Sony ADPCM by .wmus extension"},
{meta_HYPERSCAN_KVAG, "Mattel Hyperscan KVAG"},
{meta_IOS_PSND, "PSND Header"},
{meta_PSND, "Polarbit PSND header"},
{meta_ADP_WILDFIRE, "Wildfire ADP! header"},
{meta_QD_ADP, "Quantic Dream .ADP header"},
{meta_EB_SFX, "Excitebots .sfx header"},
@ -1220,7 +1232,7 @@ static const meta_info meta_info_list[] = {
{meta_MSS, "Guerilla MCSS header"},
{meta_PS2_HSF, "Lowrider 'HSF' header"},
{meta_IVAG, "Namco IVAG header"},
{meta_PS2_2PFS, "Konami 2PFS header"},
{meta_2PFS, "Konami 2PFS header"},
{meta_UBI_CKD, "Ubisoft CKD RIFF header"},
{meta_PS2_VBK, "PS2 VBK Header"},
{meta_OTM, "Otomedius OTM Header"},
@ -1316,7 +1328,6 @@ static const meta_info meta_info_list[] = {
{meta_MSV, "Sony MultiStream MSV header"},
{meta_SDF, "Beyond Reality SDF header"},
{meta_SVG, "High Voltage SVG header"},
{meta_VIS, "Konami VIS header"},
{meta_VAI, "Asobo Studio .VAI header"},
{meta_AIF_ASOBO, "Asobo Studio .AIF header"},
{meta_AO, "AlphaOgg .AO header"},
@ -1325,7 +1336,7 @@ static const meta_info meta_info_list[] = {
{meta_XAU_KONAMI, "Konami XAU header"},
{meta_DERF, "Xilam DERF header"},
{meta_UTK, "Maxis UTK header"},
{meta_NXA, "Entergram NXA header"},
{meta_NXA1, "Entergram NXA1 header"},
{meta_ADPCM_CAPCOM, "Capcom .ADPCM header"},
{meta_UE4OPUS, "Epic Games UE4OPUS header"},
{meta_XWMA, "Microsoft XWMA RIFF header"},
@ -1415,6 +1426,9 @@ static const meta_info meta_info_list[] = {
{meta_SQUEAKSTREAM, "Torus SqueakStream header"},
{meta_SQUEAKSAMPLE, "Torus SqueakSample header"},
{meta_SNDS, "Sony SNDS header"},
{meta_NXOF, "Nihon Falcom FDK header"},
{meta_GWB_GWD, "Ubisoft GWB+GWD header"},
{meta_CBX, "Traveller's Tales CBX header"},
};
void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t out_size) {

View file

@ -11,13 +11,19 @@ static size_t get_block_header_size(STREAMFILE* sf, off_t offset, size_t channel
void block_update_awc(off_t block_offset, VGMSTREAM * vgmstream) {
STREAMFILE* sf = vgmstream->ch[0].streamfile;
int32_t (*read_32bit)(off_t,STREAMFILE*) = vgmstream->codec_endian ? read_32bitBE : read_32bitLE;
size_t header_size, entries, block_size, block_samples;
size_t channel_header_size;
size_t header_size, entries, block_size, block_samples, frame_size;
size_t channel_header_size;
int i;
/* assumed only AWC_IMA enters here, MPEG/XMA2 need special parsing as blocked layout is too limited */
entries = read_32bit(block_offset + 0x04, sf); /* se first channel, assume all are the same */
//block_samples = entries * (0x800-4)*2; //todo use
/* assumes only AWC_IMA/DSP enters here, MPEG/XMA2 need special parsing as blocked layout is too limited.
* Block header (see awc.c for a complete description):
* - per channel: header table (size 0x18 or 0x10)
* - per channel: seek table (32b * entries = global samples per frame in each block) (not in DSP/Vorbis)
* - per channel: extra table (DSP only)
* - padding (not in ATRAC9/DSP)
*/
entries = read_32bit(block_offset + 0x04, sf); /* se first channel, assume all are the same (not true in MPEG/XMA) */
block_samples = read_32bit(block_offset + 0x0c, sf);
block_size = vgmstream->full_block_size;
@ -25,24 +31,32 @@ void block_update_awc(off_t block_offset, VGMSTREAM * vgmstream) {
vgmstream->next_block_offset = block_offset + block_size;
vgmstream->current_block_samples = block_samples;
/* starts with a header block */
/* for each channel
* 0x00: start entry within channel (ie. entries * ch) but may be off by +1/+2
* 0x04: entries
* 0x08: samples to discard in the beginning of this block (MPEG only?)
* 0x0c: samples in channel (for MPEG/XMA2 can vary between channels)
* (next fields don't exist in later versions for IMA)
* 0x10: (MPEG only, empty otherwise) close to number of frames but varies a bit?
* 0x14: (MPEG only, empty otherwise) channel usable data size (not counting padding)
* for each channel
* 32b * entries = global samples per frame in each block (for MPEG probably per full frame)
*/
switch(vgmstream->coding_type) {
case coding_NGC_DSP:
channel_header_size = 0x10;
frame_size = 0x08;
/* coefs on every block but it's always the same */
dsp_read_coefs_le(vgmstream, sf, block_offset + channel_header_size * vgmstream->channels + 0x10 + 0x1c + 0x00, 0x10 + 0x60);
dsp_read_hist_le (vgmstream, sf, block_offset + channel_header_size * vgmstream->channels + 0x10 + 0x1c + 0x20, 0x10 + 0x60);
header_size = 0;
header_size += channel_header_size * vgmstream->channels; /* header table */
/* no seek table */
header_size += 0x70 * vgmstream->channels; /* extra table */
/* no padding */
break;
default:
channel_header_size = get_channel_header_size(sf, block_offset, vgmstream->codec_endian);
header_size = get_block_header_size(sf, block_offset, channel_header_size, vgmstream->channels, vgmstream->codec_endian);
frame_size = 0x800;
break;
}
channel_header_size = get_channel_header_size(sf, block_offset, vgmstream->codec_endian);
header_size = get_block_header_size(sf, block_offset, channel_header_size, vgmstream->channels, vgmstream->codec_endian);
for (i = 0; i < vgmstream->channels; i++) {
vgmstream->ch[i].offset = block_offset + header_size + 0x800*entries*i;
VGM_ASSERT(entries != read_32bit(block_offset + channel_header_size*i + 0x04, sf), "AWC: variable number of entries found at %lx\n", block_offset);
vgmstream->ch[i].offset = block_offset + header_size + frame_size * entries * i;
}
}

View file

@ -2,23 +2,21 @@
#include "../coding/coding.h"
/* 2PFS - from Konami Games [Mahoromatic: Moetto - KiraKira Maid-San (PS2), GANTZ The Game (PS2)] */
VGMSTREAM* init_vgmstream_ps2_2pfs(STREAMFILE *sf) {
VGMSTREAM * vgmstream = NULL;
/* 2PFS - from Konami Games [Mahoromatic: Moetto-KiraKira Maid-San (PS2), GANTZ The Game (PS2)] */
VGMSTREAM* init_vgmstream_2pfs(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset;
int loop_flag, channels, version, interleave;
int loop_start_block, loop_end_block; /* block number */
int loop_start_block, loop_end_block;
int loop_start_adjust, loop_end_adjust; /* loops start/end a few samples into the start/end block */
/* checks */
/* .sap: standard
* .2pfs: header id? (Mahoromatic) */
if (!check_extensions(sf, "sap,2pfs"))
goto fail;
if (read_u32be(0x00,sf) != 0x32504653) /* "2PFS" */
goto fail;
if (!is_id32be(0x00,sf, "2PFS"))
return NULL;
/* .sap: standard */
if (!check_extensions(sf, "sap"))
return NULL;
version = read_u16le(0x04,sf);
if (version != 0x01 && version != 0x02) /* v1: Mahoromatic, v2: Gantz */
@ -44,7 +42,7 @@ VGMSTREAM* init_vgmstream_ps2_2pfs(STREAMFILE *sf) {
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_PS2_2PFS;
vgmstream->meta_type = meta_2PFS;
vgmstream->num_samples = read_u32le(0x34,sf) * 28 / 16 / channels;
vgmstream->coding_type = coding_PSX;
vgmstream->layout_type = layout_interleave;
@ -77,7 +75,6 @@ VGMSTREAM* init_vgmstream_ps2_2pfs(STREAMFILE *sf) {
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;

View file

@ -92,6 +92,7 @@ fail:
#define ACB_TABLE_BUFFER_TRACKCOMMAND 0x2000
#define ACB_TABLE_BUFFER_SYNTH 0x4000
#define ACB_TABLE_BUFFER_WAVEFORM 0x4000
#define ACB_TABLE_BUFFER_WAVEFORMEXTENSIONDATA 0x1000
#define ACB_MAX_NAMELIST 255
#define ACB_MAX_NAME 1024 /* even more is possible in rare cases [Senran Kagura Burst Re:Newal (PC)] */
@ -159,8 +160,15 @@ typedef struct {
uint16_t Id;
uint16_t PortNo;
uint8_t Streaming;
uint8_t LoopFlag;
uint16_t ExtensionData;
} Waveform_t;
typedef struct {
uint32_t LoopStart;
uint32_t LoopEnd;
} WaveformExtensionData_t;
typedef struct {
STREAMFILE* acbFile; /* original reference, don't close */
@ -178,6 +186,7 @@ typedef struct {
STREAMFILE* TrackCommandSf;
STREAMFILE* SynthSf;
STREAMFILE* WaveformSf;
STREAMFILE* WaveformExtensionDataSf;
Cue_t* Cue;
CueName_t* CueName;
@ -188,6 +197,7 @@ typedef struct {
TrackCommand_t* TrackCommand;
Synth_t* Synth;
Waveform_t* Waveform;
WaveformExtensionData_t* WaveformExtensionData;
int Cue_rows;
int CueName_rows;
@ -198,6 +208,7 @@ typedef struct {
int TrackCommand_rows;
int Synth_rows;
int Waveform_rows;
int WaveformExtensionData_rows;
/* config */
int is_memory;
@ -208,7 +219,8 @@ typedef struct {
int synth_depth;
int sequence_depth;
/* name stuff */
/* name/config stuff */
int16_t waveform_index;
int16_t cuename_index;
const char* cuename_name;
int awbname_count;
@ -297,7 +309,7 @@ static void add_acb_name(acb_header* acb, int8_t Streaming) {
static int preload_acb_waveform(acb_header* acb) {
utf_context* Table = NULL;
int* p_rows = &acb->Waveform_rows;
int i, c_Id, c_MemoryAwbId, c_StreamAwbId, c_StreamAwbPortNo, c_Streaming;
int i, c_Id, c_MemoryAwbId, c_StreamAwbId, c_StreamAwbPortNo, c_Streaming, c_LoopFlag, c_ExtensionData;
if (*p_rows)
return 1;
@ -315,6 +327,8 @@ static int preload_acb_waveform(acb_header* acb) {
c_StreamAwbId = utf_get_column(Table, "StreamAwbId");
c_StreamAwbPortNo = utf_get_column(Table, "StreamAwbPortNo");
c_Streaming = utf_get_column(Table, "Streaming");
c_LoopFlag = utf_get_column(Table, "LoopFlag");
c_ExtensionData = utf_get_column(Table, "ExtensionData");
for (i = 0; i < *p_rows; i++) {
Waveform_t* r = &acb->Waveform[i];
@ -332,6 +346,10 @@ static int preload_acb_waveform(acb_header* acb) {
r->PortNo = 0xFFFF;
}
utf_query_col_u8(Table, i, c_Streaming, &r->Streaming);
utf_query_col_u8(Table, i, c_LoopFlag, &r->LoopFlag);
r->ExtensionData = -1;
utf_query_col_u16(Table, i, c_ExtensionData, &r->ExtensionData); /* optional for newer/Switch acb */
}
utf_close(Table);
@ -346,7 +364,7 @@ static int load_acb_waveform(acb_header* acb, uint16_t Index) {
Waveform_t* r;
if (!preload_acb_waveform(acb)) goto fail;
if (Index > acb->Waveform_rows) goto fail;
if (Index >= acb->Waveform_rows) goto fail;
r = &acb->Waveform[Index];
//;VGM_LOG("acb: Waveform[%i]: Id=%i, PortNo=%i, Streaming=%i\n", Index, r->Id, r->PortNo, r->Streaming);
@ -362,6 +380,9 @@ static int load_acb_waveform(acb_header* acb, uint16_t Index) {
if ((acb->is_memory && r->Streaming == 1) || (!acb->is_memory && r->Streaming == 0))
return 1;
/* save waveid <> Index translation */
acb->waveform_index = Index;
/* aaand finally get name (phew) */
add_acb_name(acb, r->Streaming);
@ -417,7 +438,7 @@ static int load_acb_synth(acb_header* acb, uint16_t Index) {
int i, count;
if (!preload_acb_synth(acb)) goto fail;
if (Index > acb->Synth_rows) goto fail;
if (Index >= acb->Synth_rows) goto fail;
r = &acb->Synth[Index];
//;VGM_LOG("acb: Synth[%i]: Type=%x, ReferenceItems={%x,%x}\n", Index, r->Type, r->ReferenceItems_offset, r->ReferenceItems_size);
@ -615,7 +636,7 @@ static int load_acb_trackcommand(acb_header* acb, uint16_t Index) {
TrackCommand_t* r;
if (!preload_acb_trackcommand(acb)) goto fail;
if (Index > acb->TrackCommand_rows) goto fail;
if (Index >= acb->TrackCommand_rows) goto fail;
r = &acb->TrackCommand[Index];
//;VGM_LOG("acb: TrackEvent/Command[%i]: Command={%x,%x}\n", Index, r->Command_offset, r->Command_size);
@ -669,7 +690,7 @@ static int load_acb_track(acb_header* acb, uint16_t Index) {
Track_t* r;
if (!preload_acb_track(acb)) goto fail;
if (Index > acb->Track_rows) goto fail;
if (Index >= acb->Track_rows) goto fail;
r = &acb->Track[Index];
//;VGM_LOG("acb: Track[%i]: EventIndex=%i\n", Index, r->EventIndex);
@ -736,7 +757,7 @@ static int load_acb_sequence(acb_header* acb, uint16_t Index) {
int i;
if (!preload_acb_sequence(acb)) goto fail;
if (Index > acb->Sequence_rows) goto fail;
if (Index >= acb->Sequence_rows) goto fail;
r = &acb->Sequence[Index];
//;VGM_LOG("acb: Sequence[%i]: NumTracks=%i, TrackIndex={%x, %x}, Type=%x\n", Index, r->NumTracks, r->TrackIndex_offset, r->TrackIndex_size, r->Type);
@ -832,7 +853,7 @@ static int load_acb_block(acb_header* acb, uint16_t Index) {
int i;
if (!preload_acb_block(acb)) goto fail;
if (Index > acb->Block_rows) goto fail;
if (Index >= acb->Block_rows) goto fail;
r = &acb->Block[Index];
//;VGM_LOG("acb: Block[%i]: NumTracks=%i, TrackIndex={%x, %x}\n", Index, r->NumTracks, r->TrackIndex_offset, r->TrackIndex_size);
@ -900,7 +921,7 @@ static int load_acb_blocksequence(acb_header* acb, uint16_t Index) {
int i;
if (!preload_acb_blocksequence(acb)) goto fail;
if (Index > acb->BlockSequence_rows) goto fail;
if (Index >= acb->BlockSequence_rows) goto fail;
r = &acb->BlockSequence[Index];
//;VGM_LOG("acb: BlockSequence[%i]: NumTracks=%i, TrackIndex={%x, %x}, NumBlocks=%i, BlockIndex={%x, %x}\n", Index, r->NumTracks, r->TrackIndex_offset,r->TrackIndex_size, r->NumBlocks, r->BlockIndex_offset, r->BlockIndex_size);
@ -977,7 +998,7 @@ static int load_acb_cue(acb_header* acb, uint16_t Index) {
/* read Cue[Index] */
if (!preload_acb_cue(acb)) goto fail;
if (Index > acb->Cue_rows) goto fail;
if (Index >= acb->Cue_rows) goto fail;
r = &acb->Cue[Index];
//;VGM_LOG("acb: Cue[%i]: ReferenceType=%i, ReferenceIndex=%i\n", Index, r->ReferenceType, r->ReferenceIndex);
@ -1064,7 +1085,7 @@ static int load_acb_cuename(acb_header* acb, uint16_t Index) {
CueName_t* r;
if (!preload_acb_cuename(acb)) goto fail;
if (Index > acb->CueName_rows) goto fail;
if (Index >= acb->CueName_rows) goto fail;
r = &acb->CueName[Index];
//;VGM_LOG("acb: CueName[%i]: CueIndex=%i, CueName=%s\n", Index, r->CueIndex, r->CueName);
@ -1081,6 +1102,79 @@ fail:
return 0;
}
static int preload_acb_waveformextensiondata(acb_header* acb) {
utf_context* Table = NULL;
int* p_rows = &acb->WaveformExtensionData_rows;
int i, c_LoopStart, c_LoopEnd;
if (*p_rows)
return 1;
if (!open_utf_subtable(acb, &acb->WaveformExtensionDataSf, &Table, "WaveformExtensionDataTable", p_rows, ACB_TABLE_BUFFER_WAVEFORMEXTENSIONDATA))
goto fail;
if (!*p_rows)
return 1;
//;VGM_LOG("acb: preload WaveformExtensionData=%i\n", *p_rows);
acb->WaveformExtensionData = malloc(*p_rows * sizeof(WaveformExtensionData_t));
if (!acb->WaveformExtensionData) goto fail;
c_LoopStart = utf_get_column(Table, "LoopStart");
c_LoopEnd = utf_get_column(Table, "LoopEnd");
for (i = 0; i < *p_rows; i++) {
WaveformExtensionData_t* r = &acb->WaveformExtensionData[i];
utf_query_col_u32(Table, i, c_LoopStart, &r->LoopStart);
utf_query_col_u32(Table, i, c_LoopEnd, &r->LoopEnd);
}
utf_close(Table);
return 1;
fail:
VGM_LOG("acb: failed WaveformExtensionData preload\n");
utf_close(Table);
return 0;
}
/* for Switch Opus that has loop info in a separate "WaveformExtensionData" table (pointed by a field in Waveform) */
static int load_acb_loops(acb_header* acb, VGMSTREAM* vgmstream) {
Waveform_t* rw;
WaveformExtensionData_t* r;
uint16_t WaveIndex = acb->waveform_index;
uint16_t ExtensionIndex = -1;
if (vgmstream->loop_flag)
return 0;
/* assumes that will be init'd before while searching for names */
if (WaveIndex < 0) goto fail;
//if (!preload_acb_waveform(acb)) goto fail;
if (WaveIndex >= acb->Waveform_rows) goto fail;
rw = &acb->Waveform[WaveIndex];
/* 1=no loop, 2=loop, ignore others/0(default)/255 just in case */
if (rw->LoopFlag != 2)
return 0;
ExtensionIndex = rw->ExtensionData;
if (ExtensionIndex < 0) goto fail; /* not init'd? */
if (!preload_acb_waveformextensiondata(acb)) goto fail;
if (ExtensionIndex >= acb->WaveformExtensionData_rows) goto fail;
r = &acb->WaveformExtensionData[ExtensionIndex];
//;VGM_LOG("acb: WaveformExtensionData[%i]: LoopStart=%i, LoopEnd=%i\n", Index, r->LoopStart, r->LoopEnd);
vgmstream_force_loop(vgmstream, 1, r->LoopStart, r->LoopEnd);
return 1;
fail:
VGM_LOG("acb: failed WaveformExtensionData %i\n", ExtensionIndex);
return 0;
}
/*****************************************************************************/
/* Normally games load a .acb + .awb, and asks the .acb to play a cue by name or index.
@ -1111,7 +1205,7 @@ fail:
* per table, meaning it uses a decent chunk of memory, but having to re-read with streamfiles is much slower.
*/
void load_acb_wave_name(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int port, int is_memory) {
void load_acb_wave_info(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int port, int is_memory, int load_loops) {
acb_header acb = {0};
int i;
@ -1129,6 +1223,7 @@ void load_acb_wave_name(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int po
acb.target_waveid = waveid;
acb.target_port = port;
acb.is_memory = is_memory;
acb.waveform_index = -1;
/* read all possible cue names and find which waveids are referenced by it */
preload_acb_cuename(&acb);
@ -1143,6 +1238,11 @@ void load_acb_wave_name(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int po
vgmstream->stream_name[STREAM_NAME_SIZE - 1] = '\0';
}
/* uncommon */
if (load_loops) {
load_acb_loops(&acb, vgmstream);
}
/* done */
fail:
utf_close(acb.Header);
@ -1157,6 +1257,7 @@ fail:
close_streamfile(acb.TrackCommandSf);
close_streamfile(acb.SynthSf);
close_streamfile(acb.WaveformSf);
close_streamfile(acb.WaveformExtensionDataSf);
free(acb.CueName);
free(acb.Cue);
@ -1167,4 +1268,5 @@ fail:
free(acb.TrackCommand);
free(acb.Synth);
free(acb.Waveform);
free(acb.WaveformExtensionData);
}

View file

@ -5,7 +5,8 @@
typedef struct {
int total_subsongs;
int target_subsong;
int version;
int file_version;
int header_version; /* major.minor in hex */
uint32_t stream_offset;
uint32_t stream_size;
@ -18,7 +19,7 @@ typedef struct {
static int parse_adm(adm_header_t* adm, STREAMFILE* sf);
static VGMSTREAM* init_vgmstream_adm(STREAMFILE* sf, int version);
static VGMSTREAM* init_vgmstream_adm(STREAMFILE* sf, int file_version);
/* ADM2 - Crankcase Audio REV plugin file [The Grand Tour Game (PC)] */
VGMSTREAM* init_vgmstream_adm2(STREAMFILE* sf) {
@ -38,26 +39,24 @@ VGMSTREAM* init_vgmstream_adm3(STREAMFILE* sf) {
/* checks */
if (!is_id32be(0x00,sf, "ADM3"))
return NULL;
if (!check_extensions(sf, "wem"))
if (!check_extensions(sf, "wem,bnk"))
return NULL;
return init_vgmstream_adm(sf, 3);
}
static VGMSTREAM* init_vgmstream_adm(STREAMFILE* sf, int version) {
static VGMSTREAM* init_vgmstream_adm(STREAMFILE* sf, int file_version) {
VGMSTREAM* vgmstream = NULL;
adm_header_t adm = {0};
/* ADMx are files used with the Wwise Crankaudio plugin, that simulate engine noises with
* base internal samples and some internal RPM config (probably). Actual file seems to
* define some combo of samples, this only plays those separate samples.
* Decoder is basically Apple's IMA (internally just "ADPCMDecoder") but transforms to float
* each sample during decode by multiplying by 0.000030518509 */
* define some combo of samples, this only plays those separate samples. */
adm.target_subsong = sf->stream_index;
if (adm.target_subsong == 0) adm.target_subsong = 1;
adm.version = version;
adm.file_version = file_version;
if (!parse_adm(&adm, sf))
goto fail;
@ -73,9 +72,21 @@ static VGMSTREAM* init_vgmstream_adm(STREAMFILE* sf, int version) {
vgmstream->num_streams = adm.total_subsongs;
vgmstream->stream_size = adm.stream_size;
vgmstream->coding_type = coding_APPLE_IMA4;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x22;
switch(adm.header_version) {
case 0x00070000: /* The Crew Motorfest (PC) */
vgmstream->coding_type = coding_CRANKCASE_IMA;
//vgmstream->layout_type = layout_interleave;
//vgmstream->interleave_block_size = 0x23;
break;
default: /* The Grand Tour Game (PC) [0x00050000], MotoGP 21 (PC) [0x00060000] */
/* Basically Apple's IMA (internally just "ADPCMDecoder") but transforms to float
* each sample during decode by multiplying by 0.000030518509 */
vgmstream->coding_type = coding_APPLE_IMA4;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x22;
break;
}
if (!vgmstream_open_stream(vgmstream, sf, adm.stream_offset))
goto fail;
@ -169,12 +180,12 @@ static int parse_adm(adm_header_t* adm, STREAMFILE* sf) {
uint32_t offset;
/* 0x04: null */
/* 0x08: version? (ADM2: 0x00050000, ADM3: 0x00060000) */
adm->header_version = read_u32le(0x08, sf); /* ADM2: 0x00050000, ADM3: 0x00060000 (older) / 0x00070000 (2023) */
/* 0x0c: header size */
/* 0x10: data start */
/* rest unknown, looks mostly the same between files (some floats and stuff) */
switch(adm->version) {
switch(adm->file_version) {
case 2:
/* low to high */
offset = read_u32le(0x104, sf);

View file

@ -202,12 +202,15 @@ static const adxkey_info adxkey8_list[] = {
/* Mirai Nikki: 13-ninme no Nikki Shoyuusha Re-Write (PSP) */
{0x58a3,0x66f5,0x599f, "FDRW17th",0},
/* Shoujo Yoshitsune-den Ni - Toki wo Koeru Chigiri (PS2) */
/* Shoujo Yoshitsune-den Ni: Toki wo Koeru Chigiri (PS2) */
{0x62d7,0x483d,0x4fb7, "YOSHI2",0},
/* Junjou Romantica - Koi no Doki Doki Daisakusen (PS2) (Marvelous) */
/* Junjou Romantica: Koi no Doki Doki Daisakusen (PS2) */
{0x5827,0x612d,0x5585, "Endress-SETSUNAI!",0},
/* Corpse Party: Book of Shadows (PSP) */
{0x60ad,0x5689,0x5281, "\x83\x76\x83\x89\x83\x60\x83\x69Lovers_Day",0}, // "プラチナLovers_Day" in SHIFT-JIS
};
static const adxkey_info adxkey9_list[] = {

View file

@ -2,8 +2,8 @@
#include "../coding/coding.h"
/* Apple Core Audio Format File - from iOS games [Vectros (iOS), Ridge Racer Accelerated (iOS)] */
VGMSTREAM * init_vgmstream_apple_caff(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
VGMSTREAM* init_vgmstream_apple_caff(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset = 0, chunk_offset;
size_t file_size, data_size = 0;
int loop_flag, channel_count = 0, sample_rate = 0;
@ -15,20 +15,19 @@ VGMSTREAM * init_vgmstream_apple_caff(STREAMFILE *streamFile) {
/* checks */
if (!check_extensions(streamFile, "caf"))
goto fail;
if (!is_id32be(0x00,sf, "caff"))
return NULL;
if (read_32bitBE(0x04,sf) != 0x00010000) /* version/flags */
return NULL;
if (!check_extensions(sf, "caf"))
return NULL;
if (read_32bitBE(0x00,streamFile) != 0x63616666) /* "caff" */
goto fail;
if (read_32bitBE(0x04,streamFile) != 0x00010000) /* version/flags */
goto fail;
file_size = get_streamfile_size(streamFile);
file_size = get_streamfile_size(sf);
chunk_offset = 0x08;
while (chunk_offset < file_size) {
uint32_t chunk_type = read_32bitBE(chunk_offset+0x00,streamFile);
uint32_t chunk_size = (uint32_t)read_64bitBE(chunk_offset+0x04,streamFile);
uint32_t chunk_type = read_u32be(chunk_offset+0x00,sf);
uint32_t chunk_size = (uint32_t)read_u64be(chunk_offset+0x04,sf);
chunk_offset += 0x0c;
switch (chunk_type) {
@ -37,26 +36,26 @@ VGMSTREAM * init_vgmstream_apple_caff(STREAMFILE *streamFile) {
found_desc = 1;
{
uint64_t sample_long = (uint64_t)read_64bitBE(chunk_offset+0x00, streamFile);
uint64_t sample_long = read_u64be(chunk_offset+0x00, sf);
double* sample_double; /* double sample rate, double the fun */
sample_double = (double*)&sample_long;
sample_rate = (int)(*sample_double);
}
codec = read_32bitBE(chunk_offset+0x08, streamFile);
codec = read_32bitBE(chunk_offset+0x08, sf);
//codec_flags = read_32bitBE(chunk_offset+0x0c, streamFile);
bytes_per_packet = read_32bitBE(chunk_offset+0x10, streamFile);
samples_per_packet = read_32bitBE(chunk_offset+0x14, streamFile);
channels_per_packet = read_32bitBE(chunk_offset+0x18, streamFile);
bits_per_sample = read_32bitBE(chunk_offset+0x1C, streamFile);
bytes_per_packet = read_32bitBE(chunk_offset+0x10, sf);
samples_per_packet = read_32bitBE(chunk_offset+0x14, sf);
channels_per_packet = read_32bitBE(chunk_offset+0x18, sf);
bits_per_sample = read_32bitBE(chunk_offset+0x1C, sf);
break;
case 0x70616b74: /* "pakt" */
//found_pakt = 1;
//packets_table_size = (uint32_t)read_64bitBE(chunk_offset+0x00,streamFile); /* 0 for constant bitrate */
valid_samples = (uint32_t)read_64bitBE(chunk_offset+0x08,streamFile);
//packets_table_size = (uint32_t)read_u64be(chunk_offset+0x00,streamFile); /* 0 for constant bitrate */
valid_samples = (uint32_t)read_u64be(chunk_offset+0x08,sf);
//priming_samples = read_32bitBE(chunk_offset+0x10,streamFile); /* encoder delay samples */
//unused_samples = read_32bitBE(chunk_offset+0x14,streamFile); /* footer samples */
break;
@ -91,8 +90,8 @@ VGMSTREAM * init_vgmstream_apple_caff(STREAMFILE *streamFile) {
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = sample_rate;
vgmstream->meta_type = meta_CAFF;
vgmstream->sample_rate = sample_rate;
switch(codec) {
case 0x6C70636D: /* "lpcm" */
@ -150,7 +149,7 @@ VGMSTREAM * init_vgmstream_apple_caff(STREAMFILE *streamFile) {
goto fail;
}
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
if (!vgmstream_open_stream(vgmstream,sf,start_offset))
goto fail;
return vgmstream;

View file

@ -0,0 +1,61 @@
#include "meta.h"
#include "../coding/coding.h"
/* ASD - found Naxat (Spiel/Mesa) games [Miss Moonlight (DC), Yoshia no Oka de Nekoronde... (DC)] */
VGMSTREAM* init_vgmstream_asd_naxat(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
uint32_t start_offset;
int loop_flag, channels, sample_rate;
/* checks */
uint32_t data_size = read_u32le(0x00, sf); /* padded and slightly less than file size */
if (data_size == 0 || data_size >= get_streamfile_size(sf) || data_size + 0x20 + 0x10 < get_streamfile_size(sf))
return NULL;
if (data_size != read_u32le(0x04,sf)) /* repeated size */
return NULL;
/* extension of the audio bigfiles (there are N offsets to these subfiles) */
if (!check_extensions(sf, "asd"))
return NULL;
/* fmt chunk, extra checks since format is simple */
if (read_u16le(0x08,sf) != 0x01) /* format*/
return NULL;
channels = read_u16le(0x0a,sf);
sample_rate = read_s32le(0x0c,sf);
if (channels < 1 || channels > 2)
return NULL;
if (sample_rate != 22050)
return NULL;
if (sample_rate * channels * sizeof(int16_t) != read_u32le(0x10,sf)) /* bitrate */
return NULL;
/* 04: block size, bps */
if (read_u32le(0x18,sf) != 0x00 )
return NULL;
if (read_u32le(0x1c,sf) != 0x00 )
return NULL;
start_offset = 0x20;
loop_flag = 0;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_ASD_NAXAT;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = pcm16_bytes_to_samples(data_size, channels);
vgmstream->coding_type = coding_PCM16LE;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x02;
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View file

@ -4,7 +4,7 @@
//typedef enum { ADX, HCA, VAG, RIFF, CWAV, DSP, CWAC, M4A } awb_type_t;
static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid);
static void load_acb_info(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid, int load_loops);
/* AFS2/AWB (Atom Wave Bank) - CRI container of streaming audio, often together with a .acb cue sheet */
VGMSTREAM* init_vgmstream_awb(STREAMFILE* sf) {
@ -19,6 +19,7 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
uint8_t offset_size;
uint16_t waveid_alignment, offset_alignment, subkey;
int waveid;
int load_loops = 0;
/* checks */
@ -126,6 +127,11 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
extension = "m4a";
}
#endif
else if (read_u32be(subfile_offset + 0x00,sf) == 0x01000080) { /* (type 24=NXOpus) */
init_vgmstream =init_vgmstream_opus_std; /* Super Mario RPG (Switch) */
extension = "opus";
load_loops = 1; /* loops not in Opus (rare) but in .acb */
}
else { /* 12=XMA? */
vgm_logi("AWB: unknown codec (report)\n");
goto fail;
@ -144,8 +150,8 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
vgmstream->num_streams = total_subsongs;
}
/* try to load cue names */
load_awb_name(sf, sf_acb, vgmstream, waveid);
/* try to load cue names+etc */
load_acb_info(sf, sf_acb, vgmstream, waveid, load_loops);
close_streamfile(temp_sf);
return vgmstream;
@ -157,7 +163,7 @@ fail:
}
static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid) {
static void load_acb_info(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid, int load_loops) {
int is_memory = (sf_acb != NULL);
int port = 0;
@ -170,7 +176,7 @@ static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstre
/* try parsing TXTM if present */
sf_acb = read_filemap_file_pos(sf, 0, &port);
/* try (name).awb + (name).awb */
/* try (name).awb + (name).acb */
if (!sf_acb) {
sf_acb = open_streamfile_by_ext(sf, "acb");
}
@ -204,11 +210,11 @@ static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstre
}
/* probably loaded */
load_acb_wave_name(sf_acb, vgmstream, waveid, port, is_memory);
load_acb_wave_info(sf_acb, vgmstream, waveid, port, is_memory, load_loops);
close_streamfile(sf_acb);
}
else {
load_acb_wave_name(sf_acb, vgmstream, waveid, port, is_memory);
load_acb_wave_info(sf_acb, vgmstream, waveid, port, is_memory, load_loops);
}
}

View file

@ -1,26 +1,36 @@
#include "meta.h"
#include "../coding/coding.h"
#include "../layout/layout.h"
#include "awc_xma_streamfile.h"
#include "../util/endianness.h"
#include "awc_streamfile.h"
#include "awc_decryption_streamfile.h"
typedef struct {
int big_endian;
int is_encrypted;
int is_music;
int is_streamed; /* implicit: streams=music, sfx=memory */
int total_subsongs;
int channels;
int sample_rate;
int codec;
int num_samples;
uint8_t codec;
int block_count;
int block_chunk;
off_t stream_offset;
size_t stream_size;
off_t vorbis_offset[VGMSTREAM_MAX_CHANNELS];
uint32_t tags_offset;
uint32_t stream_offset;
uint32_t stream_size;
uint32_t vorbis_offset[AWC_MAX_MUSIC_CHANNELS];
/* stream+music only */
uint32_t channel_hash[AWC_MAX_MUSIC_CHANNELS];
struct {
uint32_t hash_id;
int tag_count;
} stream_info[AWC_MAX_MUSIC_CHANNELS];
} awc_header;
static int parse_awc_header(STREAMFILE* sf, awc_header* awc);
@ -28,17 +38,30 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc);
static layered_layout_data* build_layered_awc(STREAMFILE* sf, awc_header* awc);
/* AWC - from RAGE (Rockstar Advanced Game Engine) audio [Red Dead Redemption, Max Payne 3, GTA5 (multi)] */
/* AWC - Audio Wave Container from RAGE (Rockstar Advanced Game Engine) [GTA5 (multi), Red Dead Redemption (multi), Max Payne 3 (multi)] */
VGMSTREAM* init_vgmstream_awc(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* sf_body = NULL;
awc_header awc = {0};
/* checks */
if (!check_extensions(sf,"awc"))
goto fail;
if (!parse_awc_header(sf, &awc))
goto fail;
return NULL;
if (!check_extensions(sf,"awc"))
return NULL;
if (awc.is_encrypted) {
/* seen in GTA5 PC, music or sfx (not all files) */
sf_body = setup_awcd_streamfile(sf, awc.stream_offset, awc.stream_size, awc.block_chunk);
if (!sf_body) {
vgm_logi("AWC: encrypted data found, needs .awckey\n");
goto fail;
}
}
else {
sf_body = sf;
}
/* build the VGMSTREAM */
@ -55,7 +78,7 @@ VGMSTREAM* init_vgmstream_awc(STREAMFILE* sf) {
switch(awc.codec) {
case 0x00: /* PCM (PC) sfx, very rare, lower sample rates? [Max Payne 3 (PC)] */
case 0x01: /* PCM (PC/PS3) sfx, rarely */
if (awc.is_music) goto fail; /* blocked_awc needs to be prepared */
if (awc.is_streamed) goto fail; /* blocked_awc needs to be prepared */
vgmstream->coding_type = awc.big_endian ? coding_PCM16BE : coding_PCM16LE;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x02;
@ -63,75 +86,33 @@ VGMSTREAM* init_vgmstream_awc(STREAMFILE* sf) {
case 0x04: /* IMA (PC) */
vgmstream->coding_type = coding_AWC_IMA;
vgmstream->layout_type = awc.is_music ? layout_blocked_awc : layout_none;
vgmstream->layout_type = awc.is_streamed ? layout_blocked_awc : layout_none;
vgmstream->full_block_size = awc.block_chunk;
vgmstream->codec_endian = awc.big_endian;
break;
#ifdef VGM_USE_FFMPEG
case 0x05: { /* XMA2 (X360) */
uint32_t substream_size, substream_offset;
if (awc.is_music) {
/* 1ch XMAs in blocks, we'll use layered layout + custom IO to get multi-FFmpegs working */
int i;
layered_layout_data * data = NULL;
/* init layout */
data = init_layout_layered(awc.channels);
if (!data) goto fail;
vgmstream->layout_data = data;
if (awc.is_streamed) {
vgmstream->layout_data = build_layered_awc(sf_body, &awc);
if (!vgmstream->layout_data) goto fail;
vgmstream->layout_type = layout_layered;
vgmstream->coding_type = coding_FFmpeg;
/* open each layer subfile */
for (i = 0; i < awc.channels; i++) {
STREAMFILE* temp_sf = NULL;
int layer_channels = 1;
/* build the layer VGMSTREAM */
data->layers[i] = allocate_vgmstream(layer_channels, 0);
if (!data->layers[i]) goto fail;
data->layers[i]->meta_type = meta_AWC;
data->layers[i]->coding_type = coding_FFmpeg;
data->layers[i]->layout_type = layout_none;
data->layers[i]->sample_rate = awc.sample_rate;
data->layers[i]->num_samples = awc.num_samples;
/* setup custom IO streamfile, pass to FFmpeg and hope it's fooled */
temp_sf = setup_awc_xma_streamfile(sf, awc.stream_offset, awc.stream_size, awc.block_chunk, awc.channels, i);
if (!temp_sf) goto fail;
substream_offset = 0x00; /* where FFmpeg thinks data starts, which our custom sf will clamp */
substream_size = get_streamfile_size(temp_sf); /* data of one XMA substream without blocks */
data->layers[i]->codec_data = init_ffmpeg_xma2_raw(temp_sf, substream_offset, substream_size, awc.num_samples, layer_channels, awc.sample_rate, 0, 0);
if (data->layers[i])
xma_fix_raw_samples(data->layers[i], temp_sf, substream_offset, substream_size, 0, 0,0); /* samples are ok? */
close_streamfile(temp_sf);
if (!data->layers[i]->codec_data) goto fail;
}
/* setup layered VGMSTREAMs */
if (!setup_layout_layered(data))
goto fail;
}
else {
/* regular XMA for sfx */
vgmstream->codec_data = init_ffmpeg_xma2_raw(sf, awc.stream_offset, awc.stream_size, awc.num_samples, awc.channels, awc.sample_rate, 0, 0);
vgmstream->codec_data = init_ffmpeg_xma2_raw(sf_body, awc.stream_offset, awc.stream_size, awc.num_samples, awc.channels, awc.sample_rate, 0, 0);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
xma_fix_raw_samples(vgmstream, sf, awc.stream_offset,awc.stream_size, 0, 0,0); /* samples are ok? */
xma_fix_raw_samples(vgmstream, sf_body, awc.stream_offset,awc.stream_size, 0, 0,0); /* samples are ok? */
}
break;
}
#endif
#ifdef VGM_USE_MPEG
case 0x07: { /* MPEG (PS3) */
mpeg_custom_config cfg = {0};
@ -139,17 +120,18 @@ VGMSTREAM* init_vgmstream_awc(STREAMFILE* sf) {
cfg.chunk_size = awc.block_chunk;
cfg.big_endian = awc.big_endian;
vgmstream->codec_data = init_mpeg_custom(sf, awc.stream_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_AWC, &cfg);
vgmstream->codec_data = init_mpeg_custom(sf_body, awc.stream_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_AWC, &cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->layout_type = layout_none;
break;
}
#endif
#ifdef VGM_USE_VORBIS
case 0x08: { /* Vorbis (PC) [Red Dead Redemption 2 (PC)] */
if (awc.is_music) {
vgmstream->layout_data = build_layered_awc(sf, &awc);
case 0x08: { /* Vorbis (PC) [Red Dead Redemption 2 (PC)] */
if (awc.is_streamed) {
vgmstream->layout_data = build_layered_awc(sf_body, &awc);
if (!vgmstream->layout_data) goto fail;
vgmstream->layout_type = layout_layered;
vgmstream->coding_type = coding_VORBIS_custom;
@ -161,7 +143,7 @@ VGMSTREAM* init_vgmstream_awc(STREAMFILE* sf) {
cfg.sample_rate = awc.sample_rate;
cfg.header_offset = awc.vorbis_offset[0];
vgmstream->codec_data = init_vorbis_custom(sf, awc.stream_offset, VORBIS_AWC, &cfg);
vgmstream->codec_data = init_vorbis_custom(sf_body, awc.stream_offset, VORBIS_AWC, &cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->layout_type = layout_none;
vgmstream->coding_type = coding_VORBIS_custom;
@ -169,40 +151,116 @@ VGMSTREAM* init_vgmstream_awc(STREAMFILE* sf) {
break;
}
#endif
#ifdef VGM_USE_ATRAC9
case 0x0F: { /* ATRAC9 (PC) [Red Dead Redemption (PS4)] */
if (awc.is_streamed) {
vgmstream->layout_data = build_layered_awc(sf_body, &awc);
if (!vgmstream->layout_data) goto fail;
vgmstream->layout_type = layout_layered;
vgmstream->coding_type = coding_ATRAC9;
}
else {
VGMSTREAM* temp_vs = NULL;
STREAMFILE* temp_sf = NULL;
temp_sf = setup_subfile_streamfile(sf_body, awc.stream_offset, awc.stream_size, "at9");
if (!temp_sf) goto fail;
temp_vs = init_vgmstream_riff(temp_sf);
close_streamfile(temp_sf);
if (!temp_vs) goto fail;
temp_vs->num_streams = vgmstream->num_streams;
temp_vs->stream_size = vgmstream->stream_size;
temp_vs->meta_type = vgmstream->meta_type;
strcpy(temp_vs->stream_name, vgmstream->stream_name);
close_vgmstream(vgmstream);
//vgmstream = temp_vs;
return temp_vs;
}
break;
}
#endif
case 0x0C: /* DSP-sfx (Switch) */
case 0x10: /* DSP-music (Switch) */
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->layout_type = awc.is_streamed ? layout_blocked_awc : layout_none;
vgmstream->full_block_size = awc.block_chunk;
if (!awc.is_streamed) {
/* dsp header */
dsp_read_coefs_le(vgmstream, sf_body, awc.stream_offset + 0x1c + 0x00, 0x00);
dsp_read_hist_le (vgmstream, sf_body, awc.stream_offset + 0x1c + 0x20, 0x00);
awc.stream_offset += 0x60;
/* shouldn't be possible since it's only used for sfx anyway */
if (awc.channels > 1)
goto fail;
}
break;
case 0xFF:
vgmstream->coding_type = coding_SILENCE;
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "[%s]", "midi");
break;
default:
VGM_LOG("AWC: unknown codec 0x%02x\n", awc.codec);
goto fail;
}
if (!vgmstream_open_stream(vgmstream, sf, awc.stream_offset))
if (!vgmstream_open_stream(vgmstream, sf_body, awc.stream_offset))
goto fail;
if (sf_body != sf) close_streamfile(sf_body);
return vgmstream;
fail:
if (sf_body != sf) close_streamfile(sf_body);
close_vgmstream(vgmstream);
return NULL;
}
/* Parse Rockstar's AWC header (much info from LibertyV: https://github.com/koolkdev/libertyv).
* Made of entries for N streams, each with a number of tags pointing to chunks (header, data, events, etc). */
*
* AWC defines logical streams/tracks, each with N tags (type+offset+size) that point to headers/tables with info.
* First stream may be a "music" type, then other streams are used as channels and not always define tags.
* When the "stream" flag is set data is divided into "blocks" (used for music), described later.
* Streams are ordered by hash/id and its tags go in order, but data may be unordered (1st stream audio
* or headers could go after others). Defined streams also may be unused/dummy.
* Hashes are actually reversable and more or less stream names (see other tools).
*
* Rough file format:
* - base header
* - stream tag starts [optional]
* - stream hash ids and tag counts (stream N has M tags)
* - tags per stream
* - data from tags (headers, tables, audio data, etc)
*/
static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
uint64_t (*read_u64)(off_t,STREAMFILE*) = NULL;
uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL;
uint16_t (*read_u16)(off_t,STREAMFILE*) = NULL;
int i, ch, entries;
uint32_t flags, info_header, tag_count = 0, tags_skip = 0;
off_t offset;
read_u64_t read_u64 = NULL;
read_u32_t read_u32 = NULL;
read_u16_t read_u16 = NULL;
int entries;
uint32_t flags, tag_count = 0, tags_skip = 0;
uint32_t offset;
int target_subsong = sf->stream_index;
/** base header **/
if (is_id32be(0x00,sf,"ADAT")) {
awc->big_endian = false;
}
else if (is_id32be(0x00,sf,"TADA")) {
awc->big_endian = true;
}
else {
return false;
}
/* check header */
if (read_u32be(0x00,sf) != 0x41444154 && /* "ADAT" (LE) */
read_u32be(0x00,sf) != 0x54414441) /* "TADA" (BE) */
goto fail;
awc->big_endian = read_u32be(0x00,sf) == 0x54414441;
if (awc->big_endian) {
read_u64 = read_u64be;
read_u32 = read_u32be;
@ -213,74 +271,112 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
read_u16 = read_u16le;
}
flags = read_u32(0x04,sf);
entries = read_u32(0x08,sf);
//header_size = read_u32(0x0c,sf); /* after to stream id/tags, not including chunks */
//header_size = read_u32(0x0c,sf); /* after stream id+tags */
offset = 0x10;
/* flags = 8b (always FF) + 8b (actual flags) + 16b (version, 00=rarely, 01=common) */
if ((flags & 0xFF00FFFF) != 0xFF000001 || (flags & 0x00F00000)) {
VGM_LOG("AWC: unknown flags 0x%08x\n", flags);
goto fail;
}
if (flags & 0x00010000) /* some kind of mini offset table */
/* stream tag starts (ex. stream#0 = 0, stream#1 = 4, stream#2 = 7: to read tags from stream#2 skip to 7th tag) */
if (flags & 0x00010000)
offset += 0x2 * entries;
//if (flags % 0x00020000) /* seems to indicate chunks are not ordered (ie. header may go after data) */
// ...
//if (flags % 0x00040000) /* music/multichannel flag? (GTA5, not seen in RDR) */
// awc->is_music = 1;
if (flags & 0x00080000) /* encrypted data chunk (most of GTA5 PC) */
/* seems to indicate chunks are not ordered (ie. header structures from tags may go after data), usually for non-streams */
//if (flags % 0x00020000)
// awc->is_unordered = 1;
/* stream/multichannel flag (rare, GTA5/RDR2) */
//if (flags % 0x00040000)
// awc->is_multichannel = 1;
/* encrypted data chunk (most of GTA5 PC for licensed audio) */
if (flags & 0x00080000)
awc->is_encrypted = 1;
if (awc->is_encrypted) {
VGM_LOG("AWC: encrypted data found\n");
goto fail;
}
/* Music when the first id is 0 (base/fake entry with info for all channels), sfx pack otherwise.
* sfx = N single streams, music = N-1 interleaved mono channels (even for MP3/XMA).
* Music seems layered (N-1/2 stereo pairs), maybe set with events? */
awc->is_music = (read_u32(offset + 0x00,sf) & 0x1FFFFFFF) == 0x00000000;
if (awc->is_music) { /* all streams except id 0 is a channel */
/* When first stream hash/id is 0 AWC it has fake entry with info for all channels = music, sfx pack otherwise.
* sfx = N single streams, music = N interleaved mono channels (even for MP3/XMA/Vorbis/etc).
* Channels set a stream hash/id that typically is one of the defined ones and its tags do apply to that
* channel, but rarely may not exist. Ex.:
*
* - bgm01.awc
* Stream ID 00000000 (implicit: music stream, all others aren't used)
* Tag: music header
* Channel 0: ID 9d66fe4c
* Channel 1: ID 7a3837ef
* Channel 2: ID 032c57e9 (not actually defined)
* Tag: data chunk
* #Tag: sfx header (only in buggy files)
* Stream ID 7a3837ef (no tags)
* Stream ID 9d66fe4c (notice this is channel 0 but streams are ordered by hash)
* Tag: Event config
*
* - sfx01.awc
* Stream ID 9d66fe4c
* Tag: sfx header
* Tag: data chunk
* Stream ID 7a3837ef
* Tag: sfx header
* Tag: data chunk
*
* Music 'stream' defines it's own (streamed/blocked) data chunk, so other stream's data or headers aren't used,
* but in rare cases they actually define a useless sfx header or even a separate cloned data chunk. That seems
* to be a bug and are ignored (ex. RDR's ftr_harmonica_01, or RDR SW's countdown_song_01).
*/
awc->is_streamed = (read_u32(offset + 0x00,sf) & 0x1FFFFFFF) == 0x00000000; /* first stream's hash/id is 0 */
if (awc->is_streamed) { /* music with N channels, other streams aren't used ignored */
awc->total_subsongs = 1;
target_subsong = 1; /* we only need id 0, though channels may have its own tags/chunks */
target_subsong = 1;
/* array access below */
if (entries >= AWC_MAX_MUSIC_CHANNELS)
goto fail;
}
else { /* each stream is a single sound */
else { /* sfx pack, each stream is a sound */
awc->total_subsongs = entries;
if (target_subsong == 0) target_subsong = 1;
if (target_subsong < 0 || target_subsong > awc->total_subsongs || awc->total_subsongs < 1) goto fail;
}
/* get stream base info */
for (i = 0; i < entries; i++) {
info_header = read_u32(offset + 0x04*i, sf);
tag_count = (info_header >> 29) & 0x7; /* 3b */
//id = (info_header >> 0) & 0x1FFFFFFF; /* 29b */
if (target_subsong-1 == i)
break;
tags_skip += tag_count; /* tags to skip to reach target's tags, in the next header */
/** stream ids and tag counts **/
for (int i = 0; i < entries; i++) {
uint32_t info_header = read_u32(offset + 0x00, sf);
int entry_count = (info_header >> 29) & 0x7; /* 3b */
uint32_t hash_id = (info_header >> 0) & 0x1FFFFFFF; /* 29b */
if (i + 1 < target_subsong)
tags_skip += entry_count; /* tags to skip to reach target's tags, in the next header */
if (target_subsong == i + 1)
tag_count = entry_count;
if (awc->is_streamed) {
awc->stream_info[i].hash_id = hash_id;
awc->stream_info[i].tag_count = entry_count;
}
offset += 0x04;
}
offset += 0x04*entries;
offset += 0x08*tags_skip;
awc->tags_offset = offset; /* where tags for stream start */
/* get stream tags */
for (i = 0; i < tag_count; i++) {
uint64_t tag_header;
uint8_t tag_type;
size_t tag_size;
off_t tag_offset;
offset += 0x08 * tags_skip; /* ignore tags for other streams */
tag_header = read_u64(offset + 0x08*i,sf);
tag_type = (uint8_t)((tag_header >> 56) & 0xFF); /* 8b */
tag_size = (size_t)((tag_header >> 28) & 0x0FFFFFFF); /* 28b */
tag_offset = (off_t)((tag_header >> 0) & 0x0FFFFFFF); /* 28b */
;VGM_LOG("AWC: tag%i/%i at %lx: t=%x, o=%lx, s=%x\n", i, tag_count, offset + 0x08*i, tag_type, tag_offset, tag_size);
/* Tags are apparently part of a hash derived from a word ("data", "format", etc).
* If music + 1ch, the header and data chunks can repeat for no reason (sometimes not even pointed). */
/** tags per stream **/
for (int i = 0; i < tag_count; i++) {
uint64_t tag_header = read_u64(offset + 0x08*i,sf);
uint8_t tag_type = ((tag_header >> 56) & 0xFF); /* 8b */
uint32_t tag_size = ((tag_header >> 28) & 0x0FFFFFFF); /* 28b */
uint32_t tag_offset = ((tag_header >> 0) & 0x0FFFFFFF); /* 28b */
/* types are apparently part of a hash derived from a word ("data", "format", etc). */
switch(tag_type) {
case 0x55: /* data */
awc->stream_offset = tag_offset;
@ -288,30 +384,31 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
break;
case 0x48: /* music header */
if (!awc->is_music) {
VGM_LOG("AWC: music header found in sfx\n");
if (!awc->is_streamed) {
VGM_LOG("AWC: music header found but not streamed\n");
goto fail;
}
/* 0x00(32): unknown (some count?) */
awc->block_count = read_u32(tag_offset + 0x00,sf);
awc->block_chunk = read_u32(tag_offset + 0x04,sf);
awc->channels = read_u32(tag_offset + 0x08,sf);
awc->channels = read_u32(tag_offset + 0x08,sf);
if (awc->channels != entries - 1) { /* not counting id-0 */
VGM_LOG("AWC: number of music channels doesn't match entries\n");
goto fail;
/* extremely rare but doesn't seem to matter, some streams are dummies (RDR2 STREAMS/ABIGAIL_HUMMING_*) */
//goto fail;
}
for (ch = 0; ch < awc->channels; ch++) {
for (int ch = 0; ch < awc->channels; ch++) {
int num_samples, sample_rate, codec;
/* 0x00): stream id (not always in the header entries order) */
awc->channel_hash[ch] = read_u32(tag_offset + 0x0c + 0x10*ch + 0x00,sf); /* reference, for vorbis */
num_samples = read_u32(tag_offset + 0x0c + 0x10*ch + 0x04,sf);
/* 0x08: headroom */
sample_rate = read_u16(tag_offset + 0x0c + 0x10*ch + 0x0a,sf);
codec = read_u8(tag_offset + 0x0c + 0x10*ch + 0x0c,sf);
/* 0x0d(8): round size? */
/* 0x0e: unknown (zero/-1) */
/* 0x0e: unknown (zero/-1, loop flag? BOB_FINALE_1_A.awc, but also set in stingers) */
/* validate channels differences */
if ((awc->num_samples && !(awc->num_samples >= num_samples - 10 && awc->num_samples <= num_samples + 10)) ||
@ -336,77 +433,119 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
break;
case 0xFA: /* sfx header */
if (awc->is_music) {
VGM_LOG("AWC: sfx header found in music\n");
goto fail;
if (awc->is_streamed) {
VGM_LOG("AWC: sfx header found but streamed\n");
break; //goto fail; /* rare (RDR PC/Switch) */
}
awc->num_samples = read_u32(tag_offset + 0x00,sf);
/* 0x04: -1? */
awc->sample_rate = read_u16(tag_offset + 0x08,sf);
/* 0x0a: unknown x4 */
/* 0x0a: headroom */
/* 0x0c: unknown */
/* 0x0e: unknown */
/* 0x10: unknown */
/* 0x12: null? */
awc->codec = read_u8(tag_offset + 0x13, sf);
/* 0x14: ? (PS3 only, for any codec) */
awc->channels = 1;
break;
case 0x76: /* sfx header for vorbis */
if (awc->is_music) {
VGM_LOG("AWC: sfx header found in music\n");
if (awc->is_streamed) {
VGM_LOG("AWC: sfx header found but streamed\n");
goto fail;
}
awc->num_samples = read_u32(tag_offset + 0x00,sf);
/* 0x04: -1? */
awc->sample_rate = read_u16(tag_offset + 0x08,sf);
/* 0x0a: granule start? (negative) */
/* 0x0c: granule max? */
/* 0x0a: headroom */
/* 0x0c: unknown */
/* 0x0e: unknown */
/* 0x10: unknown */
awc->codec = read_u8(tag_offset + 0x1c, sf); /* 16b? */
/* 0x1e: vorbis header size */
awc->channels = 1;
/* 0x1e: vorbis setup size */
if (read_u16(tag_offset + 0x1e,sf))/* rarely not set and uses a tag below */
awc->vorbis_offset[0] = tag_offset + 0x20; /* data up to vorbis setup size */
awc->vorbis_offset[0] = tag_offset + 0x20;
awc->channels = 1;
break;
case 0xA3: /* block-to-sample table (32b x number of blocks w/ num_samples at the start of each block) */
case 0x7F: /* vorbis setup */
if (awc->is_streamed) {
/* music stream doesn't have this (instead every channel-strem have one, parsed later) */
VGM_LOG("AWC: vorbis setup found but streamed\n");
goto fail;
}
/* very rarely used for sfx: SS_AM/GESTURE01.awc */
awc->vorbis_offset[0] = tag_offset;
break;
case 0x68: /* midi data [Red Dead Redemption 2 (PC)] */
/* set fake info so awc doesn't break */
awc->stream_offset = tag_offset;
awc->stream_size = tag_size;
awc->num_samples = 48000;
awc->sample_rate = 48000;
awc->codec = 0xFF;
awc->channels = 1;
break;
case 0xA3: /* block-to-sample table (32b x number of blocks w/ num_samples at the start of each block)
* or frame-size table (16b x number of frames) in some cases (ex. sfx+mpeg but not sfx+vorbis) */
case 0xBD: /* events (32bx4): type_hash, params_hash, timestamp_ms, flags */
case 0x5C: /* animation/RSC config? */
default: /* 0x68=midi?, 0x36=hash thing?, 0x2B=sizes, 0x5A/0xD9=? */
case 0x5C: /* animation/RSC info? */
case 0x81: /* animation/CSR info? */
case 0x36: /* list of hash-things? */
case 0x2B: /* events/sizes? */
default: /* 0x68=midi?, 0x5A/0xD9=? */
//VGM_LOG("AWC: ignoring unknown tag 0x%02x\n", tag);
break;
}
}
/* in music mode there tags for other streams we don't use, except for vorbis. streams have vorbis setup info for channels, but
* channel<>stream order doesn't match, so we need to assign setup to channels. All setups seem to be the same though. */
if (awc->is_streamed && awc->codec == 0x08) {
offset = awc->tags_offset;
offset += 0x08 * awc->stream_info[0].tag_count; /* ignore 1st/music stream */
for (int stream = 1; stream < entries; stream++) {
for (int tag = 0; tag < awc->stream_info[stream].tag_count; tag++) {
uint64_t tag_header = read_u64(offset,sf);
uint8_t tag_type = ((tag_header >> 56) & 0xFF); /* 8b */
//uint32_t tag_size = ((tag_header >> 28) & 0x0FFFFFFF); /* 28b */
uint32_t tag_offset = ((tag_header >> 0) & 0x0FFFFFFF); /* 28b */
switch(tag_type) {
case 0x7f: /* vorbis setup */
/* find which channel uses this stream's data */
for (int ch = 0; ch < awc->channels; ch++) {
if (awc->channel_hash[ch] == awc->stream_info[stream].hash_id) {
awc->vorbis_offset[ch] = tag_offset;
//awc->vorbis_size[ch] = tag_size; /* not needed (implicit)*/
break;
}
}
break;
default:
break;
}
offset += 0x08;
}
}
}
if (!awc->stream_offset) {
VGM_LOG("AWC: stream offset not found\n");
goto fail;
}
/* vorbis offset table, somehow offsets are unordered and can go before tags */
if (awc->is_music && awc->codec == 0x08) {
offset += 0x08 * tag_count;
for (ch = 0; ch < awc->channels; ch++) {
awc->vorbis_offset[ch] = read_u16(offset + 0x08*ch + 0x00, sf);
/* 0x02: always 0xB000? */
/* 0x04: always 0x00CD? */
/* 0x06: always 0x7F00? */
}
}
/* In music mode, data is divided into blocks of block_chunk size with padding.
* Each block has a header/seek table and interleaved data for all channels */
{
int32_t seek_start = read_u32(awc->stream_offset, sf); /* -1 in later (RDR2) versions */
if (awc->is_music && !(seek_start == 0 || seek_start == -1)) {
VGM_LOG("AWC: music found, but block doesn't start with seek table at %x\n", (uint32_t)awc->stream_offset);
goto fail;
}
}
return 1;
fail:
return 0;
@ -414,140 +553,113 @@ fail:
/* ************************************************************************* */
//TODO: this method won't work properly, needs internal handling of blocks.
//
// This setups a decoder per block, but seems Vorbis' uses first frame as setup so it
// returns samples (576 vs 1024), making num_samples count in each block being off + causing
// gaps. So they must be using a single encoder + setting decode_to_discard per block
// to ge the thing working.
//
// However since blocks are probably also used for seeking, maybe they aren't resetting
// the decoder when seeking? or they force first frame to be 1024?
//
// In case of Vorvis, when setting skip samples seems repeated data from last block is
// exactly last 0x800 bytes of that channel.
static VGMSTREAM* build_block_vgmstream(STREAMFILE* sf, awc_header* awc, int channel, int32_t num_samples, int32_t skip_samples, off_t block_start, size_t block_size) {
STREAMFILE* temp_sf = NULL;
static VGMSTREAM* build_blocks_vgmstream(STREAMFILE* sf, awc_header* awc, int channel) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* temp_sf = NULL;
int block_channels = 1;
uint32_t substream_size, substream_offset;
/* setup custom IO streamfile that removes AWC's odd blocks (not perfect but serviceable) */
temp_sf = setup_awc_streamfile(sf, awc->stream_offset, awc->stream_size, awc->block_chunk, awc->channels, channel, awc->codec, awc->big_endian);
if (!temp_sf) goto fail;
substream_offset = 0x00;
substream_size = get_streamfile_size(temp_sf);
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(block_channels, 0);
if (!vgmstream) goto fail;
vgmstream->sample_rate = awc->sample_rate;
vgmstream->num_samples = num_samples - skip_samples;
vgmstream->stream_size = block_size;
vgmstream->meta_type = meta_AWC;
vgmstream->sample_rate = awc->sample_rate;
vgmstream->num_samples = awc->num_samples;
vgmstream->stream_size = awc->stream_size;
vgmstream->stream_size = substream_size;
switch(awc->codec) {
#ifdef VGM_USE_FFMPEG
case 0x05: { /* XMA2 (X360) */
vgmstream->codec_data = init_ffmpeg_xma2_raw(temp_sf, substream_offset, substream_size, awc->num_samples, block_channels, awc->sample_rate, 0, 0);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
xma_fix_raw_samples(vgmstream, temp_sf, substream_offset, substream_size, 0, 0,0); /* samples are ok? */
break;
}
#endif
#ifdef VGM_USE_VORBIS
case 0x08: { /* Vorbis (PC) [Red Dead Redemption 2 (PC)] */
case 0x08: {
vorbis_custom_config cfg = {0};
cfg.channels = 1;
cfg.sample_rate = awc->sample_rate;
cfg.header_offset = awc->vorbis_offset[channel];
//cfg.skip_samples = skip_samples; //todo
cfg.header_offset = awc->vorbis_offset[channel]; /* setup page goes separate */
vgmstream->codec_data = init_vorbis_custom(sf, block_start, VORBIS_AWC, &cfg);
/* note that it needs sf on init to read the header + start offset for later, and temp_sf on decode to read data */
vgmstream->codec_data = init_vorbis_custom(sf, substream_offset, VORBIS_AWC, &cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->layout_type = layout_none;
vgmstream->coding_type = coding_VORBIS_custom;
break;
}
#endif
#ifdef VGM_USE_ATRAC9
case 0x0F: {
atrac9_config cfg = {0};
/* read from first block (all blocks have it but same thing), see awc_streamfile.h */
uint32_t extradata_offset = awc->stream_offset + 0x10 * awc->channels + 0x70 * channel + 0x0c;
cfg.channels = block_channels;
cfg.encoder_delay = 0; //?
cfg.config_data = read_u32be(extradata_offset, sf);
vgmstream->codec_data = init_atrac9(&cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_ATRAC9;
vgmstream->layout_type = layout_none;
break;
}
break;
#endif
default:
goto fail;
}
if (!vgmstream_open_stream(vgmstream, sf, block_start))
if (!vgmstream_open_stream(vgmstream, temp_sf, substream_offset))
goto fail;
close_streamfile(temp_sf);
return vgmstream;
fail:
;VGM_LOG("AWB: can't open decoder\n");
close_vgmstream(vgmstream);
close_streamfile(temp_sf);
close_vgmstream(vgmstream);
return NULL;
}
static VGMSTREAM* build_blocks_vgmstream(STREAMFILE* sf, awc_header* awc, int channel) {
VGMSTREAM* vgmstream = NULL;
segmented_layout_data* data = NULL;
int i, ch;
int blocks = awc->stream_size / awc->block_chunk + (awc->stream_size % awc->block_chunk ? 1 : 0) ;
/* init layout */
data = init_layout_segmented(blocks);
if (!data) goto fail;
/* one segment per block of this channel */
for (i = 0; i < blocks; i++) {
off_t block_offset = awc->stream_offset + i * awc->block_chunk;
int32_t num_samples = 0, skip_samples = 0;
uint32_t header_skip = 0, block_skip = 0, block_start = 0, block_data = 0;
/* read stupid block crap to get proper offsets and whatnot, format:
* - per channel: number of channel entries + skip samples + num samples
* - per channel: seek table with N entries */
for (ch = 0; ch < awc->channels; ch++) {
/* 0x00: -1 */
int entries = read_u32le(block_offset + 0x18 * ch + 0x04, sf);
int32_t entry_skip = read_u32le(block_offset + 0x18 * ch + 0x08, sf);
int32_t entry_samples = read_u32le(block_offset + 0x18 * ch + 0x0c, sf);
if (ch == channel) {
num_samples = entry_samples;
skip_samples = entry_skip;
block_start = block_offset + block_skip;
block_data = entries * 0x800;
}
header_skip += 0x18 + entries * 0x04;
block_skip += entries * 0x800;
}
if (!block_start)
goto fail;
header_skip = align_size_to_block(header_skip, 0x800);
block_start += header_skip;
//;VGM_LOG("AWC: build ch%i, block=%i at %lx, o=%x, s=%x, ns=%i, ss=%i\n", channel, i, block_offset, block_start, block_data, num_samples, skip_samples);
data->segments[i] = build_block_vgmstream(sf, awc, channel, num_samples, skip_samples, block_start, block_data);
if (!data->segments[i]) goto fail;
}
/* setup VGMSTREAMs */
if (!setup_layout_segmented(data))
goto fail;
/* build the layout VGMSTREAM */
vgmstream = allocate_segmented_vgmstream(data, 0, 0, 0);
if (!vgmstream) goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
if (!vgmstream)
free_layout_segmented(data);
return NULL;
}
/* ************************************************************************* */
/* Make layers per channel for AWC's abhorrent blocks.
/* Make layers per channel for AWC's abhorrent blocks (see read_awb_block).
*
* File has N channels = N streams, that use their own mono decoder.
* Each block then has header + seek table for all channels. But in each block there is
* a "skip samples" value per channel, and blocks repeat some data from last block
* for this, so PCM must be discarded. Also, channels in a block don't need to have
* the same number of samples.
* A "music" .awc has N channels = N streams (each using their own mono decoder) chunked in "blocks".
* Each block then has header + seek table + etc for all channels. But when blocks change, each channel
* may have a "skip samples" value and blocks repeat some data from last block, so output PCM must be
* discarded to avoid channels desyncing. Channels in a block don't need to have the same number of samples.
* (mainly seen in MPEG).
*/
//TODO: this method won't fully work, needs feed decoder + block handler that interacts with decoder(s?)
// (doesn't use multiple decoders since default encoder delay in Vorbis would discard too much per block)
//
// When blocks change presumably block handler needs to tell decoder to finish decoding all from prev block
// then skip samples from next decodes. Also since samples may vary per channel, each would handle blocks
// independently.
//
// This can be simulated by making one decoder per block (segmented, but opens too many SFs and can't skip
// samples correctly), or with a custom STREAMFILE that skips repeated block (works ok-ish but not all codecs).
static layered_layout_data* build_layered_awc(STREAMFILE* sf, awc_header* awc) {
int i;
layered_layout_data* data = NULL;
@ -571,4 +683,3 @@ fail:
free_layout_layered(data);
return NULL;
}

View file

@ -0,0 +1,128 @@
#ifndef _AWC_DECRYPTION_STREAMFILE_H_
#define _AWC_DECRYPTION_STREAMFILE_H_
#include <stdlib.h>
#include "../streamfile.h"
#include "../util/cipher_xxtea.h"
#include "../util/companion_files.h"
#include "../util.h"
#define MAX_BLOCK_SIZE 0x6e4000 /* usually 0x80000, observed max for Nch files ~= 8MB */
/* decrypts xxtea blocks */
typedef struct {
uint32_t data_offset; /* where encryption data starts */
uint32_t data_size; /* encrypted size */
uint32_t block_size; /* xxtea block chunk size (big) */
uint32_t key[4]; /* decryption key */
uint8_t* buf; /* decrypted block */
uint32_t read_offset; /* last read offset (aligned to data_offset + block_size) */
} awcd_io_data;
static int awcd_io_init(STREAMFILE* sf, awcd_io_data* data) {
/* ktsr keys start with size then random bytes (usually 7), assumed max 0x20 */
data->buf = malloc(data->block_size);
if (!data->buf)
return -1;
return 0;
}
static void awcd_io_close(STREAMFILE* sf, awcd_io_data* data) {
free(data->buf);
}
/* reads from current block; offset/length must be within data_offset + data_size (handled externally) */
static int read_block(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length, awcd_io_data* data) {
/* detect if we requested offset falls within current decrypted block, otherwise read + decrypt */
off_t block_offset = (offset - data->data_offset) / data->block_size * data->block_size + data->data_offset; /* closest block */
int block_read = clamp_u32(data->block_size, 0, data->data_size - (block_offset - data->data_offset)); /* last block can be smaller */
if (data->read_offset != block_offset) {
int bytes = read_streamfile(data->buf, block_offset, block_read, sf);
if (bytes != block_read)
return 0;
xxtea_decrypt(data->buf, block_read, data->key);
data->read_offset = block_offset;
}
int buf_pos = offset - block_offset; /* within current block */
int to_do = clamp_u32(length, 0, block_read - buf_pos);
memcpy(dest, data->buf + buf_pos, to_do);
return to_do;
}
/* xxtea works with big chunks, so depending on requested offset read into buf + decrypt + copy */
static size_t awcd_io_read(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length, awcd_io_data* data) {
size_t total_bytes = 0;
while (length > 0) {
int bytes;
if (offset < data->data_offset) {
int to_do = clamp_u32(length, 0, data->data_offset - offset);
bytes = read_streamfile(dest, offset, to_do, sf);
}
else if (offset >= data->data_offset + data->data_size) {
int to_do = length;
bytes = read_streamfile(dest, offset, to_do, sf);
}
else {
bytes = read_block(sf, dest, offset, length, data);
}
dest += bytes;
offset += bytes;
length -= bytes;
total_bytes += bytes;
/* may be smaller than expected when reading between blocks but shouldn't be 0 */
if (bytes == 0)
break;
}
return total_bytes;
}
/* decrypts AWC blocks (seen in GTA5 PC) using .awckey + xxtea algorithm (only for target subsong).
*
* Reversed from OpenIV.exe 4.1/2023 (see fun_007D5EA8) b/c it was easier than from GTA5.exe itself.
* OpenIV includes 2 keys, one for PC and other for probably PS4 (since other platforms aren't encrypted);
* neither seem to be found in GTA5.exe though (packed/derived?). Unlike standard xxtea OpenIV only has decryption.
* Keys must be provided externally, could autodetect but given T2 suing habits let's err on the side of caution. */
static STREAMFILE* setup_awcd_streamfile(STREAMFILE* sf, uint32_t data_offset, uint32_t data_size, uint32_t block_size) {
STREAMFILE* new_sf = NULL;
awcd_io_data io_data = {0};
uint8_t key[0x10];
size_t key_size = read_key_file(key, sizeof(key), sf);
if (key_size != sizeof(key))
goto fail;
for (int i = 0; i < sizeof(key) / 4; i++) {
io_data.key[i] = get_u32be(key + i * 0x04);
}
if (data_offset == 0 || data_size == 0)
goto fail;
if (block_size == 0) /* for non-blocked audio (small streams) */
block_size = data_size;
if (block_size > MAX_BLOCK_SIZE || (block_size % 0x04) != 0)
goto fail;
io_data.data_offset = data_offset;
io_data.data_size = data_size;
io_data.block_size = block_size;
/* setup subfile */
new_sf = open_wrap_streamfile(sf);
new_sf = open_io_streamfile_ex_f(new_sf, &io_data, sizeof(awcd_io_data), awcd_io_read, NULL, awcd_io_init, awcd_io_close);
return new_sf;
fail:
return NULL;
}
#endif

View file

@ -0,0 +1,253 @@
#ifndef _AWC_STREAMFILE_H_
#define _AWC_STREAMFILE_H_
#include "deblock_streamfile.h"
#include "../util/endianness.h"
#define AWC_MAX_MUSIC_CHANNELS 32 /* seen ~24 */
/* ************************************************************************* */
typedef struct {
int start_entry; /* innacurate! */
int entries;
int32_t channel_skip;
int32_t channel_samples;
uint32_t frame_size;
/* derived */
uint32_t chunk_start; /* relative to block offset */
uint32_t chunk_size; /* size of this channel's data (not including padding) */
} awc_block_t;
typedef struct {
int big_endian;
uint8_t codec;
int channels;
uint32_t block_offset;
awc_block_t blk[AWC_MAX_MUSIC_CHANNELS];
} awc_block_info_t;
/* Block format:
* - block header for all channels (needed to find frame start)
* - frames from channel 1
* - ...
* - frames from channel N
* - usually there is padding between channels or blocks (usually 0s but seen 0x97 in AT9)
*
* Header format:
* - per channel (frame start table)
* 0x00: start entry for that channel? (-1 in vorbis)
* may be off by +1/+2?
* ex. on block 0, ch0/1 have 0x007F frames, a start entry is: ch0=0x0000, ch1=0x007F (MP3)
* ex. on block 0, ch0/1 have 0x02A9 frames, a start entry is: ch0=0x0000, ch1=0x02AA (AT9) !!
* (sum of all values from all channels may go beyond all posible frames, no idea)
* 0x04: frames in this channel (may be different between channels)
* 'frames' here may be actual single decoder frames or a chunk of frames
* 0x08: samples to discard in the beginning of this block (MPEG/XMA2/Vorbis only?)
* 0x0c: samples in channel (for MPEG/XMA2 can vary between channels)
* full samples without removing samples to discard
* (next fields only exists for MPEG, Vorbis or some IMA versions)
* 0x10: (MPEG only, empty otherwise) close to number of frames but varies a bit?
* 0x14: (MPEG only, empty otherwise) channel chunk size (not counting padding)
* - for each channel (seek table)
* 32b * entries = global samples per frame in each block (for MPEG probably per full frame)
* (AT9 doesn't have a seek table as it's CBR)
* - per channel (ATRAC9/DSP extra info):
* 0x00: "D11A"
* 0x04: frame size
* 0x06: frame samples
* 0x08: flags? (0x0103=AT9, 0x0104=DSP)
* 0x0a: sample rate
* 0x0c: ATRAC9 config (repeated but same for all blocks) or "D11E" (DSP)
* 0x10-0x70: padding with 0x77 (ATRAC3) or standard DSP header for original full file (DSP)
* - padding until channel data start, depending on codec (DSP/ATRAC9: one, others: aligned to 0x800)
* - per channel:
* 0xNN: channel frames
* 0xNN: may have padding between channels depending on codec (mainly MPEG/XMA)
* - padding until this block's end
*/
static bool read_awb_block(STREAMFILE* sf, awc_block_info_t* bi) {
read_s32_t read_s32 = bi->big_endian ? read_s32be : read_s32le;
read_u16_t read_u16 = bi->big_endian ? read_u16be : read_u16le;
uint32_t channel_entry_size, seek_entry_size, extra_entry_size, header_padding;
uint32_t offset = bi->block_offset;
int channels = bi->channels;
/* read stupid block crap + derived info at once so hopefully it's a bit easier to understand */
switch(bi->codec) {
case 0x05: /* XMA2 */
channel_entry_size = 0x10;
seek_entry_size = 0x04;
extra_entry_size = 0x00;
header_padding = 0x800;
break;
case 0x08: /* Vorbis */
channel_entry_size = 0x18;
seek_entry_size = 0x04;
extra_entry_size = 0x00;
header_padding = 0x800;
break;
case 0x0F: /* ATRAC9 */
channel_entry_size = 0x10;
seek_entry_size = 0x00;
extra_entry_size = 0x70;
header_padding = 0x00;
break;
default:
goto fail;
}
/* channel info table */
for (int ch = 0; ch < bi->channels; ch++) {
bi->blk[ch].start_entry = read_s32(offset + 0x00, sf);
bi->blk[ch].entries = read_s32(offset + 0x04, sf);
bi->blk[ch].channel_skip = read_s32(offset + 0x08, sf);
bi->blk[ch].channel_samples = read_s32(offset + 0x0c, sf);
/* others: optional */
offset += channel_entry_size;
}
/* seek table */
for (int ch = 0; ch < channels; ch++) {
offset += bi->blk[ch].entries * seek_entry_size;
}
/* extra table and derived info */
for (int ch = 0; ch < channels; ch++) {
switch(bi->codec) {
case 0x05: /* XMA2 */
case 0x08: /* Vorbis */
/* each 'frame'/entry in Vorbis is actually N vorbis frames then padding up to 0x800
* (more or less like a big Ogg page or XMA 'frame'). Padding is considered part of
* the data and handled by the decoder, since sfx (non-blocked) algo have it. */
bi->blk[ch].frame_size = 0x800;
bi->blk[ch].chunk_size = bi->blk[ch].entries * bi->blk[ch].frame_size;
break;
case 0x0F: /* ATRAC9 */
bi->blk[ch].frame_size = read_u16(offset + 0x04, sf);
bi->blk[ch].chunk_size = bi->blk[ch].entries * bi->blk[ch].frame_size;
break;
default:
goto fail;
}
offset += extra_entry_size;
}
/* header done, move into data start */
if (header_padding) {
/* padding on the current size rather than file offset (block meant to be read into memory, probably) */
uint32_t header_size = offset - bi->block_offset;
offset = bi->block_offset + align_size_to_block(header_size, header_padding);
}
/* set frame starts per channel */
for (int ch = 0; ch < channels; ch++) {
bi->blk[ch].chunk_start = offset - bi->block_offset;
offset += bi->blk[ch].chunk_size;
}
/* beyond this is padding until chunk_start */
return true;
fail:
return false;
}
/* Find data that repeats in the beginning of a new block at the end of last block.
* When a new block starts there is some repeated data + channel_skip (for seeking + encoder delay?).
* Detect it so decoder may ignore it. */
static uint32_t get_block_repeated_size(STREAMFILE* sf, awc_block_info_t* bi, int channel) {
if (bi->blk[channel].channel_skip == 0)
return 0;
switch(bi->codec) {
case 0x05: { /* XMA2 */
const uint32_t samples_per_subframe = 512;
uint32_t samples_this_frame;
uint8_t subframes;
uint32_t offset = bi->block_offset + bi->blk[channel].chunk_start;
int repeat_samples = bi->blk[channel].channel_skip;
//TODO: fix (needs proper decoder + sample discard)
/* Repeat samples are the number of decoded samples to discard, but in this streamfile we can't do that.
* Also XMA is VBR, and may encode silent frames with up to 63 subframes yet we may have few repeat samples.
* We could find out how many subframes of 512 samples to skip, then adjust the XMA frame header, though
* output will be slightly off since subframes are related.
*
* For now just skip a full frame depending on the number of subframes vs repeat samples.
* Most files work ok-ish but channels may desync slightly. */
subframes = (read_u8(offset,sf) >> 2) & 0x3F; /* peek into frame header */
samples_this_frame = subframes * samples_per_subframe;
if (repeat_samples >= (int)(samples_this_frame * 0.13)) { /* skip mosts */
return bi->blk[channel].frame_size;
}
else {
return 0;
}
}
case 0x08: /* Vorbis */
/* when data repeats seems to clone exactly the last super-frame */
return bi->blk[channel].frame_size;
case 0x0F: /* ATRAC9 */
default:
VGM_LOG("AWC: found channel skip in codec %x\n", bi->codec); /* not seen */
return 0;
}
}
/* ************************************************************************* */
static void block_callback(STREAMFILE *sf, deblock_io_data* data) {
int channel = data->cfg.track_number;
awc_block_info_t bi = {0};
bi.big_endian = data->cfg.big_endian;
bi.block_offset = data->physical_offset;
bi.channels = data->cfg.track_count;
bi.codec = data->cfg.track_type;
if (!read_awb_block(sf, &bi))
return; //???
uint32_t repeat_size = get_block_repeated_size(sf, &bi, channel);
data->block_size = data->cfg.chunk_size;
data->skip_size = bi.blk[channel].chunk_start + repeat_size;
data->data_size = bi.blk[channel].chunk_size - repeat_size;
}
/* deblocks AWC blocks */
static STREAMFILE* setup_awc_streamfile(STREAMFILE* sf, uint32_t stream_offset, uint32_t stream_size, uint32_t block_size, int channels, int channel, uint8_t codec, int big_endian) {
STREAMFILE* new_sf = NULL;
deblock_config_t cfg = {0};
if (channels >= AWC_MAX_MUSIC_CHANNELS)
return NULL;
cfg.track_number = channel;
cfg.track_count = channels;
cfg.stream_start = stream_offset;
cfg.stream_size = stream_size;
cfg.chunk_size = block_size;
cfg.track_type = codec;
cfg.big_endian = big_endian;
//cfg.physical_offset = stream_offset;
//cfg.logical_size = awc_xma_io_size(sf, &cfg); /* force init */
cfg.block_callback = block_callback;
new_sf = open_wrap_streamfile(sf);
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg);
//new_sf = open_buffer_streamfile_f(new_sf, 0);
return new_sf;
}
#endif

View file

@ -1,114 +0,0 @@
#ifndef _AWC_XMA_STREAMFILE_H_
#define _AWC_XMA_STREAMFILE_H_
#include "deblock_streamfile.h"
static size_t get_block_header_size(STREAMFILE* sf, off_t offset, int channels);
static size_t get_repeated_data_size(STREAMFILE* sf, off_t next_offset, size_t repeat_samples);
static size_t get_block_skip_count(STREAMFILE* sf, off_t offset, int channel);
static void block_callback(STREAMFILE *sf, deblock_io_data* data) {
const size_t frame_size = 0x800;
int channel = data->cfg.track_number;
int channels = data->cfg.track_count;
/* Blocks have a header then data per channel, each with a different num_samples/frames,
* separate (first all frames of ch0, then ch1, etc), padded, and sometimes the last few
* frames of a channel are repeated in the new block (marked with "repeat samples"). */
size_t header_size = get_block_header_size(sf, data->physical_offset, channels);
/* header table entries = frames... I hope */
size_t others_size = get_block_skip_count(sf, data->physical_offset, channel) * frame_size;
//size_t skip_size = read_u32be(data->physical_offset + 0x10*channel + 0x00, sf) * frame_size;
size_t data_size = read_u32be(data->physical_offset + 0x10*channel + 0x04, sf) * frame_size;
size_t repeat_samples = read_u32be(data->physical_offset + 0x10*channel + 0x08, sf);
size_t repeat_size = 0;
data->block_size = data->cfg.chunk_size;
/* if there are repeat samples current block repeats some frames from last block, find out size */
if (repeat_samples) {
off_t data_offset = data->physical_offset + header_size + others_size;
repeat_size = get_repeated_data_size(sf, data_offset, repeat_samples);
}
data->skip_size = header_size + others_size + repeat_size;
data->data_size = data_size - repeat_size;
}
/* block header size, aligned/padded to 0x800 */
static size_t get_block_header_size(STREAMFILE* sf, off_t offset, int channels) {
size_t header_size = 0;
int i;
for (i = 0; i < channels; i++) {
header_size += 0x10;
header_size += read_u32be(offset + 0x10*i + 0x04, sf) * 0x04; /* entries in the table */
}
if (header_size % 0x800) /* padded */
header_size += 0x800 - (header_size % 0x800);
return header_size;
}
/* find data that repeats in the beginning of a new block at the end of last block */
static size_t get_repeated_data_size(STREAMFILE* sf, off_t next_offset, size_t repeat_samples) {
const size_t frame_size = 0x800;
const size_t samples_per_subframe = 512;
size_t samples_this_frame;
uint8_t subframes;
//todo: fix this
/* Repeat samples are the number of decoded samples to discard, but in this streamfile we can't do that.
* Also XMA is VBR, and may encode silent frames with up to 63 subframes yet we may have few repeat samples.
* We could find out how many subframes of 512 samples to skip, then adjust the XMA frame header, though
* output will be slightly off since subframes are related.
*
* For now just skip a full frame depending on the number of subframes vs repeat samples.
* Most files work ok-ish but channels may desync slightly. */
subframes = ((uint8_t)read_8bit(next_offset,sf) >> 2) & 0x3F; /* peek into frame header */
samples_this_frame = subframes*samples_per_subframe;
if (repeat_samples >= (int)(samples_this_frame*0.13)) { /* skip mosts */
return frame_size;
}
else {
return 0;
}
}
/* header has a skip value, but somehow it's sometimes bigger than expected (WHY!?!?) so just sum all */
static size_t get_block_skip_count(STREAMFILE* sf, off_t offset, int channel) {
size_t skip_count = 0;
int i;
//skip_size = read_u32be(offset + 0x10*channel + 0x00, sf); /* wrong! */
for (i = 0; i < channel; i++) {
skip_count += read_u32be(offset + 0x10*i + 0x04, sf); /* number of frames of this channel */
}
return skip_count;
}
/* Deblocks interleaved XMA in AWC blocks */
static STREAMFILE* setup_awc_xma_streamfile(STREAMFILE *sf, off_t stream_offset, size_t stream_size, size_t block_size, int channel_count, int channel) {
STREAMFILE *new_sf = NULL;
deblock_config_t cfg = {0};
cfg.track_number = channel;
cfg.track_count = channel_count;
cfg.stream_start = stream_offset;
cfg.stream_size = stream_size;
cfg.chunk_size = block_size;
//cfg.physical_offset = stream_offset;
//cfg.logical_size = awc_xma_io_size(sf, &cfg); /* force init */
cfg.block_callback = block_callback;
new_sf = open_wrap_streamfile(sf);
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg);
//new_sf = open_buffer_streamfile_f(new_sf, 0);
return new_sf;
}
#endif /* _AWC_XMA_STREAMFILE_H_ */

View file

@ -6,7 +6,7 @@
/* AWD - Audio Wave Dictionary (RenderWare) */
VGMSTREAM* init_vgmstream_awd(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
char header_name[STREAM_NAME_SIZE], stream_name[STREAM_NAME_SIZE];
char file_name[STREAM_NAME_SIZE], header_name[STREAM_NAME_SIZE], stream_name[STREAM_NAME_SIZE];
int /*bit_depth = 0,*/ channels = 0, sample_rate = 0, stream_codec = -1, total_subsongs = 0, target_subsong = sf->stream_index;
int interleave, loop_flag;
off_t data_offset, header_name_offset, misc_data_offset, linked_list_offset, wavedict_offset;
@ -115,7 +115,8 @@ VGMSTREAM* init_vgmstream_awd(STREAMFILE* sf) {
vgmstream->num_streams = total_subsongs;
vgmstream->interleave_block_size = interleave;
if (header_name_offset)
get_streamfile_basename(sf, file_name, STREAM_NAME_SIZE);
if (header_name_offset && strcmp(file_name, header_name))
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%s/%s", header_name, stream_name);
else
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%s", stream_name);

View file

@ -3,7 +3,7 @@
#include "../streamfile.h"
#define BGW_KEY_MAX (0xC0*2)
#define BGW_KEY_MAX (0xC0 * 2)
typedef struct {
uint8_t key[BGW_KEY_MAX];
@ -11,56 +11,48 @@ typedef struct {
} bgw_decryption_data;
/* Encrypted ATRAC3 info from Moogle Toolbox (https://sourceforge.net/projects/mogbox/) */
static size_t bgw_decryption_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, bgw_decryption_data* data) {
size_t bytes_read;
int i;
bytes_read = streamfile->read(streamfile, dest, offset, length);
static size_t bgw_decryption_read(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length, bgw_decryption_data* data) {
size_t bytes_read = sf->read(sf, dest, offset, length);
/* decrypt data (xor) */
for (i = 0; i < bytes_read; i++) {
for (int i = 0; i < bytes_read; i++) {
dest[i] ^= data->key[(offset + i) % data->key_size];
}
//todo: a few files (music069.bgw, music071.bgw, music900.bgw) have the last frames unencrypted,
// though they are blank and encoder ignores wrongly decrypted frames and outputs blank samples as well
//TODO: a few files (music069.bgw, music071.bgw, music900.bgw) have the last frames unencrypted,
// though they are blank and encoder ignores wrongly decrypted frames and outputs blank samples as well.
// Only in files that don't loop?
return bytes_read;
}
static STREAMFILE* setup_bgw_atrac3_streamfile(STREAMFILE *streamFile, off_t subfile_offset, size_t subfile_size, size_t frame_size, int channels) {
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
static STREAMFILE* setup_bgw_atrac3_streamfile(STREAMFILE* sf, off_t subfile_offset, size_t subfile_size, size_t frame_size, int channels) {
STREAMFILE* new_sf = NULL;
bgw_decryption_data io_data = {0};
size_t io_data_size = sizeof(bgw_decryption_data);
int ch;
/* setup decryption with key (first frame + modified channel header) */
if (frame_size*channels == 0 || frame_size*channels > BGW_KEY_MAX) goto fail;
size_t key_size = frame_size * channels;
if (key_size <= 0 || key_size > BGW_KEY_MAX)
goto fail;
io_data.key_size = read_streamfile(io_data.key, subfile_offset, frame_size*channels, streamFile);
for (ch = 0; ch < channels; ch++) {
uint32_t xor = get_32bitBE(io_data.key + frame_size*ch);
put_32bitBE(io_data.key + frame_size*ch, xor ^ 0xA0024E9F);
io_data.key_size = read_streamfile(io_data.key, subfile_offset, key_size, sf);
if (io_data.key_size != key_size)
goto fail;
for (int ch = 0; ch < channels; ch++) {
uint32_t xor = get_u32be(io_data.key + frame_size * ch);
put_u32be(io_data.key + frame_size * ch, xor ^ 0xA0024E9F);
}
/* setup subfile */
new_streamFile = open_wrap_streamfile(streamFile);
if (!new_streamFile) goto fail;
temp_streamFile = new_streamFile;
new_streamFile = open_clamp_streamfile(temp_streamFile, subfile_offset,subfile_size);
if (!new_streamFile) goto fail;
temp_streamFile = new_streamFile;
new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, bgw_decryption_read,NULL);
if (!new_streamFile) goto fail;
temp_streamFile = new_streamFile;
return temp_streamFile;
new_sf = open_wrap_streamfile_f(sf);
new_sf = open_clamp_streamfile_f(new_sf, subfile_offset,subfile_size);
new_sf = open_io_streamfile_f(new_sf, &io_data,io_data_size, bgw_decryption_read,NULL);
return new_sf;
fail:
close_streamfile(temp_streamFile);
close_streamfile(new_sf);
return NULL;
}
#endif /* _BGW_STREAMFILE_H_ */
#endif

View file

@ -2,7 +2,7 @@
#include "../coding/coding.h"
#include "../util.h"
static int bink_get_info(STREAMFILE* sf, int target_subsong, int* p_total_subsongs, size_t* p_stream_size, int* p_channels, int* p_sample_rate, int* p_num_samples);
static bool bink_get_info(STREAMFILE* sf, int target_subsong, int* p_total_subsongs, size_t* p_stream_size, int* p_channels, int* p_sample_rate, int* p_num_samples);
/* BINK 1/2 - RAD Game Tools movies (audio/video format) */
VGMSTREAM* init_vgmstream_bik(STREAMFILE* sf) {
@ -17,13 +17,14 @@ VGMSTREAM* init_vgmstream_bik(STREAMFILE* sf) {
(read_u32be(0x00,sf) & 0xffffff00) != get_id32be("KB2\0"))
goto fail;
/* .bik/bik2/bk2: standard
/* .bik/bk2: standard
* .bik2: older?
* .xmv: Reflections games [Driver: Parallel Lines (Wii), Emergency Heroes (Wii)]
* .bik.ps3: Neversoft games [Guitar Hero: Warriors of Rock (PS3)]
* .bik.xen: Neversoft games [various Guitar Hero (PC/PS3/X360)]
* .vid: Etrange Libellules games [Alice in Wonderland (PC)]
* .bika: fake extension for demuxed audio */
if (!check_extensions(sf,"bik,bik2,bk2,ps3,xmv,xen,vid,bika"))
if (!check_extensions(sf,"bik,bk2,bik2,ps3,xmv,xen,vid,bika"))
goto fail;
/* find target stream info and samples */
@ -59,79 +60,126 @@ fail:
return NULL;
}
/* official values */
#define BINK_MAX_FRAMES 1000000
#define BINK_MAX_TRACKS 256
/**
* Gets stream info, and number of samples in a BINK file by reading all frames' headers (as it's VBR),
* as they are not in the main header. The header for BINK1 and 2 is the same.
* (a ~3 min movie needs ~6000-7000 frames = fseeks, should be fast enough)
*/
static int bink_get_info(STREAMFILE* sf, int target_subsong, int* p_total_subsongs, size_t* p_stream_size, int* p_channels, int* p_sample_rate, int* p_num_samples) {
uint32_t* offsets = NULL;
uint32_t num_frames, num_samples_b = 0;
* see: https://wiki.multimedia.cx/index.php?title=Bink_Container */
static bool bink_get_info(STREAMFILE* sf, int target_subsong, int* p_total_subsongs, size_t* p_stream_size, int* p_channels, int* p_sample_rate, int* p_num_samples) {
uint32_t* offsets = NULL; //TODO rename
uint32_t num_samples_b = 0;
off_t cur_offset;
int i, j, sample_rate, channels;
int total_subsongs;
size_t stream_size = 0;
size_t filesize = get_streamfile_size(sf);
uint32_t signature = (read_32bitBE(0x00,sf) & 0xffffff00);
uint8_t revision = (read_32bitBE(0x00,sf) & 0xFF);
/* known revisions:
* bik1: b,d,f,g,h,i,k [no "j"]
* bik2: a,d,f,g,h,i,j,k,m,n [no "l"]
* (current public binkplay.exe allows 1=f~k and 2=f~n) */
uint32_t head_id = read_u32be(0x00,sf);
uint32_t file_size = read_u32le(0x04,sf) + 0x08;
uint32_t num_frames = read_u32le(0x08,sf);
/* 0x0c: largest frame */
/* 0x10: num_frames again (found even for files without audio) */
/* 0x14: video width (max 32767, apparently can be negative but no different meaning) */
/* 0x18: video height (max 32767) */
/* 0x1c: fps dividend (must be set) */
/* 0x20: fps divider (must be set)
* - ex. 2997/100 = 29.97 fps, 30/1 = 30 fps (common values) */
//uint32_t video_flags = read_u32le(0x24,sf); /* (scale, alpha, color modes, etc) */
/* 0x28: audio tracks */
/* video flags:
- F0000000 (bits 28-31): width and height scaling bits (doubled, interlaced, etc)
- 00100000 (bit 20): has alpha plane
- 00040000 (bit 18): unknown, related to packet format? (seen in some bik2 n+, with and w/o audio)
- 00020000 (bit 17): grayscale
- 00010000 (bit 16): 12 16b numbers? (seen in binkplay)
- 00000010 (bit 4): 1 big number? (seems always set in bik1 k+ bik2 i+, but also set in bik2 g wihtout the number)
- 00000004 (bit 2): 6 16b (seen in some bik2 n+)
- 00000002 (bit 1): unknown (seen in some bik2 n+, with and w/o audio)
(from binkplay, flags 0x04 and 0x10000 can't coexist)
*/
if (file_size != get_streamfile_size(sf))
return false;
if (num_frames <= 0 || num_frames > BINK_MAX_FRAMES)
return false; /* (avoids big allocs below) */
if (read_32bitLE(0x04,sf) + 0x08 != filesize)
goto fail;
num_frames = (uint32_t)read_32bitLE(0x08,sf);
if (num_frames == 0 || num_frames > 0x100000) goto fail; /* something must be off (avoids big allocs below) */
uint32_t signature = head_id & 0xffffff00;
uint8_t revision = head_id & 0xFF;
int sample_rate, channels;
uint16_t audio_flags;
/* multichannel/multilanguage audio is usually N streams of stereo/mono, no way to know channel layout */
total_subsongs = read_32bitLE(0x28,sf);
int total_subsongs = read_s32le(0x28,sf);
if (total_subsongs < 1) {
vgm_logi("BIK: no audio in movie (ignore)\n");
goto fail;
}
if (target_subsong == 0) target_subsong = 1;
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs > 255) goto fail;
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs > BINK_MAX_TRACKS) goto fail;
/* find stream info and position in offset table */
cur_offset = 0x2c;
if ((signature == 0x42494B00 && (revision == 0x6b)) || /* k */
(signature == 0x4B423200 && (revision == 0x69 || revision == 0x6a || revision == 0x6b))) /* i,j,k */
cur_offset += 0x04; /* unknown v2 header field */
cur_offset += 0x04*total_subsongs; /* skip streams max packet bytes */
sample_rate = (uint16_t)read_16bitLE(cur_offset+0x04*(target_subsong-1)+0x00,sf);
channels = (uint16_t)read_16bitLE(cur_offset+0x04*(target_subsong-1)+0x02,sf) & 0x2000 ? 2 : 1; /* stereo flag */
cur_offset += 0x04*total_subsongs; /* skip streams info */
cur_offset += 0x04*total_subsongs; /* skip streams ids */
if ((signature == get_id32be("BIK\0") && revision >= 'k') || (signature == get_id32be("KB2\0") && revision >= 'i'))
cur_offset += 0x04;
//if (video_flags & 0x000004) /* only n+? */
// cur_offset += 0x0c; /* 6 number: s16 * 0.00003051850944757462 (video stuff?) */
//if (video_flags & 0x010000)
// cur_offset += 0x18;
//if (video_flags & 0x000010) /* only i+? */
// cur_offset += 0x04;
cur_offset += 0x04 * total_subsongs; /* skip streams max packet bytes */
sample_rate = read_u16le(cur_offset + 0x04 * (target_subsong - 1) + 0x00, sf);
audio_flags = read_u16le(cur_offset + 0x04 * (target_subsong - 1) + 0x02, sf);
cur_offset += 0x04 * total_subsongs; /* skip streams info */
cur_offset += 0x04 * total_subsongs; /* skip streams ids */
/* audio flags:
- 8000 (bit 15): unknown (observed in some samples)
- 4000 (bit 14): unknown (same file may have it set for none/some/all)
- 2000 (bit 13): stereo flag
- 1000 (bit 12): audio type (1=DCT, 0=FFT)
*/
channels = audio_flags & 0x2000 ? 2 : 1;
/* read frame offsets in a buffer, to avoid fseeking to the table back and forth */
offsets = malloc(sizeof(uint32_t) * num_frames);
offsets = malloc(num_frames * sizeof(uint32_t));
if (!offsets) goto fail;
for (i=0; i < num_frames; i++) {
offsets[i] = read_32bitLE(cur_offset,sf) & 0xFFFFFFFE; /* mask first bit (= keyframe) */
for (int i = 0; i < num_frames; i++) {
offsets[i] = read_u32le(cur_offset, sf) & 0xFFFFFFFE; /* mask first bit (= keyframe) */
cur_offset += 0x4;
if (offsets[i] > filesize) goto fail;
if (offsets[i] > file_size)
goto fail;
}
/* after the last index is the file size, validate just in case */
if (read_32bitLE(cur_offset,sf) != filesize) goto fail;
if (read_u32le(cur_offset,sf) != file_size)
goto fail;
/* read each frame header and sum all samples
* a frame has N audio packets with a header (one per stream) + video packet */
for (i=0; i < num_frames; i++) {
for (int i = 0; i < num_frames; i++) {
cur_offset = offsets[i];
/* read audio packet headers per stream */
for (j=0; j < total_subsongs; j++) {
uint32_t ap_size = read_32bitLE(cur_offset+0x00,sf); /* not counting this int */
for (int j = 0; j < total_subsongs; j++) {
uint32_t ap_size = read_u32le(cur_offset + 0x00,sf); /* not counting this int */
if (j == target_subsong-1) {
if (j + 1 == target_subsong) {
stream_size += 0x04 + ap_size;
if (ap_size > 0)
num_samples_b += read_32bitLE(cur_offset+0x04,sf); /* decoded samples in bytes */
num_samples_b += read_u32le(cur_offset + 0x04,sf); /* decoded samples in bytes */
break; /* next frame */
}
else { /* next stream packet or frame */
@ -140,8 +188,6 @@ static int bink_get_info(STREAMFILE* sf, int target_subsong, int* p_total_subson
}
}
free(offsets);
if (p_total_subsongs) *p_total_subsongs = total_subsongs;
if (p_stream_size) *p_stream_size = stream_size;
@ -150,9 +196,9 @@ static int bink_get_info(STREAMFILE* sf, int target_subsong, int* p_total_subson
//todo returns a few more samples (~48) than binkconv.exe?
if (p_num_samples) *p_num_samples = num_samples_b / (2 * channels);
return 1;
free(offsets);
return true;
fail:
free(offsets);
return 0;
return false;
}

View file

@ -143,6 +143,11 @@ VGMSTREAM* init_vgmstream_bkhd(STREAMFILE* sf) {
vgmstream = init_vgmstream_wwise_bnk(temp_sf, &prefetch);
if (!vgmstream) goto fail;
}
else if (is_id32be(0x00, temp_sf, "ADM3")) {
// TODO: these may have multiple subsongs
vgmstream = init_vgmstream_adm3(temp_sf);
if (!vgmstream) goto fail;
}
else if (read_f32(subfile_offset + 0x02, temp_sf) >= 30.0 &&
read_f32(subfile_offset + 0x02, temp_sf) <= 250.0) {
is_wmid = 1;

View file

@ -3,7 +3,7 @@
#include "../coding/coding.h"
#include "../util/endianness.h"
typedef enum { NONE, DUMMY, PSX, PCM16, ATRAC9, HEVAG, RIFF_ATRAC9 } bnk_codec;
typedef enum { NONE, DUMMY, PSX, PCM16, MPEG, ATRAC9, HEVAG, RIFF_ATRAC9 } bnk_codec;
typedef struct {
bnk_codec codec;
@ -41,6 +41,7 @@ typedef struct {
int32_t num_samples;
int32_t loop_start;
int32_t loop_end;
int32_t encoder_delay;
uint32_t start_offset;
uint32_t stream_offset;
@ -81,7 +82,7 @@ VGMSTREAM* init_vgmstream_bnk_sony(STREAMFILE* sf) {
vgmstream->meta_type = meta_BNK_SONY;
if (!h.stream_name_size)
if (h.stream_name_size >= STREAM_NAME_SIZE || h.stream_name_size <= 0)
h.stream_name_size = STREAM_NAME_SIZE;
if (!h.bank_name_offset && h.stream_name_offset) {
@ -90,7 +91,7 @@ VGMSTREAM* init_vgmstream_bnk_sony(STREAMFILE* sf) {
else if (h.bank_name_offset && h.stream_name_offset) {
read_string(bank_name, h.stream_name_size, h.bank_name_offset, sf);
read_string(stream_name, h.stream_name_size, h.stream_name_offset, sf);
snprintf(vgmstream->stream_name, h.stream_name_size, "%s/%s", bank_name, stream_name);
snprintf(vgmstream->stream_name, h.stream_name_size, "%s%s%s", bank_name, bank_name[0] == '\0' ? "" : "/", stream_name);
}
@ -147,6 +148,20 @@ VGMSTREAM* init_vgmstream_bnk_sony(STREAMFILE* sf) {
return temp_vs;
}
#endif
#ifdef VGM_USE_MPEG
case MPEG: {
mpeg_custom_config cfg = {0};
cfg.skip_samples = h.encoder_delay;
vgmstream->layout_type = layout_none;
vgmstream->codec_data = init_mpeg_custom(sf, h.start_offset, &vgmstream->coding_type, h.channels, MPEG_STANDARD, &cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->num_samples = h.num_samples;
break;
}
#endif
case PCM16:
vgmstream->coding_type = h.big_endian ? coding_PCM16BE : coding_PCM16LE;
vgmstream->layout_type = layout_interleave;
@ -417,12 +432,12 @@ static bool process_headers(STREAMFILE* sf, bnk_header_t* h) {
switch(h->sblk_version) {
case 0x01:
/* table2/3 has size 0x28 entries, seemingly:
* 0x00: subtype(01=sound)
* 0x08: same as other versions (pitch, flags, offset...)
* rest: padding
* 0x18: stream offset
* there is no stream size like in v0x03
*/
* 0x00: subtype(01=sound)
* 0x08: same as other versions (pitch, flags, offset...)
* rest: padding
* 0x18: stream offset
* there is no stream size like in v0x03
*/
for (i = 0; i < h->grains_entries; i++) {
uint32_t table2_type = read_u32(h->table2_offset + (i*0x28) + 0x00, sf);
@ -527,22 +542,22 @@ static bool process_headers(STREAMFILE* sf, bnk_header_t* h) {
h->sample_rate = (pitch * 48000) / 0x1000;
/* waves can set base sample rate (48/44/22/11/8khz) + pitch in semitones, then converted to center+fine
* 48000 + pitch 0.00 > center=0xc4, fine=0x00
* 48000 + pitch 0.10 > center=0xc4, fine=0x0c
* 48000 + pitch 0.50 > center=0xc4, fine=0x3f
* 48000 + pitch 0.99 > center=0xc4, fine=0x7d
* 48000 + pitch 1.00 > center=0xc5, fine=0x00
* 48000 + pitch 12.00 > center=0xd0, fine=0x00
* 48000 + pitch 24.00 > center=0xdc, fine=0x00
* 48000 + pitch 56.00 > center=0xfc, fine=0x00
* 48000 + pitch 68.00 > center=0x08, fine=0x00 > ?
* 48000 + pitch -12.00 > center=0xb8, fine=0x00
* 48000 + pitch -0.10 > center=0xc3, fine=0x72
* 48000 + pitch -0.001 > not allowed
* 8000 + pitch 1.00 > center=0xa4, fine=0x7c
* 8000 + pitch -12.00 > center=0x98, fine=0x7c
* 8000 + pitch -48.00 > center=0x74, fine=0x7c
*/
* 48000 + pitch 0.00 > center=0xc4, fine=0x00
* 48000 + pitch 0.10 > center=0xc4, fine=0x0c
* 48000 + pitch 0.50 > center=0xc4, fine=0x3f
* 48000 + pitch 0.99 > center=0xc4, fine=0x7d
* 48000 + pitch 1.00 > center=0xc5, fine=0x00
* 48000 + pitch 12.00 > center=0xd0, fine=0x00
* 48000 + pitch 24.00 > center=0xdc, fine=0x00
* 48000 + pitch 56.00 > center=0xfc, fine=0x00
* 48000 + pitch 68.00 > center=0x08, fine=0x00 > ?
* 48000 + pitch -12.00 > center=0xb8, fine=0x00
* 48000 + pitch -0.10 > center=0xc3, fine=0x72
* 48000 + pitch -0.001 > not allowed
* 8000 + pitch 1.00 > center=0xa4, fine=0x7c
* 8000 + pitch -12.00 > center=0x98, fine=0x7c
* 8000 + pitch -48.00 > center=0x74, fine=0x7c
*/
break;
}
@ -567,7 +582,7 @@ static bool process_headers(STREAMFILE* sf, bnk_header_t* h) {
goto fail;
}
//;VGM_LOG("BNK: stream at %lx + %x\n", h->stream_offset, h->stream_size);
;VGM_LOG("BNK: header %x, stream at %x + %x\n", sndh_offset, h->data_offset + h->stream_offset, h->stream_size);
return true;
fail:
@ -602,17 +617,17 @@ static bool process_names(STREAMFILE* sf, bnk_header_t* h) {
}
/* table4:
* 0x00: bank name (optional)
* 0x08: name section offset
* 0x0C-0x14: 3 null pointers (reserved?)
* 0x18-0x58: 32 name chunk offset indices
*/
* 0x00: bank name (optional)
* 0x08: name section offset
* 0x0C-0x18: 3 null pointers (reserved?)
* 0x18-0x58: 32 name chunk offset indices
*/
/* Name chunks are organised as
* (name[0] + name[4] + name[8] + name[12]) & 0x1F;
* and using that as the index for the chunk offsets
* name_sect_offset + (chunk_idx[result] * 0x14);
*/
* (name[0] + name[4] + name[8] + name[12]) % 32;
* and using that as the index for the chunk offsets
* name_sect_offset + (chunk_idx[result] * 0x14);
*/
if (read_u8(h->table4_offset, sf))
h->bank_name_offset = h->table4_offset;
@ -626,7 +641,7 @@ static bool process_names(STREAMFILE* sf, bnk_header_t* h) {
while (read_u8(h->stream_name_offset, sf)) {
/* in case it goes somewhere out of bounds unexpectedly */
if (((read_u8(h->stream_name_offset + 0x00, sf) + read_u8(h->stream_name_offset + 0x04, sf) +
read_u8(h->stream_name_offset + 0x08, sf) + read_u8(h->stream_name_offset + 0x0C, sf)) & 0x1F) != i)
read_u8(h->stream_name_offset + 0x08, sf) + read_u8(h->stream_name_offset + 0x0C, sf)) & 0x1F) != i)
goto fail;
if (read_u16(h->stream_name_offset + 0x10, sf) == table4_entry_id)
goto loop_break; /* to break out of the for+while loop simultaneously */
@ -653,16 +668,16 @@ static bool process_names(STREAMFILE* sf, bnk_header_t* h) {
}
/* table4:
* 0x00: bank name (optional)
* 0x08: name entries offset
* 0x0C: name section offset
*
* name entries offset:
* 0x00: name offset in name section
* 0x04: name hash(?)
* 0x08: ? (2x int16)
* 0x0C: section index (int16)
*/
* 0x00: bank name (optional)
* 0x08: name entries offset
* 0x0C: name section offset
*
* name entries offset:
* 0x00: name offset in name section
* 0x04: name hash(?)
* 0x08: ? (2x int16)
* 0x0C: section index (int16)
*/
if (read_u8(h->table4_offset, sf))
h->bank_name_offset = h->table4_offset;
@ -722,7 +737,7 @@ static bool process_names(STREAMFILE* sf, bnk_header_t* h) {
break;
}
//;VGM_LOG("BNK: stream_offset=%lx, stream_size=%x, stream_name_offset=%lx\n", h->stream_offset, h->stream_size, h->stream_name_offset);
//;VGM_LOG("BNK: stream_offset=%x, stream_size=%x, stream_name_offset=%x\n", h->stream_offset, h->stream_size, h->stream_name_offset);
return true;
fail:
@ -785,19 +800,19 @@ static bool process_data(STREAMFILE* sf, bnk_header_t* h) {
h->interleave = h->stream_size / h->channels;
/* PS Home Arcade has other flags? supposedly:
* 01 = reverb
* 02 = vol scale 20
* 04 = vol scale 50
* 06 = vol scale 100
* 08 = noise
* 10 = no dry
* 20 = no steal
* 40 = loop VAG
* 80 = PCM
* 100 = has advanced packets
* 200 = send LFE
* 400 = send center
*/
* 01 = reverb
* 02 = vol scale 20
* 04 = vol scale 50
* 06 = vol scale 100
* 08 = noise
* 10 = no dry
* 20 = no steal
* 40 = loop VAG
* 80 = PCM
* 100 = has advanced packets
* 200 = send LFE
* 400 = send center
*/
if ((h->stream_flags & 0x80) && h->sblk_version <= 3) {
h->codec = PCM16; /* rare [Wipeout HD (PS3)]-v3 */
}
@ -813,8 +828,8 @@ static bool process_data(STREAMFILE* sf, bnk_header_t* h) {
case 0x08:
case 0x09:
subtype = read_u16(h->start_offset+0x00,sf);
extradata_size = 0x08 + read_u32(h->start_offset+0x04,sf); /* 0x14 for AT9 */
subtype = read_u32(h->start_offset+0x00,sf);
extradata_size = 0x08 + read_u32(h->start_offset+0x04,sf); /* 0x14 for AT9, 0x10 for PCM, 0x90 for MPEG */
switch(subtype) {
case 0x00:
@ -829,22 +844,49 @@ static bool process_data(STREAMFILE* sf, bnk_header_t* h) {
h->interleave = 0x01;
break;
case 0x02: /* ATRAC9 mono */
case 0x05: /* ATRAC9 stereo */
if (read_u32(h->start_offset+0x08,sf) + 0x08 != extradata_size) { /* repeat? */
VGM_LOG("BNK: unknown subtype\n");
goto fail;
}
case 0x02: /* ATRAC9 / MPEG mono */
case 0x05: /* ATRAC9 / MPEG stereo */
h->channels = (subtype == 0x02) ? 1 : 2;
h->atrac9_info = read_u32be(h->start_offset+0x0c,sf);
/* 0x10: null? */
loop_length = read_u32(h->start_offset+0x14,sf);
h->loop_start = read_u32(h->start_offset+0x18,sf);
h->loop_end = h->loop_start + loop_length; /* loop_start is -1 if not set */
if (h->big_endian) {
/* The Last of Us demo (PS3) */
h->codec = ATRAC9;
/* 0x08: mpeg version? (1) */
/* 0x0C: mpeg layer? (3) */
/* 0x10: ? (related to frame size, 0xC0 > 0x40, 0x120 > 0x60) */
/* 0x14: sample rate */
/* 0x18: mpeg layer? (3) */
/* 0x1c: mpeg version? (1) */
/* 0x20: channels? */
/* 0x24: frame size */
/* 0x28: encoder delay */
/* 0x2c: num samples */
/* 0x30: ? */
/* 0x34: ? */
/* 0x38: 0? */
/* 0x3c: data size */
/* padding up to 0x90 */
h->encoder_delay = read_s32(h->start_offset+0x28,sf);
h->num_samples = read_s32(h->start_offset+0x2c,sf);
h->codec = MPEG;
}
else {
/* Puyo Puyo Tetris (PS4) */
if (read_u32(h->start_offset+0x08,sf) + 0x08 != extradata_size) { /* repeat? */
VGM_LOG("BNK: unknown subtype\n");
goto fail;
}
h->atrac9_info = read_u32be(h->start_offset+0x0c,sf);
/* 0x10: null? */
loop_length = read_u32(h->start_offset+0x14,sf);
h->loop_start = read_u32(h->start_offset+0x18,sf);
h->loop_end = h->loop_start + loop_length; /* loop_start is -1 if not set */
h->codec = ATRAC9;
}
break;
default:

View file

@ -0,0 +1,46 @@
#include "meta.h"
#include "../coding/coding.h"
/* !B0X - Traveller's Tales speech files [Lego Batman 2 (PC), Lego Dimensions (PS3)] */
VGMSTREAM* init_vgmstream_cbx(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
uint32_t start_offset, pcm_size;
int loop_flag, channels, sample_rate;
/* checks */
if (!is_id32be(0x00,sf, "!B0X"))
return NULL;
if (!check_extensions(sf, "cbx"))
return NULL;
/* debug strings identify this as "Chatterbox"/"CBOX"/"CBX", while sound lib seems called "NuSound"
* (probably based on .utk) */
pcm_size = read_u32le(0x04, sf);
sample_rate = read_s32le(0x08, sf);
start_offset = 0x0c;
channels = 1;
loop_flag = 0;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_CBX;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = pcm_size / 2;
vgmstream->coding_type = coding_EA_MT;
vgmstream->layout_type = layout_none;
vgmstream->codec_data = init_ea_mt_cbx(vgmstream->channels);
if (!vgmstream->codec_data) goto fail;
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View file

@ -282,11 +282,11 @@ static void load_cpk_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstre
return;
/* companion .acb probably loaded */
load_acb_wave_name(sf_acb, vgmstream, waveid, port, is_memory);
load_acb_wave_info(sf_acb, vgmstream, waveid, port, is_memory, 0);
close_streamfile(sf_acb);
}
else {
load_acb_wave_name(sf_acb, vgmstream, waveid, port, is_memory);
load_acb_wave_info(sf_acb, vgmstream, waveid, port, is_memory, 0);
}
}

View file

@ -1,75 +0,0 @@
#include "meta.h"
#include "../util.h"
/* ASD - found in Miss Moonlight (DC) */
VGMSTREAM * init_vgmstream_dc_asd(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
char filename[PATH_LIMIT];
off_t start_offset;
int loop_flag;
int channel_count;
/* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename));
if (strcasecmp("asd",filename_extension(filename))) goto fail;
/* We have no "Magic" words in this header format, so we have to do some,
other checks, it seems the samplecount is stored twice in the header,
we'll compare it... */
if (read_32bitLE(0x0,streamFile) != read_32bitLE(0x4,streamFile))
goto fail;
/* compare the frequency with the bitrate, if it doesn't match we'll close
the vgmstream... */
if (read_32bitLE(0x10,streamFile)/read_32bitLE(0xC,streamFile) != (uint16_t)read_16bitLE(0xA,streamFile)*2)
goto fail;
loop_flag = 0;
channel_count = read_16bitLE(0x0A,streamFile);
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
/* fill in the vital statistics */
start_offset = get_streamfile_size(streamFile) - read_32bitLE(0x0,streamFile);
vgmstream->channels = channel_count;
vgmstream->sample_rate = read_32bitLE(0x0C,streamFile);
vgmstream->coding_type = coding_PCM16LE;
vgmstream->num_samples = read_32bitLE(0x0,streamFile)/2/channel_count;
if (loop_flag) {
vgmstream->loop_start_sample = 0;
vgmstream->loop_end_sample = read_32bitLE(0x0,streamFile)/2/channel_count;
}
vgmstream->meta_type = meta_DC_ASD;
if (vgmstream->channels == 1) {
vgmstream->layout_type = layout_none;
} else if (vgmstream->channels == 2) {
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x2;
}
/* open the file for reading */
{
int i;
STREAMFILE * file;
file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (!file) goto fail;
for (i=0;i<channel_count;i++) {
vgmstream->ch[i].streamfile = file;
vgmstream->ch[i].channel_start_offset=
vgmstream->ch[i].offset=start_offset+
vgmstream->interleave_block_size*i;
}
}
return vgmstream;
/* clean up anything we may have opened */
fail:
if (vgmstream) close_vgmstream(vgmstream);
return NULL;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,197 @@
#include "meta.h"
#include "../util/endianness.h"
static VGMSTREAM* parse_s10a_header(STREAMFILE* sf, off_t offset, uint16_t target_index, off_t ast_offset);
/* EA ABK - ABK header seems to be same as in the old games but the sound table is different and it contains SNR/SNS sounds instead */
VGMSTREAM* init_vgmstream_ea_abk_eaac(STREAMFILE* sf) {
VGMSTREAM* vgmstream;
int is_dupe, total_sounds = 0, target_stream = sf->stream_index;
off_t bnk_offset, modules_table, module_data, player_offset, samples_table, entry_offset, ast_offset;
off_t cfg_num_players_off, cfg_module_data_off, cfg_module_entry_size, cfg_samples_table_off;
uint32_t num_sounds, num_sample_tables;
uint16_t num_modules, bnk_index, bnk_target_index;
uint8_t num_players;
off_t sample_tables[0x400];
int32_t(*read_32bit)(off_t, STREAMFILE*);
int16_t(*read_16bit)(off_t, STREAMFILE*);
/* checks */
if (!is_id32be(0x00, sf, "ABKC"))
return NULL;
if (!check_extensions(sf, "abk"))
return NULL;
/* use table offset to check endianness */
if (guess_endian32(0x1C, sf)) {
read_32bit = read_32bitBE;
read_16bit = read_16bitBE;
} else {
read_32bit = read_32bitLE;
read_16bit = read_16bitLE;
}
if (target_stream == 0) target_stream = 1;
if (target_stream < 0)
goto fail;
num_modules = read_16bit(0x0A, sf);
modules_table = read_32bit(0x1C, sf);
bnk_offset = read_32bit(0x20, sf);
num_sample_tables = 0;
bnk_target_index = 0xFFFF;
ast_offset = 0;
if (!bnk_offset || !is_id32be(bnk_offset, sf, "S10A"))
goto fail;
/* set up some common values */
if (modules_table == 0x5C) {
/* the usual variant */
cfg_num_players_off = 0x24;
cfg_module_data_off = 0x2C;
cfg_module_entry_size = 0x3C;
cfg_samples_table_off = 0x04;
}
else if (modules_table == 0x78) {
/* FIFA 08 has a bunch of extra zeroes all over the place, don't know what's up with that */
cfg_num_players_off = 0x40;
cfg_module_data_off = 0x54;
cfg_module_entry_size = 0x68;
cfg_samples_table_off = 0x0C;
}
else {
goto fail;
}
for (uint32_t i = 0; i < num_modules; i++) {
num_players = read_8bit(modules_table + cfg_num_players_off, sf);
module_data = read_32bit(modules_table + cfg_module_data_off, sf);
if (num_players == 0xff) goto fail; /* EOF read */
for (uint32_t j = 0; j < num_players; j++) {
player_offset = read_32bit(modules_table + cfg_module_entry_size + 0x04 * j, sf);
samples_table = read_32bit(module_data + player_offset + cfg_samples_table_off, sf);
/* multiple players may point at the same sound table */
is_dupe = 0;
for (uint32_t k = 0; k < num_sample_tables; k++) {
if (samples_table == sample_tables[k]) {
is_dupe = 1;
break;
}
}
if (is_dupe)
continue;
sample_tables[num_sample_tables++] = samples_table;
num_sounds = read_32bit(samples_table, sf);
if (num_sounds == 0xffffffff) goto fail; /* EOF read */
for (uint32_t k = 0; k < num_sounds; k++) {
/* 0x00: sound index */
/* 0x02: priority */
/* 0x03: azimuth */
/* 0x08: streamed data offset */
entry_offset = samples_table + 0x04 + 0x0C * k;
bnk_index = read_16bit(entry_offset + 0x00, sf);
/* some of these are dummies */
if (bnk_index == 0xFFFF)
continue;
total_sounds++;
if (target_stream == total_sounds) {
bnk_target_index = bnk_index;
ast_offset = read_32bit(entry_offset + 0x08, sf);
}
}
}
/* skip class controllers */
num_players += read_8bit(modules_table + cfg_num_players_off + 0x03, sf);
modules_table += cfg_module_entry_size + num_players * 0x04;
}
if (bnk_target_index == 0xFFFF || ast_offset == 0)
goto fail;
vgmstream = parse_s10a_header(sf, bnk_offset, bnk_target_index, ast_offset);
if (!vgmstream) goto fail;
vgmstream->num_streams = total_sounds;
return vgmstream;
fail:
return NULL;
}
/* EA S10A header - seen inside new ABK files. Putting it here in case it's encountered stand-alone. */
static VGMSTREAM* parse_s10a_header(STREAMFILE* sf, off_t offset, uint16_t target_index, off_t sns_offset) {
VGMSTREAM* vgmstream;
STREAMFILE *sf_ast = NULL;
uint32_t num_sounds;
off_t snr_offset;
eaac_meta_t info = {0};
/* header is always big endian */
/* 0x00: header magic */
/* 0x04: version */
/* 0x05: padding */
/* 0x06: serial number */
/* 0x08: number of files */
/* 0x0C: offsets table */
if (!is_id32be(offset + 0x00, sf, "S10A"))
return NULL;
num_sounds = read_32bitBE(offset + 0x08, sf);
if (num_sounds == 0 || target_index >= num_sounds)
return NULL;
snr_offset = offset + read_32bitBE(offset + 0x0C + 0x04 * target_index, sf);
if (sns_offset == 0xFFFFFFFF) {
/* RAM asset */
//;VGM_LOG("EA S10A: RAM at snr=%lx", snr_offset);
info.sf_head = sf;
info.head_offset = snr_offset;
info.body_offset = 0x00;
info.type = meta_EA_SNR_SNS;
vgmstream = load_vgmstream_ea_eaac(&info);
if (!vgmstream) goto fail;
}
else {
/* streamed asset */
sf_ast = open_streamfile_by_ext(sf, "ast");
if (!sf_ast) {
vgm_logi("EA ABK: .ast file not found (find and put together)\n");
goto fail;
}
if (!is_id32be(0x00, sf_ast, "S10S"))
goto fail;
//;VGM_LOG("EA S10A: stream at snr=%lx, sns=%lx\n", snr_offset, sns_offset);
info.sf_head = sf;
info.sf_body = sf_ast;
info.head_offset = snr_offset;
info.body_offset = sns_offset;
info.type = meta_EA_SNR_SNS;
vgmstream = load_vgmstream_ea_eaac(&info);
if (!vgmstream) goto fail;
close_streamfile(sf_ast);
}
return vgmstream;
fail:
close_streamfile(sf_ast);
return NULL;
}

View file

@ -0,0 +1,160 @@
#include "meta.h"
#include "../util/endianness.h"
#define EAAC_BLOCKID0_DATA 0x00
#define EAAC_BLOCKID0_END 0x80 /* maybe meant to be a bitflag? */
/* EA HDR/STH/DAT - seen in older 7th gen games, used for storing speech */
VGMSTREAM* init_vgmstream_ea_hdr_sth_dat(STREAMFILE* sf) {
VGMSTREAM* vgmstream;
STREAMFILE *sf_dat = NULL, *sf_sth = NULL;
int target_stream = sf->stream_index;
uint32_t snr_offset, sns_offset, block_size;
uint16_t sth_offset, sth_offset2;
uint8_t num_params, num_sounds, block_id;
size_t dat_size;
uint32_t(*read_u32)(off_t, STREAMFILE*);
eaac_meta_t info = {0};
/* 0x00: ID */
/* 0x02: number of parameters */
/* 0x03: number of samples */
/* 0x04: speaker ID (used for different police voices in NFS games) */
/* 0x08: sample repeat (alt number of samples?) */
/* 0x09: block size (always zero?) */
/* 0x0a: number of blocks (related to size?) */
/* 0x0c: number of sub-banks (always zero?) */
/* 0x0e: padding */
/* 0x10: table start */
if (!check_extensions(sf, "hdr"))
return NULL;
if (read_u8(0x09, sf) != 0)
return NULL;
if (read_u32be(0x0c, sf) != 0)
return NULL;
/* first offset is always zero */
if (read_u16be(0x10, sf) != 0)
return NULL;
sf_sth = open_streamfile_by_ext(sf, "sth");
if (!sf_sth) goto fail;
sf_dat = open_streamfile_by_ext(sf, "dat");
if (!sf_dat) goto fail;
/* STH always starts with the first offset of zero */
sns_offset = read_u32be(0x00, sf_sth);
if (sns_offset != 0)
goto fail;
/* check if DAT starts with a correct SNS block */
block_id = read_u8(0x00, sf_dat);
if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END)
goto fail;
num_params = read_u8(0x02, sf) & 0x7F;
num_sounds = read_u8(0x03, sf);
if (read_u8(0x08, sf) > num_sounds)
goto fail;
if (target_stream == 0) target_stream = 1;
if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds)
goto fail;
/* offsets in HDR are always big endian */
sth_offset = read_u16be(0x10 + (0x02 + num_params) * (target_stream - 1), sf);
#if 0
snr_offset = sth_offset + 0x04;
sns_offset = read_u32(sth_offset + 0x00, sf_sth);
#else
/* overly intricate way to detect byte endianness because of the simplicity of HDR format */
dat_size = get_streamfile_size(sf_dat);
snr_offset = 0;
sns_offset = 0;
if (num_sounds == 1) {
/* always 0 */
snr_offset = sth_offset + 0x04;
sns_offset = 0x00;
}
else {
/* find the first sound size and match it up with the second sound offset to detect endianness */
while (1) {
if (sns_offset >= dat_size)
goto fail;
block_id = read_u8(sns_offset, sf_dat);
block_size = read_u32be(sns_offset, sf_dat) & 0x00FFFFFF;
if (block_size == 0)
goto fail;
if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END)
goto fail;
sns_offset += block_size;
if (block_id == EAAC_BLOCKID0_END)
break;
}
sns_offset = align_size_to_block(sns_offset, 0x40);
sth_offset2 = read_u16be(0x10 + (0x02 + num_params) * 1, sf);
if (sns_offset == read_u32be(sth_offset2, sf_sth)) {
read_u32 = read_u32be;
}
else if (sns_offset == read_u32le(sth_offset2, sf_sth)) {
read_u32 = read_u32le;
}
else {
goto fail;
}
snr_offset = sth_offset + 0x04;
sns_offset = read_u32(sth_offset + 0x00, sf_sth);
}
#endif
block_id = read_u8(sns_offset, sf_dat);
if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END)
goto fail;
info.sf_head = sf_sth;
info.sf_body = sf_dat;
info.head_offset = snr_offset;
info.body_offset = sns_offset;
info.type = meta_EA_SNR_SNS;
vgmstream = load_vgmstream_ea_eaac(&info);
if (!vgmstream) goto fail;
if (num_params != 0) {
uint8_t val;
char buf[8];
int i;
for (i = 0; i < num_params; i++) {
val = read_u8(0x10 + (0x02 + num_params) * (target_stream - 1) + 0x02 + i, sf);
snprintf(buf, sizeof(buf), "%u", val);
concatn(STREAM_NAME_SIZE, vgmstream->stream_name, buf);
if (i != num_params - 1)
concatn(STREAM_NAME_SIZE, vgmstream->stream_name, ", ");
}
}
vgmstream->num_streams = num_sounds;
close_streamfile(sf_sth);
close_streamfile(sf_dat);
return vgmstream;
fail:
close_streamfile(sf_sth);
close_streamfile(sf_dat);
return NULL;
}

View file

@ -0,0 +1,343 @@
#include "meta.h"
#include "../util/endianness.h"
#include "../layout/layout.h"
#include "../util/companion_files.h"
static STREAMFILE *open_mapfile_pair(STREAMFILE* sf, int track /*, int num_tracks*/);
/* EA MPF/MUS combo - used in older 7th gen games for storing interactive music */
VGMSTREAM* init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE *sf_mus = NULL;
uint32_t num_tracks, track_start, track_checksum = 0, mus_sounds, mus_stream = 0, bnk_index = 0, bnk_sound_index = 0,
tracks_table, samples_table, eof_offset, table_offset, entry_offset = 0, snr_offset, sns_offset;
uint16_t num_subbanks, index, sub_index;
uint8_t version, sub_version;
segmented_layout_data* data_s = NULL;
int i;
int target_stream = sf->stream_index, total_streams, is_ram = 0;
uint32_t(*read_u32)(off_t, STREAMFILE *);
uint16_t(*read_u16)(off_t, STREAMFILE *);
/* checks */
if (is_id32be(0x00, sf, "PFDx")) {
read_u32 = read_u32be;
read_u16 = read_u16be;
}
else if (is_id32le(0x00, sf, "PFDx")) {
read_u32 = read_u32le;
read_u16 = read_u16le;
}
else {
return NULL;
}
if (!check_extensions(sf, "mpf"))
return NULL;
version = read_u8(0x04, sf);
sub_version = read_u8(0x05, sf);
if (version != 5 || sub_version < 2 || sub_version > 3) goto fail;
num_tracks = read_u8(0x0d, sf);
tracks_table = read_u32(0x2c, sf);
samples_table = read_u32(0x34, sf);
eof_offset = read_u32(0x38, sf);
total_streams = (eof_offset - samples_table) / 0x08;
if (target_stream == 0) target_stream = 1;
if (target_stream < 0 || total_streams == 0 || target_stream > total_streams)
goto fail;
for (i = num_tracks - 1; i >= 0; i--) {
entry_offset = read_u32(tracks_table + i * 0x04, sf) * 0x04;
track_start = read_u32(entry_offset + 0x00, sf);
if (track_start == 0 && i != 0)
continue; /* empty track */
if (track_start <= target_stream - 1) {
num_subbanks = read_u16(entry_offset + 0x04, sf);
track_checksum = read_u32be(entry_offset + 0x08, sf);
is_ram = (num_subbanks != 0);
mus_stream = target_stream - 1 - track_start;
break;
}
}
/* open MUS file that matches this track */
sf_mus = open_mapfile_pair(sf, i);//, num_tracks
if (!sf_mus) goto fail;
/* sample offsets table is still there but it just holds SNS offsets, we only need it for RAM sound indexes */
/* 0x00 - offset/index, 0x04 - duration (in milliseconds) */
sns_offset = read_u32(samples_table + (target_stream - 1) * 0x08 + 0x00, sf);
if (is_ram) {
bnk_sound_index = (sns_offset & 0x0000FFFF);
bnk_index = (sns_offset & 0xFFFF0000) >> 16;
if (bnk_index != 0) {
/* HACK: open proper .mus now since open_mapfile_pair doesn't let us adjust the name */
char filename[PATH_LIMIT], basename[PATH_LIMIT], ext[32];
int basename_len;
STREAMFILE* sf_temp;
get_streamfile_basename(sf_mus, basename, PATH_LIMIT);
basename_len = strlen(basename);
get_streamfile_ext(sf_mus, ext, sizeof(ext));
/* strip off 0 at the end */
basename[basename_len - 1] = '\0';
/* append bank index to the name */
snprintf(filename, PATH_LIMIT, "%s%u.%s", basename, bnk_index, ext);
sf_temp = open_streamfile_by_filename(sf_mus, filename);
if (!sf_temp) goto fail;
close_streamfile(sf_mus);
sf_mus = sf_temp;
}
track_checksum = read_u32be(entry_offset + 0x14 + bnk_index * 0x10, sf);
if (track_checksum && read_u32be(0x00, sf_mus) != track_checksum)
goto fail;
}
else {
if (track_checksum && read_u32be(0x00, sf_mus) != track_checksum)
goto fail;
}
/* MUS file has a header, however */
if (sub_version == 2) {
if (read_u32(0x04, sf_mus) != 0x00)
goto fail;
/*
* 0x00: index
* 0x02: sub-index
* 0x04: SNR offset
* 0x08: SNS offset (contains garbage for RAM sounds)
*/
table_offset = 0x08;
if (is_ram) {
int ram_segments;
/* find number of parts for this node */
for (i = 0; ; i++) {
entry_offset = table_offset + (bnk_sound_index + i) * 0x0c;
index = read_u16(entry_offset + 0x00, sf_mus);
sub_index = read_u16(entry_offset + 0x02, sf_mus);
if (index == 0xffff) /* EOF check */
goto fail;
entry_offset += 0x0c;
if (read_u16(entry_offset + 0x00, sf_mus) != index ||
read_u16(entry_offset + 0x02, sf_mus) != sub_index + 1)
break;
}
ram_segments = i + 1;
/* init layout */
data_s = init_layout_segmented(ram_segments);
if (!data_s) goto fail;
for (i = 0; i < ram_segments; i++) {
eaac_meta_t info = {0};
entry_offset = table_offset + (bnk_sound_index + i) * 0x0c;
snr_offset = read_u32(entry_offset + 0x04, sf_mus);
info.sf_head = sf_mus;
info.head_offset = snr_offset;
info.body_offset = 0x00;
info.type = meta_EA_SNR_SNS;
data_s->segments[i] = load_vgmstream_ea_eaac(&info);
if (!data_s->segments[i]) goto fail;
}
/* setup segmented VGMSTREAMs */
if (!setup_layout_segmented(data_s))
goto fail;
vgmstream = allocate_segmented_vgmstream(data_s, 0, 0, 0);
}
else {
eaac_meta_t info = {0};
entry_offset = table_offset + mus_stream * 0x0c;
snr_offset = read_u32(entry_offset + 0x04, sf_mus);
sns_offset = read_u32(entry_offset + 0x08, sf_mus);
info.sf_head = sf_mus;
info.sf_body = sf_mus;
info.head_offset = snr_offset;
info.body_offset = sns_offset;
info.type = meta_EA_SNR_SNS;
vgmstream = load_vgmstream_ea_eaac(&info);
}
}
else if (sub_version == 3) {
eaac_meta_t info = {0};
/* number of samples is always little endian */
mus_sounds = read_u32le(0x04, sf_mus);
if (mus_stream >= mus_sounds)
goto fail;
if (is_ram) {
/* not seen so far */
VGM_LOG("EA EAAC MUS: found RAM track in MPF v5.3.\n");
goto fail;
}
/*
* 0x00: checksum
* 0x04: index
* 0x06: sub-index
* 0x08: SNR offset
* 0x0c: SNS offset
* 0x10: SNR size
* 0x14: SNS size
* 0x18: zero
*/
table_offset = 0x28;
entry_offset = table_offset + mus_stream * 0x1c;
snr_offset = read_u32(entry_offset + 0x08, sf_mus) * 0x10;
sns_offset = read_u32(entry_offset + 0x0c, sf_mus) * 0x80;
info.sf_head = sf_mus;
info.sf_body = sf_mus;
info.head_offset = snr_offset;
info.body_offset = sns_offset;
info.type = meta_EA_SNR_SNS;
vgmstream = load_vgmstream_ea_eaac(&info);
}
else {
goto fail;
}
if (!vgmstream)
goto fail;
vgmstream->num_streams = total_streams;
get_streamfile_filename(sf_mus, vgmstream->stream_name, STREAM_NAME_SIZE);
close_streamfile(sf_mus);
return vgmstream;
fail:
close_streamfile(sf_mus);
free_layout_segmented(data_s);
return NULL;
}
//TODO remove a few lesser ones + use .txtm
/* open map/mpf+mus pairs that aren't exact pairs, since EA's games can load any combo */
static STREAMFILE *open_mapfile_pair(STREAMFILE* sf, int track /*, int num_tracks*/) {
static const char *const mapfile_pairs[][2] = {
/* standard cases, replace map part with mus part (from the end to preserve prefixes) */
{"game.mpf", "Game_Stream.mus"}, /* Skate 1/2/3 */
{"ipod.mpf", "Ipod_Stream.mus"},
{"world.mpf", "World_Stream.mus"},
{"FreSkate.mpf", "track.mus,ram.mus"}, /* Skate It */
{"nsf_sing.mpf", "track_main.mus"}, /* Need for Speed: Nitro */
{"nsf_wii.mpf", "Track.mus"},
{"ssx_fe.mpf", "stream_1.mus,stream_2.mus"}, /* SSX 2012 */
{"ssxdd.mpf", "main_trk.mus,"
"trick_alaska0.mus,"
"trick_rockies0.mus,"
"trick_pata0.mus,"
"trick_ant0.mus,"
"trick_killi0.mus,"
"trick_cyb0.mus,"
"trick_hima0.mus,"
"trick_nz0.mus,"
"trick_alps0.mus,"
"trick_lhotse0.mus"}
};
STREAMFILE *sf_mus = NULL;
char file_name[PATH_LIMIT];
int pair_count = (sizeof(mapfile_pairs) / sizeof(mapfile_pairs[0]));
int i, j;
size_t file_len, map_len;
/* try parsing TXTM if present */
sf_mus = read_filemap_file(sf, track);
if (sf_mus) return sf_mus;
/* if loading the first track, try opening MUS with the same name first (most common scenario) */
if (track == 0) {
sf_mus = open_streamfile_by_ext(sf, "mus");
if (sf_mus) return sf_mus;
}
get_streamfile_filename(sf, file_name, PATH_LIMIT);
file_len = strlen(file_name);
for (i = 0; i < pair_count; i++) {
const char *map_name = mapfile_pairs[i][0];
const char *mus_name = mapfile_pairs[i][1];
char buf[PATH_LIMIT] = { 0 };
char *pch;
int use_mask = 0;
map_len = strlen(map_name);
/* replace map_name with expected mus_name */
if (file_len < map_len)
continue;
if (map_name[0] == '*') {
use_mask = 1;
map_name++;
map_len--;
if (strncmp(file_name + (file_len - map_len), map_name, map_len) != 0)
continue;
} else {
if (strcmp(file_name, map_name) != 0)
continue;
}
strncpy(buf, mus_name, PATH_LIMIT - 1);
pch = strtok(buf, ","); //TODO: not thread safe in std C
for (j = 0; j < track && pch; j++) {
pch = strtok(NULL, ",");
}
if (!pch) continue; /* invalid track */
if (use_mask) {
file_name[file_len - map_len] = '\0';
strncat(file_name, pch + 1, PATH_LIMIT - 1);
} else {
strncpy(file_name, pch, PATH_LIMIT - 1);
}
sf_mus = open_streamfile_by_filename(sf, file_name);
if (sf_mus) return sf_mus;
get_streamfile_filename(sf, file_name, PATH_LIMIT); /* reset for next loop */
}
/* hack when when multiple maps point to the same mus, uses name before "+"
* ex. ZZZTR00A.TRJ+ZTR00PGR.MAP or ZZZTR00A.TRJ+ZTR00R0A.MAP both point to ZZZTR00A.TRJ */
{
char *mod_name = strchr(file_name, '+');
if (mod_name) {
mod_name[0] = '\0';
sf_mus = open_streamfile_by_filename(sf, file_name);
if (sf_mus) return sf_mus;
}
}
vgm_logi("EA MPF: .mus file not found (find and put together)\n");
return NULL;
}

View file

@ -0,0 +1,115 @@
#include "meta.h"
#include "../util/endianness.h"
#define EAAC_BLOCKID1_HEADER 0x48 /* 'H' */
/* EA SBR/SBS - used in older 7th gen games for storing SFX */
VGMSTREAM* init_vgmstream_ea_sbr(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE *sf_sbs = NULL;
uint32_t num_sounds, sound_id, type_desc, num_items, item_type,
table_offset, types_offset, entry_offset, items_offset, data_offset, snr_offset, sns_offset;
int target_stream = sf->stream_index;
eaac_meta_t info = {0};
/* checks */
if (!is_id32be(0x00, sf, "SBKR"))
return NULL;
if (!check_extensions(sf, "sbr"))
return NULL;
/* SBR files are always big endian */
num_sounds = read_u32be(0x1c, sf);
table_offset = read_u32be(0x24, sf);
types_offset = read_u32be(0x28, sf);
if (target_stream == 0) target_stream = 1;
if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds)
goto fail;
entry_offset = table_offset + 0x0a * (target_stream - 1);
sound_id = read_u32be(entry_offset + 0x00, sf);
num_items = read_u16be(entry_offset + 0x04, sf);
items_offset = read_u32be(entry_offset + 0x06, sf);
snr_offset = 0;
sns_offset = 0;
for (uint32_t i = 0; i < num_items; i++) {
entry_offset = items_offset + 0x06 * i;
item_type = read_u16be(entry_offset + 0x00, sf);
data_offset = read_u32be(entry_offset + 0x02, sf);
type_desc = read_u32be(types_offset + 0x06 * item_type, sf);
switch (type_desc) {
case 0x534E5231: /* "SNR1" */
snr_offset = data_offset;
break;
case 0x534E5331: /* "SNS1" */
sns_offset = read_u32be(data_offset, sf);
break;
default:
break;
}
}
if (snr_offset == 0 && sns_offset == 0)
goto fail;
if (sns_offset == 0) {
/* RAM asset */
info.sf_head = sf;
info.head_offset = snr_offset;
info.body_offset = 0x00;
info.type = (read_u8(snr_offset, sf) == EAAC_BLOCKID1_HEADER) ? meta_EA_SPS : meta_EA_SNR_SNS;;
vgmstream = load_vgmstream_ea_eaac(&info);
if (!vgmstream) goto fail;
}
else {
/* streamed asset */
sf_sbs = open_streamfile_by_ext(sf, "sbs");
if (!sf_sbs) goto fail;
if (!is_id32be(0x00, sf_sbs, "SBKS"))
goto fail;
if (read_u8(sns_offset, sf_sbs) == EAAC_BLOCKID1_HEADER) {
/* SPS */
info.sf_head = sf_sbs;
info.head_offset = sns_offset;
info.body_offset = 0x00;
info.type = meta_EA_SPS;
vgmstream = load_vgmstream_ea_eaac(&info);
if (!vgmstream) goto fail;
}
else {
/* SNR/SNS */
if (snr_offset == 0)
goto fail;
info.sf_head = sf;
info.sf_body = sf_sbs;
info.head_offset = snr_offset;
info.body_offset = sns_offset;
info.type = meta_EA_SNR_SNS;
vgmstream = load_vgmstream_ea_eaac(&info);
if (!vgmstream) goto fail;
}
}
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%08x", sound_id);
vgmstream->num_streams = num_sounds;
close_streamfile(sf_sbs);
return vgmstream;
fail:
close_streamfile(sf_sbs);
return NULL;
}

View file

@ -0,0 +1,243 @@
#include "meta.h"
#include "../layout/layout.h"
#include "../coding/coding.h"
#include "../util/endianness.h"
/* EA Harmony Sample Bank - used in 8th gen EA Sports games */
VGMSTREAM* init_vgmstream_ea_sbr_harmony(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE *sf_sbs = NULL, *sf_data = NULL;
uint64_t base_offset, sound_offset, offset, prev_offset;
uint32_t dset_id, dset_offset, num_values, num_fields, field_id,
data_offset, table_offset, set_sounds, sound_table_offset;
int16_t flag;
uint16_t num_dsets;
uint8_t set_type, offset_size;
char sound_name[STREAM_NAME_SIZE];
int target_stream = sf->stream_index, total_sounds, local_target, is_streamed = 0;
int i, j;
uint64_t(*read_u64)(off_t, STREAMFILE *);
uint32_t(*read_u32)(off_t, STREAMFILE*);
uint16_t(*read_u16)(off_t, STREAMFILE*);
eaac_meta_t info = {0};
/* checks */
if (is_id32be(0x00, sf, "SBle")) {
read_u64 = read_u64le;
read_u32 = read_u32le;
read_u16 = read_u16le;
}
else if (is_id32be(0x00, sf, "SBbe")) {
read_u64 = read_u64be;
read_u32 = read_u32be;
read_u16 = read_u16be;
}
else {
return NULL;
}
if (!check_extensions(sf, "sbr"))
return NULL;
num_dsets = read_u16(0x0a, sf);
table_offset = read_u32(0x18, sf);
data_offset = read_u32(0x20, sf);
if (target_stream == 0) target_stream = 1;
if (target_stream < 0)
goto fail;
total_sounds = 0;
sound_offset = 0;
/* the bank is split into DSET sections each of which references one or multiple sounds */
/* each set can contain RAM sounds (stored in SBR in data section) or streamed sounds (stored separately in SBS file) */
for (i = 0; i < num_dsets; i++) {
dset_offset = read_u32(table_offset + 0x08 * i, sf);
if (read_u32(dset_offset, sf) != get_id32be("DSET"))
goto fail;
dset_id = read_u32(dset_offset + 0x08, sf);
num_values = read_u32(dset_offset + 0x38, sf);
num_fields = read_u32(dset_offset + 0x3c, sf);
local_target = target_stream - total_sounds - 1;
dset_offset += 0x48;
/* find RAM or OFF field */
for (j = 0; j < num_fields; j++) {
field_id = read_u32(dset_offset, sf);
if (field_id == get_id32be(".RAM") ||
field_id == get_id32be(".OFF")) {
break;
}
dset_offset += 0x18;
}
if (j == num_fields)
goto fail;
/* different set types store offsets differently */
set_type = read_u8(dset_offset + 0x05, sf);
/* data sets often contain duplicate offets, need to filter them out however we can */
/* offsets are stored in ascending order which makes things easier */
if (set_type == 0x00) {
set_sounds = 1;
total_sounds += set_sounds;
if (local_target < 0 || local_target > 0)
continue;
sound_offset = read_u64(dset_offset + 0x08, sf);
}
else if (set_type == 0x01) {
flag = (int16_t)read_u16(dset_offset + 0x06, sf);
base_offset = read_u64(dset_offset + 0x08, sf);
set_sounds = num_values;
total_sounds += set_sounds;
if (local_target < 0 || local_target >= set_sounds)
continue;
sound_offset = base_offset + flag * local_target;
}
else if (set_type == 0x02) {
flag = (read_u16(dset_offset + 0x06, sf) >> 0) & 0xFF;
offset_size = (read_u16(dset_offset + 0x06, sf) >> 8) & 0xFF;
base_offset = read_u64(dset_offset + 0x08, sf);
sound_table_offset = read_u32(dset_offset + 0x10, sf);
set_sounds = 0;
prev_offset = UINT64_MAX;
for (j = 0; j < num_values; j++) {
if (offset_size == 0x01) {
offset = read_u8(sound_table_offset + 0x01 * j, sf);
}
else if (offset_size == 0x02) {
offset = read_u16(sound_table_offset + 0x02 * j, sf);
}
else if (offset_size == 0x04) {
offset = read_u32(sound_table_offset + 0x04 * j, sf);
}
else {
goto fail;
}
offset <<= flag;
offset += base_offset;
if (offset != prev_offset) {
if (set_sounds == local_target)
sound_offset = offset;
set_sounds++;
}
prev_offset = offset;
}
total_sounds += set_sounds;
if (local_target < 0 || local_target >= set_sounds)
continue;
}
else if (set_type == 0x03) {
offset_size = (read_u16(dset_offset + 0x06, sf) >> 8) & 0xFF;
set_sounds = read_u64(dset_offset + 0x08, sf);
sound_table_offset = read_u32(dset_offset + 0x10, sf);
total_sounds += set_sounds;
if (local_target < 0 || local_target >= set_sounds)
continue;
if (offset_size == 0x01) {
sound_offset = read_u8(sound_table_offset + 0x01 * local_target, sf);
}
else if (offset_size == 0x02) {
sound_offset = read_u16(sound_table_offset + 0x02 * local_target, sf);
}
else if (offset_size == 0x04) {
sound_offset = read_u32(sound_table_offset + 0x04 * local_target, sf);
}
else {
goto fail;
}
}
else if (set_type == 0x04) {
sound_table_offset = read_u32(dset_offset + 0x10, sf);
set_sounds = 0;
prev_offset = UINT64_MAX;
for (j = 0; j < num_values; j++) {
offset = read_u64(sound_table_offset + 0x08 * j, sf);
if (sound_offset != prev_offset) {
if (set_sounds == local_target)
sound_offset = offset;
set_sounds++;
}
prev_offset = offset;
}
total_sounds += set_sounds;
if (local_target < 0 || local_target >= set_sounds)
continue;
}
else {
goto fail;
}
snprintf(sound_name, STREAM_NAME_SIZE, "DSET %08x/%04d", dset_id, local_target);
if (field_id == get_id32be(".RAM")) {
is_streamed = 0;
}
else if (field_id == get_id32be(".OFF")) {
is_streamed = 1;
}
}
if (sound_offset == 0)
goto fail;
if (!is_streamed) {
/* RAM asset */
if (!is_id32be(data_offset, sf, "data") &&
!is_id32be(data_offset, sf, "DATA"))
goto fail;
sf_data = sf;
sound_offset += data_offset;
}
else {
/* streamed asset */
sf_sbs = open_streamfile_by_ext(sf, "sbs");
if (!sf_sbs) goto fail;
if (!is_id32be(0x00, sf_sbs, "data") &&
!is_id32be(0x00, sf_sbs, "DATA"))
goto fail;
sf_data = sf_sbs;
if (is_id32be(sound_offset, sf_data, "slot")) {
/* skip "slot" section */
sound_offset += 0x30;
}
}
info.sf_head = sf_data;
info.head_offset = sound_offset;
info.body_offset = 0x00;
info.type = meta_EA_SPS;
vgmstream = load_vgmstream_ea_eaac(&info);
if (!vgmstream) goto fail;
vgmstream->num_streams = total_sounds;
strncpy(vgmstream->stream_name, sound_name, STREAM_NAME_SIZE);
close_streamfile(sf_sbs);
return vgmstream;
fail:
close_streamfile(sf_sbs);
return NULL;
}

View file

@ -0,0 +1,82 @@
#include "meta.h"
#include "../util/endianness.h"
#define EAAC_BLOCKID1_HEADER 0x48 /* 'H' */
/* .SNR+SNS - from EA latest games (~2005-2010), v0 header */
VGMSTREAM* init_vgmstream_ea_snr_sns(STREAMFILE* sf) {
eaac_meta_t info = {0};
/* checks */
if (!check_extensions(sf,"snr"))
return NULL;
info.sf_head = sf;
info.head_offset = 0x00;
info.body_offset = 0x00;
info.type = meta_EA_SNR_SNS;
info.standalone = true;
return load_vgmstream_ea_eaac(&info);
}
/* .SPS - from EA latest games (~2010~present), v1 header */
VGMSTREAM* init_vgmstream_ea_sps(STREAMFILE* sf) {
eaac_meta_t info = {0};
/* checks */
if (read_u8(0x00, sf) != EAAC_BLOCKID1_HEADER) /* validated later but fails faster */
return NULL;
if (!check_extensions(sf,"sps"))
return NULL;
info.sf_head = sf;
info.head_offset = 0x00;
info.type = meta_EA_SPS;
info.standalone = true;
return load_vgmstream_ea_eaac(&info);
}
/* .SNU - from EA Redwood Shores/Visceral games (Dead Space, Dante's Inferno, The Godfather 2), v0 header */
VGMSTREAM* init_vgmstream_ea_snu(STREAMFILE* sf) {
eaac_meta_t info = {0};
read_u32_t read_u32 = NULL;
/* checks */
if (!check_extensions(sf,"snu"))
return NULL;
/* EA SNU header (BE/LE depending on platform) */
/* 0x00(1): related to sample rate? (03=48000)
* 0x01(1): flags/count? (when set has extra block data before start_offset)
* 0x02(1): always 0?
* 0x03(1): channels? (usually matches but rarely may be 0)
* 0x04(4): some size, maybe >>2 ~= number of frames
* 0x08(4): start offset
* 0x0c(4): some sub-offset? (0x20, found when @0x01 is set) */
/* use start offset as endianness flag */
read_u32 = guess_read_u32(0x08,sf);
uint32_t body_offset = read_u32(0x08,sf);
uint8_t block_id = read_u8(body_offset, sf);
if (block_id == EAAC_BLOCKID1_HEADER) {
/* Dead Space 3 (PC) */
info.sf_head = sf;
info.head_offset = body_offset; /* header also at 0x10, but useless in SPS */
info.type = meta_EA_SNU;
info.is_sps = true;
}
else {
info.sf_head = sf;
info.sf_body = sf;
info.head_offset = 0x10; /* SNR header */
info.body_offset = body_offset; /* SNR body */
info.type = meta_EA_SNU;
}
return load_vgmstream_ea_eaac(&info);
}

View file

@ -0,0 +1,71 @@
#include "meta.h"
#include "../util/endianness.h"
#include "../coding/coding.h"
/* EA TMX - used for engine sounds in NFS games (2007-2011) */
VGMSTREAM* init_vgmstream_ea_tmx(STREAMFILE* sf) {
uint32_t num_sounds, sound_type, table_offset, data_offset, entry_offset, sound_offset;
VGMSTREAM* vgmstream = NULL;
STREAMFILE* temp_sf = NULL;
int target_stream = sf->stream_index;
uint32_t(*read_u32)(off_t, STREAMFILE *);
/* checks */
if (is_id32be(0x0c, sf, "0001")) {
read_u32 = read_u32be;
}
else if (is_id32le(0x0c, sf, "1000")) {
read_u32 = read_u32le;
}
else {
return NULL;
}
if (!check_extensions(sf, "tmx"))
return NULL;
num_sounds = read_u32(0x20, sf);
table_offset = read_u32(0x58, sf);
data_offset = read_u32(0x5c, sf);
if (target_stream == 0) target_stream = 1;
if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds)
goto fail;
entry_offset = table_offset + (target_stream - 1) * 0x24;
sound_type = read_u32(entry_offset + 0x00, sf);
sound_offset = read_u32(entry_offset + 0x08, sf) + data_offset;
switch (sound_type) {
case 0x47494E20: /* "GIN " */
temp_sf = setup_subfile_streamfile(sf, sound_offset, get_streamfile_size(sf) - sound_offset, "gin");
if (!temp_sf) goto fail;
vgmstream = init_vgmstream_gin(temp_sf);
if (!vgmstream) goto fail;
close_streamfile(temp_sf);
break;
case 0x534E5220: { /* "SNR " */
eaac_meta_t info = {0};
info.sf_head = sf;
info.head_offset = sound_offset;
info.body_offset = 0x00;
info.type = meta_EA_SNR_SNS;
vgmstream = load_vgmstream_ea_eaac(&info);
if (!vgmstream) goto fail;
break;
}
default:
goto fail;
}
vgmstream->num_streams = num_sounds;
return vgmstream;
fail:
close_streamfile(temp_sf);
return NULL;
}

View file

@ -297,8 +297,9 @@ VGMSTREAM* init_vgmstream_ea_bnk(STREAMFILE* sf) {
* .sdt: Harry Potter games, Burnout games (PSP)
* .hdt/ldt: Burnout games (PSP)
* .abk: GoldenEye - Rogue Agent
* .ast: FIFA 2004 (inside .big) */
if (!check_extensions(sf,"bnk,sdt,hdt,ldt,abk,ast"))
* .ast: FIFA 2004 (inside .big)
* .cat: FIFA 2000 (PC, chant.cat) */
if (!check_extensions(sf,"bnk,sdt,hdt,ldt,abk,ast,cat"))
goto fail;
if (target_stream == 0) target_stream = 1;

View file

@ -84,7 +84,7 @@ VGMSTREAM* init_vgmstream_ego_dic(STREAMFILE* sf) {
if (sb == NULL) {
vgm_logi("DIC1: external file '%s' not found (put together)\n", resource_name);
/* allow missing as silence since some game use huge .dic that is a bit hard to get */
//goto fail;
codec = 0xFFFFFFFF; //goto fail;
}
}
@ -100,6 +100,13 @@ VGMSTREAM* init_vgmstream_ego_dic(STREAMFILE* sf) {
switch(codec) {
case 0xFFFFFFFF: //fake
vgmstream->coding_type = coding_SILENCE;
vgmstream->layout_type = layout_none;
vgmstream->num_samples = sample_rate;
break;
case 0x57495000: //WIP\0
vgmstream->coding_type = coding_PCM16LE;
vgmstream->layout_type = layout_interleave;
@ -143,10 +150,6 @@ VGMSTREAM* init_vgmstream_ego_dic(STREAMFILE* sf) {
vgmstream->loop_start_sample = 0;
vgmstream->loop_end_sample = vgmstream->num_samples;
if (!sb) {
vgmstream->coding_type = coding_SILENCE;
vgmstream->layout_type = layout_none;
}
if (!vgmstream_open_stream(vgmstream, sb, stream_offset))
goto fail;

View file

@ -154,7 +154,7 @@ static VGMSTREAM* init_vgmstream_encrypted_rpgmvo_riff(STREAMFILE* sf) {
e.key_size = 0x10;
load_key(&cfg, e.keybuf, e.key_size);
cfg.start = 0x10;
cfg.max_offset = 0x10;
cfg.max_offset = 0x20;
e.temp_sf = setup_ogg_vorbis_streamfile(sf, &cfg);
if (!e.temp_sf) goto fail;

View file

@ -3,7 +3,7 @@
#include "../util/chunks.h"
static int get_subsongs(STREAMFILE* sf, off_t fsb5_offset, size_t fsb5_size);
static int get_subsongs(STREAMFILE* sf, uint32_t fsb5_offset, uint32_t fsb5_size);
/* FEV+FSB5 container [Just Cause 3 (PC), Shantae: Half-Genie Hero (Switch)] */
VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) {
@ -99,11 +99,11 @@ VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) {
total_subsongs = 0;
for (i = 0; i < banks; i++) {
//TODO: fsb5_size fails for v0x28< + encrypted, but only used with multibanks = unlikely
off_t fsb5_offset = read_u32le(bank_offset + 0x04 + entry_size*i + 0x00,sf);
size_t fsb5_size = read_u32le(bank_offset+0x08 + entry_size*i,sf);
uint32_t fsb5_offset = read_u32le(bank_offset + 0x04 + entry_size*i + 0x00,sf);
uint32_t fsb5_size = read_u32le(bank_offset + 0x08 + entry_size*i,sf);
int fsb5_subsongs = get_subsongs(sf, fsb5_offset, fsb5_size);
if (!fsb5_subsongs) {
vgm_logi("FSB: couldn't load bank (encrypted?)\n");
vgm_logi("FSB: couldn't load bank %i at %x (encrypted?)\n", i, fsb5_offset);
goto fail;
}
@ -159,14 +159,13 @@ fail:
return NULL;
}
static int get_subsongs(STREAMFILE* sf, off_t fsb5_offset, size_t fsb5_size) {
static int get_subsongs(STREAMFILE* sf, uint32_t fsb5_offset, uint32_t fsb5_size) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* temp_sf = NULL;
int subsongs = 0;
/* standard */
if (read_u32be(fsb5_offset, sf) == 0x46534235) { /* FSB5 */
if (is_id32be(fsb5_offset, sf, "FSB5")) {
return read_s32le(fsb5_offset + 0x08,sf);
}
@ -174,6 +173,7 @@ static int get_subsongs(STREAMFILE* sf, off_t fsb5_offset, size_t fsb5_size) {
temp_sf = setup_subfile_streamfile(sf, fsb5_offset, fsb5_size, "fsb");
if (!temp_sf) goto end;
temp_sf->stream_index = 0;
vgmstream = init_vgmstream_fsb_encrypted(temp_sf);
if (!vgmstream) goto end;

View file

@ -1,7 +1,7 @@
#ifndef _FSB_ENCRYPTED_STREAMFILE_H_
#define _FSB_ENCRYPTED_STREAMFILE_H_
#define FSB_KEY_MAX 0x10000 //0x168
#define FSB_KEY_MAX 0x80 /* known max ~0x33 */
typedef struct {
@ -57,7 +57,7 @@ static STREAMFILE* setup_fsb_streamfile(STREAMFILE* sf, const uint8_t* key, size
size_t io_data_size = sizeof(fsb_decryption_data);
/* setup decryption with key (external) */
if (!key_size || key_size > FSB_KEY_MAX)
if (!key_size || key_size >= FSB_KEY_MAX)
return NULL;
memcpy(io_data.key, key, key_size);

View file

@ -21,7 +21,7 @@ typedef struct {
#define FLAG_FSB4 (1 << 0) /* key is valid for FSB4/3 */
#define FLAG_FSB5 (1 << 1) /* key is valid for FSB5 */
#define FLAG_STD (1 << 2) /* regular XOR mode */
#define FLAG_ALT (1 << 3) /* alt XOR mode (seemingly not tied to FSB version or anything, maybe wrong key) */
#define FLAG_ALT (1 << 3) /* alt XOR mode (seemingly older files or possibly FSB3 only) */
#define MODE_FSB4_STD (FLAG_FSB4 | FLAG_STD)
#define MODE_FSB4_ALT (FLAG_FSB4 | FLAG_ALT)
@ -55,10 +55,8 @@ static const fsbkey_info fsbkey_list[] = {
{ MODE_FSBS_ALL, FSBKEY_ADD("5atu6w4zaw") }, // Guitar Hero 3 [untested]
{ MODE_FSBS_ALL, FSBKEY_ADD("B2A7BB00") }, // Supreme Commander 2 [untested]
{ MODE_FSB4_STD, FSBKEY_ADD("ghfxhslrghfxhslr") }, // Cookie Run: Ovenbreak
{ MODE_FSB4_ALT, FSBKEY_ADD("truck/impact/carbody") },// Monster Jam (PS2) [FSB3]
{ MODE_FSB4_ALT, FSBKEY_ADD("truck/impact/carbody") }, // Monster Jam (PS2) [FSB3]
{ MODE_FSB4_ALT, FSBKEY_ADD("\xFC\xF9\xE4\xB3\xF5\x57\x5C\xA5\xAC\x13\xEC\x4A\x43\x19\x58\xEB\x4E\xF3\x84\x0B\x8B\x78\xFA\xFD\xBB\x18\x46\x7E\x31\xFB\xD0") }, // Guitar Hero 5 (X360)
{ MODE_FSB4_ALT, FSBKEY_ADD("\x8C\xFA\xF3\x14\xB1\x53\xDA\xAB\x2B\x82\x6B\xD5\x55\x16\xCF\x01\x90\x20\x28\x14\xB1\x53\xD8") }, // Guitar Hero: Metallica (X360)
{ MODE_FSB4_STD, FSBKEY_ADD("\xd2\x37\x70\x39\xa9\x86\xc5\xaf\x5b\x7f\xa2\x23\x98\x7e\xb6\xc2\x7e\x18\x7b\x2d\xd9\x31\x4b\x20\xb0\xc1\x8d\x06\xf2\xa7\xcd") }, // Guitar Hero: Metallica (PS3) [FSB4]
{ MODE_FSB5_STD, FSBKEY_ADD("G0KTrWjS9syqF7vVD6RaVXlFD91gMgkC") }, // Sekiro: Shadows Die Twice (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("BasicEncryptionKey") }, // SCP: Unity (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("FXnTffGJ9LS855Gc") }, // Worms Rumble Beta (PC)
@ -73,6 +71,16 @@ static const fsbkey_info fsbkey_list[] = {
{ MODE_FSB5_STD, FSBKEY_ADD("Aurogon666") }, // Afterimage demo (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("IfYouLikeThosesSoundsWhyNotRenumerateTheir2Authors?") }, // Blanc (PC/Switch)
{ MODE_FSB5_STD, FSBKEY_ADD("L36nshM520") }, // Nishuihan Mobile (Android)
{ MODE_FSB5_STD, FSBKEY_ADD("Forza2!") }, // Forza Motorsport (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("cbfjZTlUPaZI") }, // JDM: Japanese Drift Master (PC)
{ MODE_FSB4_ALT, FSBKEY_ADD("tkdnsem000") }, // Ys Online: The Call of Solum (PC) [FSB3] (alt key: 2ED62676CEA6B60C0C0C)
{ MODE_FSB4_STD, FSBKEY_ADD("4DxgpNV3pQLPD6GT7g9Gf6eWU7SXutGQ") }, // Test Drive: Ferrari Racing Legends (PC)
{ MODE_FSB4_STD, FSBKEY_ADD("AjaxIsTheGoodestBoy") }, // Hello Kitty: Island Adventure (iOS)
/* these games use a key per file, generated from the filename; could be possible to add them but there is a lot of songs,
so external .fsbkey may be better (use guessfsb 3.1 with --write-key-file or ) */
//{ MODE_FSB4_STD, FSBKEY_ADD("...") }, // Guitar Hero: Metallica (PC/PS3/X360) [FSB4]
//{ MODE_FSB4_STD, FSBKEY_ADD("...") }, // Guitar Hero: World Tour (PC/PS3/X360) [FSB4]
};
static const int fsbkey_list_count = sizeof(fsbkey_list) / sizeof(fsbkey_list[0]);

View file

@ -0,0 +1,57 @@
#include "meta.h"
#include "../coding/coding.h"
/* GbTs - from Konami/KCE Studio games [Pop'n Music 9/10 (PS2)] */
VGMSTREAM* init_vgmstream_gbts(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
uint32_t data_offset, data_size;
int loop_flag, channels, sample_rate;
uint32_t loop_start, loop_end;
/* checks */
if (!is_id32be(0x00,sf, "GbTs"))
return NULL;
/* .gbts: header id (no apparent exts) */
if (!check_extensions(sf, "gbts"))
return NULL;
/* 04: always 0x24 */
data_offset = read_u32le(0x08,sf);
data_size = read_u32le(0x0C,sf); /* without padding */
loop_start = read_u32le(0x10,sf); /* (0x20 = start frame if not set) */
loop_end = read_u32le(0x14,sf); /* (0x00 if not set) */
sample_rate = read_s32le(0x18,sf);
channels = read_s32le(0x1C,sf);
/* 20: 1? */
/* 24: block size (interleave * channels) */
/* 30+: empty */
loop_flag = (loop_end > 0);
loop_end += loop_start; /* loop region matches PS-ADPCM flags */
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = ps_bytes_to_samples(data_size, channels);
vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channels);
vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end, channels);
vgmstream->coding_type = coding_PSX;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x10;
vgmstream->meta_type = meta_GBTS;
if (!vgmstream_open_stream(vgmstream, sf, data_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View file

@ -1,96 +1,145 @@
#include "meta.h"
#include "../coding/coding.h"
#include "../util/endianness.h"
typedef enum { XMA2, ATRAC9 } gtd_codec;
//TODO rename gtd to ghs
/* GHS - Hexadrive's HexaEngine games [Knights Contract (X360), Valhalla Knights 3 (Vita)] */
typedef enum { PCM16LE, MSADPCM, XMA2, ATRAC9 } gtd_codec_t;
static void read_name(VGMSTREAM* vgmstream, STREAMFILE* sf, uint32_t offset);
/* GHS - Hexadrive's HexaEngine games [Gunslinger Stratos (AC), Knights Contract (X360), Valhalla Knights 3 (Vita)] */
VGMSTREAM* init_vgmstream_ghs(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset, chunk_offset, stpr_offset, name_offset = 0, loop_start_offset, loop_end_offset;
size_t data_size, chunk_size;
uint32_t stream_offset, stream_size, stpr_offset = 0, loop_start_offset = 0, loop_end_offset = 0;
uint32_t chunk_offset, chunk_size = 0, at9_config_data = 0, block_size = 0;
int loop_flag, channels, sample_rate;
int num_samples, loop_start_sample, loop_end_sample;
uint32_t at9_config_data;
gtd_codec codec;
int32_t num_samples, loop_start_sample, loop_end_sample;
gtd_codec_t codec;
int total_subsongs = 0, target_subsong = sf->stream_index;
/* checks */
if (!is_id32be(0x00,sf, "GHS "))
goto fail;
if ( !check_extensions(sf,"gtd"))
return NULL;
if (!check_extensions(sf,"gtd"))
return NULL;
int big_endian = guess_endian32(0x04,sf);
read_u32_t read_u32 = big_endian ? read_u32be : read_u32le;
read_u16_t read_u16 = big_endian ? read_u16be : read_u16le;
int is_old = 0x34 + read_u32le(0x30,sf) + read_u32le(0x0c,sf) == get_streamfile_size(sf);
total_subsongs = read_u32(0x04, sf); /* seen in sfx packs inside .ged */
if (!check_subsongs(&target_subsong, total_subsongs))
return NULL;
/* not seen */
if (target_subsong > 1 && is_old)
goto fail;
/* header version, not formally specified */
if (!is_old) {
/* 0x08: size of all seek tables (XMA2, all tables go together after headers) / null */
uint32_t offset = 0x0c + (target_subsong - 1) * 0x64;
/* header type, not formally specified */
if (read_32bitBE(0x04,sf) == 1 && read_16bitBE(0x0C,sf) == 0x0166) { /* XMA2 */
/* 0x08(4): seek table size */
chunk_offset = 0x0c; /* custom header with a "fmt " data chunk inside */
chunk_size = 0x34;
channels = read_16bitBE(chunk_offset+0x02,sf);
sample_rate = read_32bitBE(chunk_offset+0x04,sf);
xma2_parse_fmt_chunk_extra(sf, chunk_offset, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample, 1);
start_offset = read_32bitBE(0x58,sf); /* always 0x800 */
data_size = read_32bitBE(0x5c,sf);
/* 0x34(18): null, 0x54(4): seek table offset, 0x58(4): seek table size, 0x5c(8): null, 0x64: seek table */
stpr_offset = read_32bitBE(chunk_offset+0x54,sf) + read_32bitBE(chunk_offset+0x58,sf);
if (is_id32be(stpr_offset,sf, "STPR")) {
/* SRPR encases the original "S_P_STH" header (no data) */
name_offset = stpr_offset + 0xB8; /* there are offsets fields but seems to work */
int format = read_u16(offset + 0x00,sf);
if (format == 0x0001)
codec = PCM16LE; /* GS bgm */
else if (format == 0x0002)
codec = MSADPCM; /* GS sfx */
else if (format == 0x0166) {
codec = XMA2;
chunk_offset = offset; /* "fmt " */
chunk_size = 0x34;
}
else {
goto fail;
}
codec = XMA2;
}
else if (0x34 + read_32bitLE(0x30,sf) + read_32bitLE(0x0c,sf) == get_streamfile_size(sf)) { /* ATRAC9 */
/* 0x0c: standard fmt chunk (depending on format, reserved with padding up to 0x48 if needed) */
channels = read_u16(offset + 0x02,sf);
sample_rate = read_u32(offset + 0x04,sf);
block_size = read_u16(offset + 0x0c,sf);
/* loops can be found at 0x28/2c in PCM16 (also later) */
stream_offset = read_u32(offset + 0x4c,sf); /* always 0x800 */
stream_size = read_u32(offset + 0x50,sf);
/* 0x54: seek table offset (XMA2) / data start */
/* 0x58: seek table size (XMA2) / null */
loop_start_sample = read_u32(offset + 0x5c,sf); /* null in XMA2 */
loop_end_sample = read_u32(offset + 0x60,sf) + loop_start_sample; /* +1? */
data_size = read_32bitLE(0x0c,sf);
start_offset = 0x34 + read_32bitLE(0x30,sf);
channels = read_32bitLE(0x10,sf);
sample_rate = read_32bitLE(0x14,sf);
loop_start_offset = read_32bitLE(0x1c, sf);
loop_end_offset = read_32bitLE(0x20, sf);
loop_flag = loop_end_offset > loop_start_offset;
at9_config_data = read_32bitBE(0x28,sf);
/* 0x18-0x28: fixed/unknown values */
stpr_offset = 0x2c;
if (is_id32be(stpr_offset,sf, "STPR")) {
/* STPR encases the original "S_P_STH" header (no data) */
name_offset = stpr_offset + 0xE8; /* there are offsets fields but seems to work */
if (codec == XMA2) {
xma2_parse_fmt_chunk_extra(sf, chunk_offset, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample, 1);
}
else {
loop_flag = loop_end_sample != 0;
}
codec = ATRAC9;
stpr_offset = read_u32(offset + 0x54,sf) + read_u32(offset + 0x58,sf);
}
else {
goto fail;
}
codec = ATRAC9;
/* 08: always 02? */
stream_size = read_u32(0x0c,sf);
channels = read_u32(0x10,sf);
sample_rate = read_u32(0x14,sf);
/* 18: null? */
loop_start_offset = read_u32(0x1c,sf);
loop_end_offset = read_u32(0x20,sf);
/* 24: channel layout? */
at9_config_data = read_u32be(0x28,sf);
/* 2c: STPR */
stream_offset = read_u32(0x30,sf) + 0x34;
loop_flag = loop_end_offset > loop_start_offset;
stpr_offset = 0x2c;
}
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_GHS;
vgmstream->sample_rate = sample_rate;
vgmstream->loop_start_sample = loop_start_sample;
vgmstream->loop_end_sample = loop_end_sample;
vgmstream->meta_type = meta_GHS;
if (name_offset) //encoding is Shift-Jis in some PSV files
read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,sf);
vgmstream->loop_end_sample = loop_end_sample;
vgmstream->num_streams = total_subsongs;
vgmstream->stream_size = stream_size;
read_name(vgmstream, sf, stpr_offset);
switch(codec) {
case PCM16LE:
vgmstream->coding_type = coding_PCM16LE;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = block_size / channels;
vgmstream->num_samples = pcm16_bytes_to_samples(stream_size, channels);
break;
case MSADPCM:
vgmstream->coding_type = coding_MSADPCM;
vgmstream->layout_type = layout_interleave;
vgmstream->frame_size = block_size;
vgmstream->num_samples = msadpcm_bytes_to_samples(stream_size, block_size, channels);
break;
#ifdef VGM_USE_FFMPEG
case XMA2:
vgmstream->codec_data = init_ffmpeg_xma_chunk(sf, start_offset, data_size, chunk_offset, chunk_size);
vgmstream->codec_data = init_ffmpeg_xma_chunk(sf, stream_offset, stream_size, chunk_offset, chunk_size);
if ( !vgmstream->codec_data ) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
vgmstream->num_samples = num_samples;
xma_fix_raw_samples(vgmstream, sf, start_offset, data_size, chunk_offset, 1,1);
xma_fix_raw_samples(vgmstream, sf, stream_offset, stream_size, chunk_offset, 1,1);
break;
#endif
#ifdef VGM_USE_ATRAC9
@ -105,13 +154,12 @@ VGMSTREAM* init_vgmstream_ghs(STREAMFILE* sf) {
vgmstream->coding_type = coding_ATRAC9;
vgmstream->layout_type = layout_none;
if (loop_flag) {
vgmstream->loop_start_sample = atrac9_bytes_to_samples(loop_start_offset - start_offset, vgmstream->codec_data);
vgmstream->loop_end_sample = atrac9_bytes_to_samples(loop_end_offset - start_offset, vgmstream->codec_data);
vgmstream->loop_start_sample = atrac9_bytes_to_samples(loop_start_offset - stream_offset, vgmstream->codec_data);
vgmstream->loop_end_sample = atrac9_bytes_to_samples(loop_end_offset - stream_offset, vgmstream->codec_data);
}
vgmstream->num_samples = atrac9_bytes_to_samples(data_size, vgmstream->codec_data);
vgmstream->num_samples = atrac9_bytes_to_samples(stream_size, vgmstream->codec_data);
break;
}
#endif
default:
@ -119,7 +167,7 @@ VGMSTREAM* init_vgmstream_ghs(STREAMFILE* sf) {
}
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
if (!vgmstream_open_stream(vgmstream, sf, stream_offset))
goto fail;
return vgmstream;
fail:
@ -131,14 +179,14 @@ fail:
VGMSTREAM* init_vgmstream_s_p_sth(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* temp_sf = NULL;
uint32_t subfile_offset, subfile_size, name_offset;
uint32_t subfile_offset, subfile_size, stpr_offset;
/* checks */
if (!is_id64be(0x00,sf,"S_P_STH\x01"))
goto fail;
return NULL;
if (!check_extensions(sf,"gtd"))
goto fail;
return NULL;
subfile_offset = read_u32be(0x08, sf);
subfile_size = get_streamfile_size(sf) - subfile_offset;
@ -150,8 +198,52 @@ VGMSTREAM* init_vgmstream_s_p_sth(STREAMFILE* sf) {
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_GHS;
name_offset = 0xB0; /* there are offsets fields but seems to work */
read_string(vgmstream->stream_name, STREAM_NAME_SIZE, name_offset, sf);
stpr_offset = 0x00;
read_name(vgmstream, sf, stpr_offset);
close_streamfile(temp_sf);
return vgmstream;
fail:
close_streamfile(temp_sf);
close_vgmstream(vgmstream);
return NULL;
}
/* S_PACK - Hexadrive's HexaEngine games [Gunslinger Stratos (PC), Knights Contract (X360)] */
VGMSTREAM* init_vgmstream_s_pack(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* temp_sf = NULL;
/* checks */
if (!is_id64be(0x00,sf,"S_PACK\x00\x00") && !is_id64be(0x00,sf,"S_PACK\x00\x01")) /* v1: KC */
return NULL;
if (!check_extensions(sf,"ged"))
return NULL;
/* 0x08: file size */
/* 0x0c-0x20: null */
int big_endian = guess_endian32(0x20,sf);
read_u32_t read_u32 = big_endian ? read_u32be : read_u32le;
uint32_t offset = read_u32(0x20, sf); /* offset to minitable */
/* 0x24: number of chunks in S_P_H? */
/* 0x28: number of entries in minitable */
/* minitable */
/* 0x00: offset to "S_P_H", that seems to have cuenames (may have more cues than waves though) */
uint32_t subfile_offset = read_u32(offset + 0x04, sf); /* may be null or S_CHR_M (no GHS in file) */
uint32_t schar_offset = read_u32(offset + 0x08, sf); /* S_CHR_M seen in KC, some kind of cues */
if (schar_offset == 0)
schar_offset = get_streamfile_size(sf);
uint32_t subfile_size = schar_offset - subfile_offset;
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, "gtd");
if (!temp_sf) goto fail;
vgmstream = init_vgmstream_ghs(temp_sf);
if (!vgmstream) goto fail;
close_streamfile(temp_sf);
return vgmstream;
@ -161,3 +253,41 @@ fail:
close_vgmstream(vgmstream);
return NULL;
}
static void read_name(VGMSTREAM* vgmstream, STREAMFILE* sf, uint32_t offset) {
uint32_t name_offset = 0;
//if (!offset) //may be 0 in PS3
// return;
if (is_id32be(offset,sf, "STPR"))
offset += 0x08;
if (is_id64be(offset + 0x00,sf, "S_P_STH\0")) { /* stream header v0: GS, VK3 */
/* 08 subheader size */
/* 0c version/count? */
/* 10 version/count? */
/* 20 offset to header configs */
/* 24 hash? */
/* 2c -1? */
/* 30 1? */
if (!is_id64be(offset + 0x40,sf, "stream\0\0"))
return;
/* 50 bank name */
/* 70+ header configs (some are repeats from GHS) */
/* E0 file name .gtd */
name_offset = offset + 0xE0; /* show file name though actual files are already (bankname)_(filename).gtd */
}
else if (is_id64be(offset + 0x00,sf, "S_P_STH\1")) { /* stream header v1: KC */
/* same as above, except no stream+bank name, so at 0x40 are header configs */
name_offset = offset + 0xB0;
}
/* optional (only found in streams, sfx packs that point to GHS have cue names) */
if (!name_offset)
return;
//TO-DO: Shift-Jis in some Vita files
read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,sf);
}

View file

@ -0,0 +1,149 @@
#include "meta.h"
#include "../coding/coding.h"
/* GWB+GWD - Ubisoft bank [Monster 4x4: World Circuit (Wii)] */
VGMSTREAM* init_vgmstream_gwb_gwd(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* sf_body = NULL;
uint32_t stream_offset = 0, stream_size = 0, coef_offset;
int loop_flag, channels, sample_rate, interleave = 0;
uint32_t loop_start, loop_end;
int total_subsongs, target_subsong = sf->stream_index;
/* checks */
int version = read_u8(0x00, sf);
if (version != 6 && version != 7)
return NULL;
if (read_u32be(0x01, sf) > 0x0400) /* ID, max seen */
return NULL;
if (get_streamfile_size(sf) > 0x2000) /* arbitrary max */
return NULL;
if (!check_extensions(sf,"gwb"))
return NULL;
/* format (vaguely similar to ubi's hx and such banks)
* common
* 00: version (06/07, both found in the same game)
* 01: file ID (low number: 0x0001, 0x0342...)
* v6:
* 05: subsongs
* v7
* 05: null
* 09: subsongs
*
* per subsong:
* - 00: flags: (v6: 09=stereo, 02=mono; v7: 0a=stereo)
* - 01: id (ex. 0x0002, 0x0343...)
* v6
* - 05: 0x4a header * channels
* v7
* - 05: always 0x02?
* - 09: stream offset
* - 0d: stream size
* - 11: always 5
* - 15: 0x4a header * channels
*
* per header:
* - 00: loop flag
* - 04: sample rate
* - 08: loop start nibbles
* - 0c: loop end nibbles
* - 10: end nibble
* - 14: start nibble (after DSP frame header, so uses 0x02 at file start)
* - 18: null
* - 1c: coefs + gain + initial ps/hists + loop ps/hists
* Data in .gwd is N headerless DSPs. All nibble values are absolute within the file. */
uint32_t offset = version == 6 ? 0x05 : 0x09;
total_subsongs = read_s32be(offset, sf);
if (target_subsong == 0) target_subsong = 1;
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) return NULL;
offset += 0x04;
/* find target header */
for (int i = 0; i < total_subsongs; i++) {
if (i + 1 == target_subsong)
break;
uint8_t type = read_u8(offset + 0x00, sf);
if (type != 0x0a && type != 0x09 && type != 0x02)
goto fail;
offset += 0x05 + (version == 7 ? 0x10 : 0);
offset += 0x4a * (type & 0x08 ? 2 : 1);
}
/* header */
{
uint32_t st_nibble, ed_nibble, ls_nibble, le_nibble;
uint8_t type = read_u8(offset + 0x00, sf);
channels = (type & 0x08 ? 2 : 1);
offset += 0x05;
if (version == 7) {
stream_offset = read_u32be(offset + 0x04, sf);
stream_size = read_u32be(offset + 0x08, sf);
interleave = 0x4000;
offset += 0x10;
}
loop_flag = read_u32be(offset + 0x00, sf) == 1;
sample_rate = read_u32be(offset + 0x04, sf);
ls_nibble = read_u32be(offset + 0x08, sf);
le_nibble = read_u32be(offset + 0x0c, sf);
ed_nibble = read_u32be(offset + 0x10, sf);
st_nibble = read_u32be(offset + 0x14, sf);
coef_offset = offset + 0x1c;
if (version == 6) {
stream_offset = ((st_nibble - 2) / 2);
stream_size = ((ed_nibble - st_nibble - 2) / 2) * channels;
/* stereo repeats loop flag/sample rate/offsets/etc but simplify */
if (channels == 2) {
uint32_t s2_nibble = read_u32be(offset + 0x4a + 0x14, sf);
interleave = (s2_nibble - st_nibble) / 2;
}
}
loop_start = ((ls_nibble - 2) / 2 - stream_offset);
loop_end = ((le_nibble) / 2 - stream_offset) * channels;
}
/* files also have an optional companion .gsb with volume/etc config that seems to be adapted from Xbox's .xsb */
sf_body = open_streamfile_by_ext(sf, "gwd");
if (!sf_body) goto fail;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_GWB_GWD;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = dsp_bytes_to_samples(stream_size, channels);
vgmstream->loop_start_sample = dsp_bytes_to_samples(loop_start, channels);
vgmstream->loop_end_sample = dsp_bytes_to_samples(loop_end, channels);;
vgmstream->num_streams = total_subsongs;
vgmstream->stream_size = stream_size;
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = interleave;
dsp_read_coefs_be(vgmstream, sf, coef_offset + 0x00, 0x4a);
dsp_read_hist_be (vgmstream, sf, coef_offset + 0x24, 0x4a);
if (!vgmstream_open_stream(vgmstream, sf_body, stream_offset))
goto fail;
close_streamfile(sf_body);
return vgmstream;
fail:
close_streamfile(sf_body);
close_vgmstream(vgmstream);
return NULL;
}

View file

@ -1254,6 +1254,30 @@ static const hcakey_info hcakey_list[] = {
// BlazBlue Entropy Effect (Early Access) (PC)
{29814655674508831}, // 0069EC457894661F
// Star Ocean: The Second Story R (PC, Switch)
{533948357975462459}, // 0768F733DD87D23B
// Girls' Frontline 2: Exilium (Android)
{8930254087621254}, // 001FBA04CEA58A86
// Sonic Superstars (Switch)
{1991062320230623}, // 000712DC5252B0DF
// Persona 5 Tactica (Switch)
{48319776512953016}, // 00ABAA94AAAE4AB8
// THE IDOLM@STER Shiny Colors Song For Prism (PC)
{156967709847897761}, // 022DA94CEAB0C6A1
// Dokapon Kingdom Connect (PC, Switch)
{104863924750642073}, // 01748d2f1883eb99
// Girls' Frontline 2: Exilium (PC)
{7152097263845921}, // 001968CB68CF8221
// Girls' Frontline 2: Exilium (PC)
{4079616028775461768}, // 389DB529D726B388
};
#endif/*_HCA_KEYS_H_*/

View file

@ -389,7 +389,7 @@ VGMSTREAM* init_vgmstream_bsnf(STREAMFILE* sf) {
if (stream_size != get_streamfile_size(sb))
goto fail;
loop_flag = (loop_start > 0);
loop_flag = (loop_start > 0); /* loops from 0 on some codecs aren't detectable though */
start_offset = 0x00;
/* build the VGMSTREAM */
@ -404,13 +404,18 @@ VGMSTREAM* init_vgmstream_bsnf(STREAMFILE* sf) {
vgmstream->num_streams = num_languages;
strncpy(vgmstream->stream_name, language, STREAM_NAME_SIZE);
/* for codecs with explicit encoder delay (mp3/at9/etc) num_samples includes it
* ex. mus_c05_dream_doorloop_* does full loops; with some codecs loop start is encoder delay and num_samples
* has extra delay samples compared to codecs with implicit delay (ex. mp3 1152 to 101152 vs ogg 0 to 100000),
* but there is no header value for encoder delay, maybe engine hardcodes it? */
switch (codec) {
#ifdef VGM_USE_MPEG
case 0x0055: {
mpeg_custom_config cfg = { 0 };
cfg.skip_samples = 1152; /* seems ok */
//cfg.skip_samples = 1152; /* observed default */
vgmstream->codec_data = init_mpeg_custom(sb, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_STANDARD, &cfg);
if (!vgmstream->codec_data) goto fail;
@ -451,14 +456,20 @@ VGMSTREAM* init_vgmstream_bsnf(STREAMFILE* sf) {
case 0x42D2: {
atrac9_config cfg = { 0 };
/* extra offset: RIFF fmt extra data (extra size, frame size, GUID, etc), no fact samples/delay */
cfg.channels = vgmstream->channels;
cfg.encoder_delay = read_u16le(extra_offset + 0x02, sf);
//cfg.encoder_delay = read_u16le(extra_offset + 0x02, sf) / 4; /* seemingly one subframe = 256 */
cfg.config_data = read_u32be(extra_offset + 0x1c, 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 -= cfg.encoder_delay;
vgmstream->loop_start_sample -= cfg.encoder_delay;
vgmstream->loop_end_sample -= cfg.encoder_delay;
break;
}
#endif

View file

@ -1,76 +0,0 @@
#include "meta.h"
#include "../util.h"
/* PSND (from Crash Bandicoot Nitro Kart 2 (iOS) */
VGMSTREAM * init_vgmstream_ios_psnd(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
char filename[PATH_LIMIT];
off_t start_offset;
int loop_flag;
int channel_count;
/* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename));
if (strcasecmp("psnd",filename_extension(filename))) goto fail;
/* check header */
if (read_32bitBE(0x00,streamFile) != 0x50534E44) /* "PSND" */
goto fail;
if (read_16bitBE(0xC,streamFile)==0x2256){
loop_flag = 1;
}
else {
loop_flag = 0;
}
channel_count = read_8bit(0xE,streamFile);
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
/* fill in the vital statistics */
start_offset = 0x10;
vgmstream->channels = channel_count;
if (read_16bitBE(0xC,streamFile)==0x44AC){
vgmstream->sample_rate = 44100;
}
else {
vgmstream->sample_rate = read_16bitLE(0xC,streamFile);
}
vgmstream->coding_type = coding_PCM16LE;
vgmstream->num_samples = (read_32bitLE(0x4,streamFile)-8)/4;
if (loop_flag) {
vgmstream->loop_start_sample = 0;
vgmstream->loop_end_sample = vgmstream->num_samples;
}
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 2;
vgmstream->meta_type = meta_IOS_PSND;
/* open the file for reading */
{
int i;
STREAMFILE * file;
file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (!file) goto fail;
for (i=0;i<channel_count;i++) {
vgmstream->ch[i].streamfile = file;
vgmstream->ch[i].channel_start_offset=
vgmstream->ch[i].offset=start_offset+
vgmstream->interleave_block_size*i;
}
}
return vgmstream;
/* clean up anything we may have opened */
fail:
if (vgmstream) close_vgmstream(vgmstream);
return NULL;
}

View file

@ -14,8 +14,7 @@ VGMSTREAM* init_vgmstream_ktsc(STREAMFILE* sf) {
/* checks */
if (!is_id32be(0x00, sf, "KTSC"))
goto fail;
if (read_u32be(0x04, sf) != 0x01000001) /* version? */
goto fail;
/* 0x04: version? (0x01000001: common, 0x01000004: FE Three Houses) */
/* .ktsl2asbin: common [Atelier Ryza (PC)]
* .asbin: Warriors Orochi 4 (PC) (assumed) */

View file

@ -1,18 +1,26 @@
#include "meta.h"
#include "../coding/coding.h"
#include "../layout/layout.h"
#include "../util/companion_files.h"
#include "ktsr_streamfile.h"
typedef enum { NONE, MSADPCM, DSP, GCADPCM, ATRAC9, RIFF_ATRAC9, KOVS, KTSS, } ktsr_codec;
#define MAX_CHANNELS 8
typedef struct {
uint32_t base_offset;
bool is_srsa;
int total_subsongs;
int target_subsong;
ktsr_codec codec;
uint32_t audio_id;
int platform;
int format;
uint32_t sound_id;
uint32_t sound_flags;
uint32_t config_flags;
int channels;
int sample_rate;
@ -31,32 +39,73 @@ typedef struct {
char name[255+1];
} ktsr_header;
static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, bool is_srsa);
static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf);
static layered_layout_data* build_layered_atrac9(ktsr_header* ktsr, STREAMFILE *sf, uint32_t config_data);
static VGMSTREAM* init_vgmstream_ktsr_sub(STREAMFILE* sf_b, ktsr_header* ktsr, VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf), const char* ext);
/* KTSR - Koei Tecmo sound resource container */
VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) {
/* checks */
if (!is_id32be(0x00, sf, "KTSR"))
return NULL;
/* others: see internal */
/* .ktsl2asbin: common [Atelier Ryza (PC/Switch), Nioh (PC)]
* .asbin: Warriors Orochi 4 (PC) */
if (!check_extensions(sf, "ktsl2asbin,asbin"))
return NULL;
return init_vgmstream_ktsr_internal(sf, false) ;
}
/* ASRS - container of KTSR found in newer games */
VGMSTREAM* init_vgmstream_asrs(STREAMFILE* sf) {
/* checks */
if (!is_id32be(0x00, sf, "ASRS"))
return NULL;
/* 0x04: null */
/* 0x08: file size */
/* 0x0c: null */
/* .srsa: header id (as generated by common tools, probably "(something)asbin") */
if (!check_extensions(sf, "srsa"))
return NULL;
/* mini-container of memory-KTSR (streams also have a "TSRS" for stream-KTSR).
* .srsa/srst usually have hashed filenames, so it isn't easy to match them, so this
* is mainly useful for .srsa with internal streams. */
return init_vgmstream_ktsr_internal(sf, true);
}
static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, bool is_srsa) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* sf_b = NULL;
ktsr_header ktsr = {0};
int target_subsong = sf->stream_index;
int separate_offsets = 0;
ktsr.is_srsa = is_srsa;
if (ktsr.is_srsa) {
ktsr.base_offset = 0x10;
}
/* checks */
if (!is_id32be(0x00, sf, "KTSR"))
goto fail;
if (read_u32be(0x04, sf) != 0x777B481A) /* hash(?) id: 0x777B481A=as, 0x0294DDFC=st, 0xC638E69E=gc */
goto fail;
/* .ktsl2asbin: common [Atelier Ryza (PC/Switch), Nioh (PC)]
* .asbin: Warriors Orochi 4 (PC) */
if (!check_extensions(sf, "ktsl2asbin,asbin"))
goto fail;
if (!is_id32be(ktsr.base_offset + 0x00, sf, "KTSR"))
return NULL;
if (read_u32be(ktsr.base_offset + 0x04, sf) != 0x777B481A) /* hash id: 0x777B481A=as, 0x0294DDFC=st, 0xC638E69E=gc */
return NULL;
/* KTSR can be a memory file (ktsl2asbin), streams (ktsl2stbin) and global config (ktsl2gcbin)
* This accepts .ktsl2asbin with internal data or external streams as subsongs.
* Some info from KTSR.bt */
* Hashes are meant to be read LE, but here are BE for easier comparison (they probably correspond
* to some text but are pre-hashed in exes). Some info from KTSR.bt */
if (target_subsong == 0) target_subsong = 1;
ktsr.target_subsong = target_subsong;
@ -66,11 +115,22 @@ VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) {
/* open companion body */
if (ktsr.is_external) {
const char* companion_ext = check_extensions(sf, "asbin") ? "stbin" : "ktsl2stbin";
sf_b = open_streamfile_by_ext(sf, companion_ext);
if (ktsr.is_srsa) {
/* try parsing TXTM if present, since .srsa+srst have hashed names and don't match unless renamed */
sf_b = read_filemap_file(sf, 0);
}
if (!sf_b) {
vgm_logi("KTSR: companion file '*.%s' not found\n", companion_ext);
goto fail;
/* try (name).(ext), as seen in older games */
const char* companion_ext = check_extensions(sf, "asbin") ? "stbin" : "ktsl2stbin";
if (ktsr.is_srsa)
companion_ext = "srst";
sf_b = open_streamfile_by_ext(sf, companion_ext);
if (!sf_b) {
vgm_logi("KTSR: companion file '*.%s' not found\n", companion_ext);
goto fail;
}
}
}
else {
@ -172,15 +232,20 @@ fail:
return NULL;
}
// TODO improve, unity with other metas that do similar stuff
// TODO improve, unify with other metas that do similar stuff
static VGMSTREAM* init_vgmstream_ktsr_sub(STREAMFILE* sf_b, ktsr_header* ktsr, VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf), const char* ext) {
VGMSTREAM* sub_vgmstream = NULL;
STREAMFILE* temp_sf = setup_subfile_streamfile(sf_b, ktsr->stream_offsets[0], ktsr->stream_sizes[0], ext);
STREAMFILE* temp_sf = NULL;
temp_sf = setup_ktsr_streamfile(sf_b, ktsr->is_external, ktsr->stream_offsets[0], ktsr->stream_sizes[0], ext);
if (!temp_sf) return NULL;
sub_vgmstream = init_vgmstream(temp_sf);
close_streamfile(temp_sf);
if (!sub_vgmstream) return NULL;
if (!sub_vgmstream) {
VGM_LOG("ktsr: can't open subfile at %x (size %x)\n", ktsr->stream_offsets[0], ktsr->stream_sizes[0]);
return NULL;
}
sub_vgmstream->stream_size = ktsr->stream_sizes[0];
sub_vgmstream->num_streams = ktsr->total_subsongs;
@ -253,6 +318,7 @@ static int parse_codec(ktsr_header* ktsr) {
/* platform + format to codec, simplified until more codec combos are found */
switch(ktsr->platform) {
case 0x01: /* PC */
case 0x05: /* PC/Steam [Fate/Samurai Remnant (PC)] */
if (ktsr->is_external) {
if (ktsr->format == 0x0005)
ktsr->codec = KOVS; // Atelier Ryza (PC)
@ -284,11 +350,13 @@ static int parse_codec(ktsr_header* ktsr) {
if (ktsr->is_external) {
if (ktsr->format == 0x0005)
ktsr->codec = KTSS; // [Ultra Kaiju Monster Rancher (Switch)]
else if (ktsr->format == 0x1000)
ktsr->codec = KTSS; // [Fire Emblem: Three Houses (Switch)-some DSP voices]
else
goto fail;
}
else if (ktsr->format == 0x0000)
ktsr->codec = DSP; // Fire Emblem: Three Houses (Switch)
ktsr->codec = DSP; // [Fire Emblem: Three Houses (Switch)]
else
goto fail;
break;
@ -318,9 +386,11 @@ static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, uint32_t offset
case 0x3DEA478D: /* external [Nioh (PC)] (smaller) */
case 0xDF92529F: /* external [Atelier Ryza (PC)] */
case 0x6422007C: /* external [Atelier Ryza (PC)] */
case 0x793A1FD7: /* external [Stranger of Paradise (PS4)]-encrypted */
case 0xA0F4FC6C: /* external [Stranger of Paradise (PS4)]-encrypted */
/* 08 subtype? (ex. 0x522B86B9)
* 0c channels
* 10 ? (always 0x002706B8)
* 10 ? (always 0x002706B8 / 7864523D in SoP)
* 14 external codec
* 18 sample rate
* 1c num samples
@ -420,16 +490,53 @@ fail:
return 0;
}
/* ktsr engine reads+decrypts in the same func based on passed flag tho (reversed from exe)
* Strings are usually ASCII but Shift-JIS is used in rare cases (0x0c3e2edf.srsa) */
static void decrypt_string_ktsr(char* buf, size_t buf_size, uint32_t seed) {
for (int i = 0; i < buf_size - 1; i++) {
if (!buf[i]) /* just in case */
break;
seed = 0x343FD * seed + 0x269EC3; /* classic rand */
buf[i] ^= (seed >> 16) & 0xFF; /* 3rd byte */
if (!buf[i]) /* end null is also encrypted (but there are extra nulls after it anyway) */
break;
}
}
/* like read_string but allow any value since it can be encrypted */
static size_t read_string_ktsr(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf) {
int pos;
for (pos = 0; pos < buf_size; pos++) {
uint8_t byte = read_u8(offset + pos, sf);
char c = (char)byte;
if (buf) buf[pos] = c;
if (c == '\0')
return pos;
if (pos+1 == buf_size) {
if (buf) buf[pos] = '\0';
return buf_size;
}
}
return 0;
}
static void build_name(ktsr_header* ktsr, STREAMFILE* sf) {
char sound_name[255] = {0};
char config_name[255] = {0};
/* names can be different or same but usually config is better */
if (ktsr->sound_name_offset) {
read_string(sound_name, sizeof(sound_name), ktsr->sound_name_offset, sf);
read_string_ktsr(sound_name, sizeof(sound_name), ktsr->sound_name_offset, sf);
if (ktsr->sound_flags & 0x0008)
decrypt_string_ktsr(sound_name, sizeof(sound_name), ktsr->audio_id);
}
if (ktsr->config_name_offset) {
read_string(config_name, sizeof(config_name), ktsr->config_name_offset, sf);
read_string_ktsr(config_name, sizeof(config_name), ktsr->config_name_offset, sf);
if (ktsr->config_flags & 0x0200)
decrypt_string_ktsr(config_name, sizeof(config_name), ktsr->audio_id);
}
//if (longname[0] && shortname[0]) {
@ -445,22 +552,24 @@ static void build_name(ktsr_header* ktsr, STREAMFILE* sf) {
}
static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf, uint32_t target_id) {
static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf) {
/* more configs than sounds is possible so we need target_id first */
uint32_t offset, end, name_offset;
uint32_t stream_id;
offset = 0x40;
end = get_streamfile_size(sf);
offset = 0x40 + ktsr->base_offset;
end = get_streamfile_size(sf) - ktsr->base_offset;
while (offset < end) {
uint32_t type = read_u32be(offset + 0x00, sf); /* hash-id? */
uint32_t size = read_u32le(offset + 0x04, sf);
switch(type) {
case 0xBD888C36: /* config */
stream_id = read_u32be(offset + 0x08, sf);
if (stream_id != target_id)
stream_id = read_u32le(offset + 0x08, sf);
if (stream_id != ktsr->sound_id)
break;
ktsr->config_flags = read_u32le(offset + 0x0c, sf);
name_offset = read_u32le(offset + 0x28, sf);
if (name_offset > 0)
ktsr->config_name_offset = offset + name_offset;
@ -476,14 +585,14 @@ static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf, uint32_t target_id
static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
uint32_t offset, end, header_offset, name_offset;
uint32_t stream_id = 0, stream_count;
uint32_t stream_count;
/* 00: KTSR
* 04: type
* 08: version?
* 0a: unknown (usually 00, 02/03 seen in Vita)
* 0b: platform (01=PC, 03=Vita, 04=Switch)
* 0c: game id?
* 0c: audio id? (seen in multiple files/games and used as Ogg stream IDs)
* 10: null
* 14: null
* 18: file size
@ -491,25 +600,28 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
* up to 40: reserved
* until end: entries (totals not defined) */
ktsr->platform = read_u8(0x0b,sf);
ktsr->platform = read_u8(ktsr->base_offset + 0x0b,sf);
ktsr->audio_id = read_u32le(ktsr->base_offset + 0x0c,sf);
if (read_u32le(0x18, sf) != read_u32le(0x1c, sf))
if (read_u32le(ktsr->base_offset + 0x18, sf) != read_u32le(ktsr->base_offset + 0x1c, sf))
goto fail;
if (read_u32le(0x1c, sf) != get_streamfile_size(sf))
if (read_u32le(ktsr->base_offset + 0x1c, sf) != get_streamfile_size(sf) - ktsr->base_offset) {
vgm_logi("KTSR: incorrect file size (bad rip?)\n");
goto fail;
}
offset = 0x40;
end = get_streamfile_size(sf);
offset = 0x40 + ktsr->base_offset;
end = get_streamfile_size(sf) - ktsr->base_offset;
while (offset < end) {
uint32_t type = read_u32be(offset + 0x00, sf); /* hash-id? */
uint32_t size = read_u32le(offset + 0x04, sf);
/* parse chunk-like subfiles, usually N configs then N songs */
switch(type) {
case 0x6172DBA8: /* padding (empty) */
case 0xBD888C36: /* config (floats, stream id, etc, may have extended name) */
case 0x6172DBA8: /* ? (mostly empty) */
case 0xBD888C36: /* cue? (floats, stream id, etc, may have extended name; can have sub-chunks)-appears N times */
case 0xC9C48EC1: /* unknown (has some string inside like "boss") */
case 0xA9D23BF1: /* "state container", some kind of config/floats, witgh names like 'State_bgm01'..N */
case 0xA9D23BF1: /* "state container", some kind of config/floats, with names like 'State_bgm01'..N */
case 0x836FBECA: /* unknown (~0x300, encrypted? table + data) */
break;
@ -517,9 +629,10 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
ktsr->total_subsongs++;
/* sound table:
* 08: stream id (used in several places)
* 0c: unknown (low number but not version?)
* 0e: external flag
* 08: current/stream id (used in several places)
* 0c: flags (sounds only; other types are similar but different bits)
* 0x08: encrypted names (only used after ASRS was introduced?)
* 0x10000: external flag
* 10: sub-streams?
* 14: offset to header offset
* 18: offset to name
@ -528,11 +641,10 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
* --: header
* --: subheader (varies) */
if (ktsr->total_subsongs == ktsr->target_subsong) {
stream_id = read_u32be(offset + 0x08,sf);
//ktsr->is_external = read_u16le(offset + 0x0e,sf);
ktsr->sound_id = read_u32le(offset + 0x08,sf);
ktsr->sound_flags = read_u32le(offset + 0x0c,sf);
stream_count = read_u32le(offset + 0x10,sf);
if (stream_count != 1) {
VGM_LOG("ktsr: unknown stream count\n");
@ -563,9 +675,18 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
if (ktsr->target_subsong > ktsr->total_subsongs)
goto fail;
parse_longname(ktsr, sf, stream_id);
parse_longname(ktsr, sf);
build_name(ktsr, sf);
/* skip TSRS header (internals are pre-adjusted) */
if (ktsr->is_external && ktsr->base_offset) {
for (int i = 0; i < ktsr->channels; i++) {
ktsr->stream_offsets[i] += ktsr->base_offset;
}
ktsr->extra_offset += ktsr->base_offset; /* ? */
}
return 1;
fail:
vgm_logi("KTSR: unknown variation (report)\n");

View file

@ -0,0 +1,152 @@
#ifndef _KTSR_STREAMFILE_H_
#define _KTSR_STREAMFILE_H_
#include "../streamfile.h"
#include "../util/log.h"
#include "../util/cipher_blowfish.h"
/* decrypts blowfish in realtime (as done by games) */
typedef struct {
uint8_t key[0x20];
uint8_t block[0x08];
blowfish_ctx* ctx;
} ktsr_io_data;
static int ktsr_io_init(STREAMFILE* sf, ktsr_io_data* data) {
/* ktsr keys start with size then random bytes (usually 7), assumed max 0x20 */
if (data->key[0] >= sizeof(data->key) - 1)
return -1;
data->ctx = blowfish_init_ecb(data->key + 1, data->key[0]);
if (!data->ctx)
return -1;
return 0;
}
static void ktsr_io_close(STREAMFILE* sf, ktsr_io_data* data) {
blowfish_free(data->ctx);
}
static int read_part(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length, ktsr_io_data* data) {
off_t offset_rem = offset % 0x08;
offset -= offset_rem;
if (offset_rem == 0 && length >= 0x08) /* handled in main */
return 0;
/* read one full block, regardless of requested length */
int bytes = read_streamfile(data->block, offset, 0x08, sf);
/* useless since KTSR data is padded and blocks don't work otherwise but for determinability */
if (bytes < 0x08)
memset(data->block + bytes, 0, 0x08 - bytes);
blowfish_decrypt_ecb(data->ctx, data->block);
int max_copy = bytes - offset_rem;
if (max_copy > length)
max_copy = length;
memcpy(dest, data->block + offset_rem, max_copy);
return max_copy;
}
static int read_main(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length, ktsr_io_data* data) {
int read = 0;
off_t offset_rem = offset % 0x08;
size_t length_rem = length % 0x08;
length -= length_rem;
if (offset_rem != 0 || length == 0) /* handled in part */
return 0;
int bytes = read_streamfile(dest, offset, length, sf);
while (read < bytes) {
blowfish_decrypt_ecb(data->ctx, dest);
dest += 0x08;
read += 0x08;
}
return bytes;
}
/* blowfish is a 64-bit block cipher, so arbitrary reads will need to handle partial cases. ex
* - reading 0x00 to 0x20: direct decrypt (4 blocks of 0x08)
* - reading 0x03 to 0x07: decrypt 0x00 to 0x08 but copy 4 bytes at 0x03
* - reading 0x03 to 0x22: handle as 0x00 to 0x08 (head, copy 5 at 0x3), 0x08 to 0x20 (body, direct), and 0x20 to 0x28 (tail, copy 2 at 0x0). */
static size_t ktsr_io_read(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length, ktsr_io_data* data) {
int bytes = 0;
/* head */
if (length) {
int done = read_part(sf, dest, offset, length, data);
dest += done;
offset += done;
length -= done;
bytes += done;
}
/* body */
if (length) {
int done = read_main(sf, dest, offset, length, data);
dest += done;
offset += done;
length -= done;
bytes += done;
}
/* tail */
if (length) {
int done = read_part(sf, dest, offset, length, data);
dest += done;
offset += done;
length -= done;
bytes += done;
}
return bytes;
}
/* Decrypts blowfish KTSR streams */
static STREAMFILE* setup_ktsr_streamfile(STREAMFILE* sf, bool is_external, uint32_t subfile_offset, uint32_t subfile_size, const char* extension) {
STREAMFILE* new_sf = NULL;
ktsr_io_data io_data = {0};
if (is_external) {
uint32_t offset = 0x00;
if (is_id32be(0x00, sf, "TSRS"))
offset += 0x10;
if (!is_id32be(offset + 0x00, sf, "KTSR"))
goto fail;
read_streamfile(io_data.key, offset + 0x20, sizeof(io_data.key), sf);
}
/* setup subfile */
new_sf = open_wrap_streamfile(sf);
/* no apparent flag other than key at offset. Only data at subfile is encrypted, so this reader assumes it will be clamped */
if (io_data.key[0] != 0)
new_sf = open_io_streamfile_ex_f(new_sf, &io_data, sizeof(ktsr_io_data), ktsr_io_read, NULL, ktsr_io_init, ktsr_io_close);
new_sf = open_clamp_streamfile_f(new_sf, subfile_offset, subfile_size);
if (extension)
new_sf = open_fakename_streamfile_f(new_sf, NULL, extension);
return new_sf;
fail:
return NULL;
}
#endif

View file

@ -26,8 +26,8 @@ typedef struct {
//off_t name_offset;
} kwb_header;
static int parse_kwb(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b);
static int parse_xws(kwb_header* kwb, STREAMFILE* sf);
static bool parse_kwb(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b);
static bool parse_xws(kwb_header* kwb, STREAMFILE* sf);
static VGMSTREAM* init_vgmstream_koei_wavebank(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b);
@ -43,14 +43,14 @@ VGMSTREAM* init_vgmstream_kwb(STREAMFILE* sf) {
if (!is_id32be(0x00, sf, "WBD_") &&
!is_id32le(0x00, sf, "WBD_") &&
!is_id32be(0x00, sf, "WHD1"))
goto fail;
return NULL;
/* .wbd+wbh: common [Bladestorm Nightmare (PC)]
* .wbd+whd: uncommon [Nights of Azure 2 (PS4)]
* .wb2+wh2: newer [Nights of Azure 2 (PC)]
* .sed: mixed header+data [Dissidia NT (PC)] */
if (!check_extensions(sf, "wbd,wb2,sed"))
goto fail;
return NULL;
/* open companion header */
@ -102,13 +102,13 @@ VGMSTREAM* init_vgmstream_xws(STREAMFILE* sf) {
/* checks */
if (!check_extensions(sf, "xws"))
goto fail;
return NULL;
if (target_subsong == 0) target_subsong = 1;
kwb.target_subsong = target_subsong;
if (!parse_xws(&kwb, sf))
goto fail;
return NULL;
vgmstream = init_vgmstream_koei_wavebank(&kwb, sf, sf);
if (!vgmstream) goto fail;
@ -119,6 +119,34 @@ fail:
return NULL;
}
#if 0
/* SND - Sound? from Koei games [Ninja Gaiden Sigma -Master Collection- (PC)] */
VGMSTREAM* init_vgmstream_snd_koei(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
kwb_header kwb = {0};
int target_subsong = sf->stream_index;
/* checks */
/* .snd: header id/assumed by extractors? (doatool) */
if (!check_extensions(sf, "snd"))
return NULL;
if (target_subsong == 0) target_subsong = 1;
kwb.target_subsong = target_subsong;
if (!parse_xws(&kwb, sf))
return NULL;
vgmstream = init_vgmstream_koei_wavebank(&kwb, sf, sf);
if (!vgmstream) goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}
#endif
static VGMSTREAM* init_vgmstream_koei_wavebank(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b) {
VGMSTREAM* vgmstream = NULL;
@ -563,7 +591,7 @@ fail:
}
static int parse_kwb(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b) {
static bool parse_kwb(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b) {
off_t head_offset, body_offset, start;
uint32_t type;
uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL;
@ -644,12 +672,12 @@ static int parse_kwb(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b) {
if (!kwb->found)
goto fail;
return 1;
return true;
fail:
return 0;
return false;
}
static int parse_type_msfbank(kwb_header* kwb, off_t offset, STREAMFILE* sf) {
static bool parse_type_msfbank(kwb_header* kwb, off_t offset, STREAMFILE* sf) {
/* this is just like XWSF, abridged: */
int entries, current_subsongs, relative_subsong;
off_t header_offset;
@ -671,9 +699,9 @@ static int parse_type_msfbank(kwb_header* kwb, off_t offset, STREAMFILE* sf) {
kwb->stream_offset += offset;
return 1;
return true;
//fail:
// return 0;
// return false;
}
static int parse_type_xwsfile(kwb_header* kwb, uint32_t offset, STREAMFILE* sf) {
@ -681,22 +709,22 @@ static int parse_type_xwsfile(kwb_header* kwb, uint32_t offset, STREAMFILE* sf)
int i, chunks, chunks2;
uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL;
if (!(is_id64be(offset + 0x00, sf, "XWSFILE\0")) &&
!(is_id64be(offset + 0x00, sf, "tdpack\0\0")))
if (!( is_id64be(offset + 0x00, sf, "XWSFILE\0") ||
is_id64be(offset + 0x00, sf, "tdpack\0\0")))
//is_id64be(offset + 0x00, sf, "SND\0\0\0\0\0")
goto fail;
kwb->big_endian = read_u8(offset + 0x08, sf) == 0xFF;
/* 0x0a: version? (0100: NG2/NG3 PS3, 0101: DoA LR PC, NG2/3 PC) */
/* 0x0a: version? (0100: NG2/NG3 PS3, NGm PC, 0101: DoA LR PC, NG2/3 PC) */
read_u32 = kwb->big_endian ? read_u32be : read_u32le;
/* 0x0c: tables start */
/* 0x10: file size */
chunks = read_u32(offset + 0x14, sf);
chunks2 = read_u32(offset + 0x18, sf);
chunks2 = read_u32(offset + 0x14, sf);
chunks = read_u32(offset + 0x18, sf);
/* 0x1c: null */
if (chunks != chunks2)
if (chunks != chunks2) //seen in NGm PC
goto fail;
table1_offset = read_u32(offset + 0x20, sf); /* offsets */
@ -737,6 +765,13 @@ static int parse_type_xwsfile(kwb_header* kwb, uint32_t offset, STREAMFILE* sf)
if (!parse_type_xwsfile(kwb, head_offset, sf))
goto fail;
}
#if 0
else if (entry_type == get_id32be("tdpa")) { /* + "ck\0\0" */
i += 1;
if (!parse_type_xwsfile(kwb, head_offset, sf))
goto fail;
}
#endif
else if (entry_type == get_id32be("CUEB") || entry_type < 0x100) {
i += 1;
/* CUE-like info (may start with 0 or a low number instead) */
@ -767,7 +802,7 @@ fail:
}
static int parse_xws(kwb_header* kwb, STREAMFILE* sf) {
static bool parse_xws(kwb_header* kwb, STREAMFILE* sf) {
/* Format is similar to WHD1 with some annoyances of its own. Variations:
* - XWSFILE w/ N chunks: CUE offsets + 1 MSFBANK offset
@ -776,16 +811,16 @@ static int parse_xws(kwb_header* kwb, STREAMFILE* sf) {
* [Dead or Alive 5 Last Round (PC)]
* - tdpack: same but points to N XWSFILE
* [Ninja Gaiden 3 Razor's Edge (PS3)]
* - SND: similar to XWSFILE w/ 2*N chunks, points to tdpack (which point to _HBW0000+KWB2)
* [Ninja Gaiden Sigma -Master Collection- (PC)]
*
* Needs to call sub-parts multiple times to fill total subsongs when parsing xwsfile.
*/
if (!parse_type_xwsfile(kwb, 0x00, sf))
goto fail;
return false;
if (!kwb->found)
goto fail;
return false;
return 1;
fail:
return 0;
return true;
}

View file

@ -234,7 +234,7 @@ VGMSTREAM* init_vgmstream_rstm_rockstar(STREAMFILE* sf);
VGMSTREAM * init_vgmstream_acm(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_ps2_kces(STREAMFILE * streamFile);
VGMSTREAM* init_vgmstream_vig_kces(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_hxd(STREAMFILE* sf);
@ -300,7 +300,7 @@ VGMSTREAM* init_vgmstream_mus_krome(STREAMFILE* sf);
VGMSTREAM * init_vgmstream_rsd(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_dc_asd(STREAMFILE * streamFile);
VGMSTREAM* init_vgmstream_asd_naxat(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_spsd(STREAMFILE* sf);
@ -344,9 +344,9 @@ VGMSTREAM * init_vgmstream_thp(STREAMFILE *streamFile);
VGMSTREAM* init_vgmstream_sts(STREAMFILE* sf);
VGMSTREAM * init_vgmstream_ps2_p2bt(STREAMFILE *streamFile);
VGMSTREAM* init_vgmstream_p2bt_move_visa(STREAMFILE* sf);
VGMSTREAM * init_vgmstream_ps2_gbts(STREAMFILE *streamFile);
VGMSTREAM* init_vgmstream_gbts(STREAMFILE* sf);
VGMSTREAM * init_vgmstream_wii_sng(STREAMFILE *streamFile);
@ -500,7 +500,7 @@ VGMSTREAM * init_vgmstream_ps2_wmus(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_hyperscan_kvag(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_ios_psnd(STREAMFILE* streamFile);
VGMSTREAM* init_vgmstream_psnd(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_adp_wildfire(STREAMFILE* sf);
@ -523,7 +523,7 @@ VGMSTREAM * init_vgmstream_ps2_hsf(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_ivag(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_ps2_2pfs(STREAMFILE* streamFile);
VGMSTREAM* init_vgmstream_2pfs(STREAMFILE* sf);
VGMSTREAM * init_vgmstream_ubi_ckd(STREAMFILE* streamFile);
@ -580,6 +580,7 @@ VGMSTREAM * init_vgmstream_mc3(STREAMFILE *streamFile);
VGMSTREAM* init_vgmstream_ghs(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_s_p_sth(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_s_pack(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_aac_triace(STREAMFILE* sf);
@ -618,7 +619,6 @@ VGMSTREAM* init_vgmstream_opus_nop(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_opus_shinen(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_opus_nus3(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_opus_sps_n1(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_opus_nxa(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_opus_opusx(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_opus_prototype(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_opus_opusnx(STREAMFILE* sf);
@ -651,6 +651,18 @@ VGMSTREAM * init_vgmstream_ea_tmx(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_ea_sbr(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_ea_sbr_harmony(STREAMFILE * streamFile);
typedef struct {
STREAMFILE* sf_head;
STREAMFILE* sf_body;
uint32_t head_offset;
uint32_t body_offset;
meta_t type;
bool standalone;
bool is_sps;
} eaac_meta_t;
VGMSTREAM* load_vgmstream_ea_eaac(eaac_meta_t* info);
VGMSTREAM* init_vgmstream_vid1(STREAMFILE* sf);
VGMSTREAM * init_vgmstream_flx(STREAMFILE * streamFile);
@ -740,8 +752,6 @@ VGMSTREAM * init_vgmstream_sdf(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_svg(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_vis(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_vai(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_aif_asobo(STREAMFILE *streamFile);
@ -758,6 +768,8 @@ VGMSTREAM * init_vgmstream_derf(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_utk(STREAMFILE *streamFile);
VGMSTREAM* init_vgmstream_nxa1(STREAMFILE* sf);
VGMSTREAM * init_vgmstream_adpcm_capcom(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_ue4opus(STREAMFILE *streamFile);
@ -818,8 +830,8 @@ VGMSTREAM * init_vgmstream_bwav(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_awb(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_awb_memory(STREAMFILE * streamFile, STREAMFILE *acbFile);
VGMSTREAM * init_vgmstream_acb(STREAMFILE * streamFile);
void load_acb_wave_name(STREAMFILE *acbFile, VGMSTREAM* vgmstream, int waveid, int port, int is_memory);
VGMSTREAM* init_vgmstream_acb(STREAMFILE* sf);
void load_acb_wave_info(STREAMFILE *acbFile, VGMSTREAM* vgmstream, int waveid, int port, int is_memory, int load_loops);
VGMSTREAM * init_vgmstream_rad(STREAMFILE * streamFile);
@ -845,6 +857,7 @@ VGMSTREAM * init_vgmstream_nub_xma(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_nub_dsp(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_nub_idsp(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_nub_is14(STREAMFILE * streamFile);
VGMSTREAM* init_vgmstream_nub_caf(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_xwv_valve(STREAMFILE* sf);
@ -883,6 +896,7 @@ VGMSTREAM* init_vgmstream_diva(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_imuse(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_asrs(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_mups(STREAMFILE* sf);
@ -982,4 +996,10 @@ VGMSTREAM* init_vgmstream_squeaksample(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_snds(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_nxof(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_gwb_gwd(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_cbx(STREAMFILE* sf);
#endif /*_META_H*/

View file

@ -37,8 +37,9 @@ VGMSTREAM* init_vgmstream_mpeg(STREAMFILE* sf) {
* .mus: Marc Ecko's Getting Up (PC)
* .imf: Colors (Gizmondo)
* .aix: Classic Compendium 2 (Gizmondo)
* .wav/lwav: The Seventh Seal (PC)
* (extensionless): Interstellar Flames 2 (Gizmondo) */
if (!check_extensions(sf, "mp3,mp2,lmp3,lmp2,mus,imf,aix,,"))
if (!check_extensions(sf, "mp3,mp2,lmp3,lmp2,mus,imf,aix,wav,lwav,"))
goto fail;
loop_flag = 0;

View file

@ -29,12 +29,12 @@ struct dsp_header {
};
/* read and do basic validations to the above struct */
static int read_dsp_header_endian(struct dsp_header *header, off_t offset, STREAMFILE* sf, int big_endian) {
static bool read_dsp_header_endian(struct dsp_header *header, off_t offset, STREAMFILE* sf, int big_endian) {
uint32_t (*get_u32)(const uint8_t*) = big_endian ? get_u32be : get_u32le;
uint16_t (*get_u16)(const uint8_t*) = big_endian ? get_u16be : get_u16le;
int16_t (*get_s16)(const uint8_t*) = big_endian ? get_s16be : get_s16le;
int i;
uint8_t buf[0x60];
int zero_coefs;
if (offset > get_streamfile_size(sf))
goto fail;
@ -46,10 +46,13 @@ static int read_dsp_header_endian(struct dsp_header *header, off_t offset, STREA
/* base */
header->sample_count = get_u32(buf+0x00);
if (header->sample_count > 0x10000000)
if (header->sample_count > 0x10000000 || header->sample_count == 0)
goto fail; /* unlikely to be that big, should catch fourccs */
/* usually nibbles = size*2 in mono, but interleaved stereo or L+R may use nibbles =~ size (or not), so can't
* easily reject files with more nibbles than data (nibbles may be part of the -R file) without redoing L+R handling */
header->nibble_count = get_u32(buf+0x04);
if (header->nibble_count > 0x10000000)
if (header->nibble_count > 0x20000000 || header->nibble_count == 0)
goto fail;
header->sample_rate = get_u32(buf+0x08);
@ -70,14 +73,21 @@ static int read_dsp_header_endian(struct dsp_header *header, off_t offset, STREA
if (header->initial_offset != 2 && header->initial_offset != 0)
goto fail; /* Dr. Muto uses 0 */
for (i = 0; i < 16; i++)
zero_coefs = 0;
for (int i = 0; i < 16; i++) {
header->coef[i] = get_s16(buf+0x1c + i*0x02);
if (header->coef[i] == 0)
zero_coefs++;
}
/* some 0s are ok, more than 8 is probably wrong */
if (zero_coefs == 16)
goto fail;
header->gain = get_u16(buf+0x3c);
if (header->gain != 0)
goto fail;
/* decoder state */
/* decoder state (could check that ps <= 0xNN but not that useful) */
header->initial_ps = get_u16(buf+0x3e);
header->initial_hist1 = get_s16(buf+0x40);
header->initial_hist2 = get_s16(buf+0x42);
@ -94,9 +104,9 @@ static int read_dsp_header_endian(struct dsp_header *header, off_t offset, STREA
if (header->block_size >= 0xF000) /* same, 16b (usually 0) */
header->block_size = 0;
return 1;
return true;
fail:
return 0;
return false;
}
static int read_dsp_header_be(struct dsp_header *header, off_t offset, STREAMFILE* file) {
return read_dsp_header_endian(header, offset, file, 1);
@ -146,16 +156,16 @@ static VGMSTREAM* init_vgmstream_dsp_common(STREAMFILE* sf, dsp_meta* dspm) {
if (dspm->channels > dspm->max_channels)
goto fail;
if (dspm->channels > COMMON_DSP_MAX_CHANNELS)
goto fail;
return NULL;
if (dspm->channels > COMMON_DSP_MAX_CHANNELS || dspm->channels < 0)
return NULL;
/* load standard DSP header per channel */
{
for (i = 0; i < dspm->channels; i++) {
if (!read_dsp_header_endian(&ch_header[i], dspm->header_offset + i*dspm->header_spacing, sf, !dspm->little_endian)) {
//;VGM_LOG("DSP: bad header\n");
goto fail;
return NULL;
}
}
}

View file

@ -30,7 +30,7 @@ VGMSTREAM* init_vgmstream_nub(STREAMFILE* sf) {
goto fail;
/* .nub: standard
* .nub2: rare [iDOLM@STER - Gravure For You (PS3)] */
* .nub2: rare [iDOLM@STER: Gravure For You (PS3), Noby Noby Boy (iOS)] */
if (!check_extensions(sf, "nub,nub2"))
goto fail;
@ -97,47 +97,52 @@ VGMSTREAM* init_vgmstream_nub(STREAMFILE* sf) {
header_size = align_size_to_block(subheader_start + subheader_size, 0x10);
switch(codec) {
case 0x00: /* (none) (xma1) */
case 0x00: /* xma1 */
fake_ext = "xma";
init_vgmstream_function = init_vgmstream_nub_xma;
break;
case 0x01: /* "wav\0" */
case 0x01:
fake_ext = "wav";
init_vgmstream_function = init_vgmstream_nub_wav;
break;
case 0x02: /* "vag\0" */
case 0x02:
fake_ext = "vag";
init_vgmstream_function = init_vgmstream_nub_vag;
break;
case 0x03: /* "at3\0" */
case 0x03:
fake_ext = "at3";
init_vgmstream_function = init_vgmstream_nub_at3;
break;
case 0x04: /* "xma\0" (xma2 old) */
case 0x08: /* "xma\0" (xma2 new) */
case 0x04: /* xma2 old */
case 0x08: /* xma2 new */
fake_ext = "xma";
init_vgmstream_function = init_vgmstream_nub_xma;
break;
case 0x05: /* "dsp\0" */
case 0x05:
fake_ext = "dsp";
init_vgmstream_function = init_vgmstream_nub_dsp;
break;
case 0x06: /* "idsp" */
case 0x06:
fake_ext = "idsp";
init_vgmstream_function = init_vgmstream_nub_idsp;
break;
case 0x07: /* "is14" */
case 0x07:
fake_ext = "is14";
init_vgmstream_function = init_vgmstream_nub_is14;
break;
case 0x09:
fake_ext = "caf";
init_vgmstream_function = init_vgmstream_nub_caf;
break;
default:
VGM_LOG("NUB: unknown codec %x\n", codec);
goto fail;
@ -235,9 +240,10 @@ static STREAMFILE* setup_nub_streamfile(STREAMFILE* sf, off_t header_offset, siz
//todo could be simplified
/* .nub wav - from Namco NUB archives [Ridge Racer 7 (PS3)] */
/* .nub wav - from Namco NUB archives [Ridge Racer 7 (PS3), Noby Noby Boy (iOS)] */
VGMSTREAM* init_vgmstream_nub_wav(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* temp_sf = NULL;
off_t start_offset;
int loop_flag, channel_count, sample_rate;
size_t data_size, loop_start, loop_length;
@ -246,10 +252,10 @@ VGMSTREAM* init_vgmstream_nub_wav(STREAMFILE* sf) {
/* checks */
if (!is_id32be(0x00,sf, "wav\0"))
return NULL;
if (!check_extensions(sf, "wav,lwav"))
goto fail;
if (read_32bitBE(0x00,sf) != 0x77617600) /* "wav\0" "*/
goto fail;
return NULL;
if (guess_endian32(0x1c, sf)) {
read_32bit = read_32bitBE;
@ -276,6 +282,21 @@ VGMSTREAM* init_vgmstream_nub_wav(STREAMFILE* sf) {
start_offset = 0xD0;
/* seen in Noby Noby Boy (iOS), not sure about flags */
if (is_id32be(start_offset, sf, "RIFF")) {
uint32_t subfile_offset = start_offset;
uint32_t subfile_size = read_32bitLE(subfile_offset + 0x04, sf) + 0x08; /* RIFF size */
temp_sf = setup_subfile_streamfile(sf, subfile_offset,subfile_size, NULL);
if (!temp_sf) goto fail;
vgmstream = init_vgmstream_riff(temp_sf);
if (!vgmstream) goto fail;
close_streamfile(temp_sf);
return vgmstream;
}
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
@ -296,6 +317,7 @@ VGMSTREAM* init_vgmstream_nub_wav(STREAMFILE* sf) {
return vgmstream;
fail:
close_streamfile(temp_sf);
close_vgmstream(vgmstream);
return NULL;
}
@ -310,10 +332,10 @@ VGMSTREAM* init_vgmstream_nub_vag(STREAMFILE* sf) {
/* checks */
if (!is_id32be(0x00,sf, "vag\0"))
return NULL;
if ( !check_extensions(sf, "vag"))
goto fail;
if (read_32bitBE(0x00,sf) != 0x76616700) /* "vag\0" */
goto fail;
return NULL;
if (guess_endian32(0x1c, sf)) {
read_32bit = read_32bitBE;
@ -362,15 +384,13 @@ fail:
VGMSTREAM* init_vgmstream_nub_at3(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* temp_sf = NULL;
off_t subfile_offset = 0;
size_t subfile_size = 0;
/* checks */
if (!is_id32be(0x00,sf, "at3\0"))
return NULL;
if (!check_extensions(sf,"at3"))
goto fail;
if (read_32bitBE(0x00,sf) != 0x61743300) /* "at3\0" */
goto fail;
return NULL;
/* info header */
/* 0x20: loop start (in samples) */
@ -381,8 +401,8 @@ VGMSTREAM* init_vgmstream_nub_at3(STREAMFILE* sf) {
/* format header: mini fmt (WAVEFORMATEX) + fact chunks LE (clone of RIFF's) */
/* we can just ignore and use RIFF at data start since it has the same info */
subfile_offset = 0x100;
subfile_size = read_32bitLE(subfile_offset + 0x04, sf) + 0x08; /* RIFF size */
uint32_t subfile_offset = 0x100;
uint32_t subfile_size = read_32bitLE(subfile_offset + 0x04, sf) + 0x08; /* RIFF size */
temp_sf = setup_subfile_streamfile(sf, subfile_offset,subfile_size, NULL);
if (!temp_sf) goto fail;
@ -410,9 +430,9 @@ VGMSTREAM* init_vgmstream_nub_xma(STREAMFILE* sf) {
/* checks */
if (!check_extensions(sf,"xma"))
goto fail;
return NULL;
if (read_32bitBE(0x00,sf) == 0x786D6100) { /* "xma\0" */
if (is_id32be(0x00,sf, "xma\0")) {
/* nub v2.1 */
nus_codec = read_32bitBE(0x0C,sf);
data_size = read_32bitBE(0x14,sf);
@ -435,7 +455,7 @@ VGMSTREAM* init_vgmstream_nub_xma(STREAMFILE* sf) {
chunk_size = header_size;
}
else {
goto fail;
return NULL;
}
start_offset = align_size_to_block(chunk_offset + header_size, 0x10);
@ -520,10 +540,10 @@ VGMSTREAM* init_vgmstream_nub_dsp(STREAMFILE* sf) {
/* checks */
if (!is_id32be(0x00,sf, "dsp\0"))
return NULL;
if (!check_extensions(sf,"dsp"))
goto fail;
if (read_32bitBE(0x00,sf) != 0x64737000) /* "dsp\0" */
goto fail;
return NULL;
/* paste header+data together and pass to meta, which has loop info too */
header_offset = 0xBC;
@ -554,10 +574,10 @@ VGMSTREAM* init_vgmstream_nub_idsp(STREAMFILE* sf) {
/* checks */
if (!is_id32be(0x00,sf, "idsp"))
return NULL;
if (!check_extensions(sf,"idsp"))
goto fail;
if (read_32bitBE(0x00,sf) != 0x69647370) /* "idsp" */
goto fail;
return NULL;
/* info header */
/* 0x20: loop start (in samples) */
@ -595,10 +615,10 @@ VGMSTREAM* init_vgmstream_nub_is14(STREAMFILE* sf) {
/* checks */
if (!is_id32be(0x00,sf, "is14"))
return NULL;
if (!check_extensions(sf,"is14"))
goto fail;
if (read_32bitBE(0x00,sf) != 0x69733134) /* "is14" */
goto fail;
return NULL;
if (guess_endian32(0x1c, sf)) {
read_32bit = read_32bitBE;
@ -632,3 +652,41 @@ fail:
close_vgmstream(vgmstream);
return NULL;
}
/* .nub is14 - from Namco NUB archives [Noby Noby Boy (iOS)] */
VGMSTREAM* init_vgmstream_nub_caf(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* temp_sf = NULL;
/* checks */
if (!is_id32be(0x00,sf, "caf\0"))
return NULL;
if (!check_extensions(sf,"caf"))
return NULL;
/* info header */
/* 0x20: loop start (in samples) */
/* 0x24: loop length (in samples) */
/* 0x28: loop flag */
/* 0x2c: null */
/* format header: simplified caff with some chunks */
/* we can just ignore and use caff at data start since it has the same info */
uint32_t subfile_offset = 0x110;
uint32_t subfile_size = read_u32le(0x14, sf); /* padded but not fully validated */
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, NULL);
if (!temp_sf) goto fail;
vgmstream = init_vgmstream_apple_caff(temp_sf);
if (!vgmstream) goto fail;
close_streamfile(temp_sf);
return vgmstream;
fail:
close_streamfile(temp_sf);
close_vgmstream(vgmstream);
return NULL;
}

View file

@ -2,8 +2,8 @@
#include "../coding/coding.h"
/* Entergram NXA Opus [Higurashi no Naku Koro ni Hou (Switch), Gensou Rougoku no Kaleidoscope (Switch)] */
VGMSTREAM* init_vgmstream_opus_nxa(STREAMFILE* sf) {
/* Entergram NXA1 Opus [Higurashi no Naku Koro ni Hou (Switch), Gensou Rougoku no Kaleidoscope (Switch)] */
VGMSTREAM* init_vgmstream_nxa1(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset;
int loop_flag, channels, type, sample_rate;
@ -11,10 +11,10 @@ VGMSTREAM* init_vgmstream_opus_nxa(STREAMFILE* sf) {
size_t data_size, frame_size;
/* checks */
if (!check_extensions(sf, "nxa"))
goto fail;
if (!is_id32be(0x00, sf, "NXA1"))
goto fail;
if (!check_extensions(sf, "nxa"))
goto fail;
start_offset = 0x30;
type = read_u32le(0x04, sf);
@ -36,7 +36,7 @@ VGMSTREAM* init_vgmstream_opus_nxa(STREAMFILE* sf) {
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_NXA;
vgmstream->meta_type = meta_NXA1;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = num_samples;
vgmstream->loop_start_sample = loop_start;

View file

@ -0,0 +1,53 @@
#include "meta.h"
#include "../coding/coding.h"
/* Nihon Falcom FDK NXOpus [Ys X -NORDICS- (Switch)] */
VGMSTREAM* init_vgmstream_nxof(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset;
int loop_flag, channels, sample_rate;
size_t data_size, skip = 0;
int32_t num_samples, loop_start, loop_end;
/* checks */
if (!is_id32le(0x00, sf, "nxof"))
goto fail;
if (!check_extensions(sf,"nxopus"))
goto fail;
channels = read_u8(0x05, sf);
sample_rate = read_u32le(0x08, sf);
start_offset = read_u32le(0x18, sf);
data_size = read_u32le(0x1C, sf);
num_samples = read_u32le(0x20, sf);
loop_start = read_u32le(0x30, sf);
loop_end = read_u32le(0x34, sf);
loop_flag = (loop_end > 0);
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_NXOF;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = num_samples;
vgmstream->loop_start_sample = loop_start;
vgmstream->loop_end_sample = loop_end;
#ifdef VGM_USE_FFMPEG
skip = switch_opus_get_encoder_delay(start_offset, sf);
vgmstream->codec_data = init_ffmpeg_switch_opus(sf, start_offset, data_size, vgmstream->channels, skip, vgmstream->sample_rate);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
#else
goto fail;
#endif
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View file

@ -135,8 +135,9 @@ VGMSTREAM* init_vgmstream_opus_std(STREAMFILE* sf) {
/* .opus: standard / .lopus: for plugins
* .bgm: Cotton Reboot (Switch)
* .opu: Ys Memoire: The Oath in Felghana (Switch) */
if (!check_extensions(sf,"opus,lopus,bgm,opu"))
* .opu: Ys Memoire: The Oath in Felghana (Switch)
* .ogg: Trouble Witches Origin (Switch) */
if (!check_extensions(sf,"opus,lopus,bgm,opu,ogg,logg"))
goto fail;
offset = 0x00;

View file

@ -0,0 +1,65 @@
#include "meta.h"
#include "../coding/coding.h"
/* P2BT/MOVE/VISA - from Konami/KCE Studio games [Pop'n Music 7/8/Best (PS2), AirForce Delta Strike (PS2)] */
VGMSTREAM* init_vgmstream_p2bt_move_visa(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
uint32_t data_offset;
int loop_flag, channels, sample_rate, interleave;
uint32_t loop_start, data_size;
/* checks */
if (!is_id32be(0x00,sf, "P2BT") &&
!is_id32be(0x00,sf, "MOVE") &&
!is_id32be(0x00,sf, "VISA"))
return NULL;
/* .psbt/move: header id (no apparent exts)
* .vis: actual extension found in AFDS and other KCES games */
if (!check_extensions(sf, "p2bt,move,vis"))
return NULL;
/* (header is the same with different IDs, all may be used within a single same game) */
/* 04: 07FC? */
sample_rate = read_s32le(0x08,sf);
loop_start = read_s32le(0x0c,sf);
data_size = read_u32le(0x10,sf); /* without padding */
interleave = read_u32le(0x14,sf); /* usually 0x10, sometimes 0x400 */
/* 18: 1? */
/* 1c: 0x10? */
channels = read_s32le(0x20,sf);
/* 24: 1? */
/* 28: stream name (AFDS), same as basename + original ext */
loop_flag = (loop_start != 0);
data_offset = 0x800;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = ps_bytes_to_samples(data_size, channels);
vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channels);
vgmstream->loop_end_sample = vgmstream->num_samples;
vgmstream->coding_type = coding_PSX;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = interleave;
if (vgmstream->interleave_block_size)
vgmstream->interleave_last_block_size = (data_size % (vgmstream->interleave_block_size * channels)) / channels;
read_string(vgmstream->stream_name,0x10+1, 0x28, sf);
vgmstream->meta_type = meta_P2BT_MOVE_VISA;
if (!vgmstream_open_stream(vgmstream, sf, data_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View file

@ -1,92 +0,0 @@
#include "meta.h"
#include "../util.h"
/* GBTS : Pop'n'Music 9 Bgm File */
VGMSTREAM * init_vgmstream_ps2_gbts(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
char filename[PATH_LIMIT];
int loop_flag=0;
int channel_count;
off_t start_offset;
off_t loopStart = 0;
off_t loopEnd = 0;
size_t filelength;
int i;
/* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename));
if (strcasecmp("gbts",filename_extension(filename))) goto fail;
/* check loop */
start_offset=0x801;
filelength = get_streamfile_size(streamFile);
do {
// Loop Start ...
if(read_8bit(start_offset,streamFile)==0x06) {
if(loopStart==0) loopStart = start_offset-0x801;
}
// Loop End ...
if(read_8bit(start_offset,streamFile)==0x03) {
if(loopEnd==0) loopEnd = start_offset-0x801-0x10;
}
start_offset+=0x10;
} while (start_offset<(int32_t)filelength);
loop_flag = (loopEnd!=0);
channel_count=read_32bitLE(0x1C,streamFile);
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
/* fill in the vital statistics */
vgmstream->channels = channel_count;
vgmstream->sample_rate = read_32bitLE(0x18,streamFile);;
/* Check for Compression Scheme */
vgmstream->coding_type = coding_PSX;
vgmstream->num_samples = read_32bitLE(0x0C,streamFile)/16*28/vgmstream->channels;
vgmstream->interleave_block_size = 0x10;
/* Get loop point values */
if(vgmstream->loop_flag) {
vgmstream->loop_start_sample = (loopStart/(vgmstream->interleave_block_size)*vgmstream->interleave_block_size)/16*28;
vgmstream->loop_start_sample += (loopStart%vgmstream->interleave_block_size)/16*28;
vgmstream->loop_start_sample /=vgmstream->channels;
vgmstream->loop_end_sample = (loopEnd/(vgmstream->interleave_block_size)*vgmstream->interleave_block_size)/16*28;
vgmstream->loop_end_sample += (loopEnd%vgmstream->interleave_block_size)/16*28;
vgmstream->loop_end_sample /=vgmstream->channels;
}
vgmstream->layout_type = layout_interleave;
vgmstream->meta_type = meta_PS2_GBTS;
start_offset = (off_t)0x800;
/* open the file for reading by each channel */
{
for (i=0;i<channel_count;i++) {
vgmstream->ch[i].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (!vgmstream->ch[i].streamfile) goto fail;
vgmstream->ch[i].channel_start_offset=
vgmstream->ch[i].offset=
(off_t)(start_offset+vgmstream->interleave_block_size*i);
}
}
return vgmstream;
/* clean up anything we may have opened */
fail:
if (vgmstream) close_vgmstream(vgmstream);
return NULL;
}

View file

@ -1,71 +0,0 @@
#include "meta.h"
#include "../util.h"
/* KCES (from Dance Dance Revolution) */
VGMSTREAM * init_vgmstream_ps2_kces(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
char filename[PATH_LIMIT];
off_t start_offset;
int loop_flag = 0;
int channel_count;
/* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename));
if (strcasecmp("kces",filename_extension(filename)) &&
strcasecmp("vig",filename_extension(filename))) goto fail;
/* check header */
if (read_32bitBE(0x00,streamFile) != 0x01006408)
goto fail;
loop_flag = (read_32bitLE(0x14,streamFile)!=0);
channel_count = read_32bitLE(0x1C,streamFile);
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
/* fill in the vital statistics */
start_offset = read_32bitLE(0x08,streamFile);
vgmstream->channels = channel_count;
vgmstream->sample_rate = read_32bitLE(0x18,streamFile);
vgmstream->coding_type = coding_PSX;
vgmstream->num_samples = read_32bitLE(0x0C,streamFile)*28/16/channel_count;
if (loop_flag) {
vgmstream->loop_start_sample = (read_32bitLE(0x0C,streamFile)-read_32bitLE(0x14,streamFile))*28/16/channel_count;
vgmstream->loop_end_sample = read_32bitLE(0x0C,streamFile)*28/16/channel_count;
}
if(vgmstream->channels==1) {
vgmstream->layout_type=layout_none;
} else {
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = read_32bitLE(0x24,streamFile);
}
vgmstream->meta_type = meta_PS2_KCES;
/* open the file for reading */
{
int i;
STREAMFILE * file;
file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (!file) goto fail;
for (i=0;i<channel_count;i++) {
vgmstream->ch[i].streamfile = file;
vgmstream->ch[i].channel_start_offset=
vgmstream->ch[i].offset=start_offset+
vgmstream->interleave_block_size*i;
}
}
return vgmstream;
/* clean up anything we may have opened */
fail:
if (vgmstream) close_vgmstream(vgmstream);
return NULL;
}

View file

@ -1,70 +0,0 @@
#include "meta.h"
#include "../util.h"
/* P2BT : Pop'n'Music 7 & 8 Bgm File */
VGMSTREAM * init_vgmstream_ps2_p2bt(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
char filename[PATH_LIMIT];
int loop_flag=0;
int channel_count;
off_t start_offset;
int i;
/* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename));
if (strcasecmp("p2bt",filename_extension(filename))) goto fail;
if((read_32bitBE(0x00,streamFile)!=0x4d4F5645) && // MOVE
(read_32bitBE(0x00,streamFile)!=0x50324254)) // P2BT
goto fail;
/* check loop */
loop_flag = (read_32bitLE(0x0C,streamFile)!=0);
channel_count=read_32bitLE(0x20,streamFile);
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
/* fill in the vital statistics */
vgmstream->channels = channel_count;
vgmstream->sample_rate = read_32bitLE(0x08,streamFile);;
/* Check for Compression Scheme */
vgmstream->coding_type = coding_PSX;
vgmstream->num_samples = read_32bitLE(0x10,streamFile)/16*28/vgmstream->channels;
/* Get loop point values */
if(vgmstream->loop_flag) {
vgmstream->loop_start_sample = read_32bitLE(0x0C,streamFile)/16*28/vgmstream->channels;
vgmstream->loop_end_sample = vgmstream->num_samples;
}
vgmstream->interleave_block_size = read_32bitLE(0x14,streamFile);;
vgmstream->layout_type = layout_interleave;
vgmstream->meta_type = meta_PS2_P2BT;
start_offset = (off_t)0x800;
/* open the file for reading by each channel */
{
for (i=0;i<channel_count;i++) {
vgmstream->ch[i].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (!vgmstream->ch[i].streamfile) goto fail;
vgmstream->ch[i].channel_start_offset=
vgmstream->ch[i].offset=
(off_t)(start_offset+vgmstream->interleave_block_size*i);
}
}
return vgmstream;
/* clean up anything we may have opened */
fail:
if (vgmstream) close_vgmstream(vgmstream);
return NULL;
}

View file

@ -4,7 +4,7 @@
#include "../layout/layout.h"
#define PSB_MAX_LAYERS 2
#define PSB_MAX_LAYERS 6 /* MGS Master Collection Vo.1 (Switch) */
typedef enum { PCM, RIFF_AT3, XMA2, MSADPCM, XWMA, DSP, OPUSNX, RIFF_AT9, VAG } psb_codec_t;
typedef struct {

View file

@ -0,0 +1,74 @@
#include "meta.h"
#include "../coding/coding.h"
/* PSND - from Polarbit games [Crash Bandicoot Nitro Kart 3D/2 (iOS), Reckless Racing 2 (Android/iOS)] */
VGMSTREAM* init_vgmstream_psnd(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
uint32_t start_offset, data_size, type;
int loop_flag, channels, sample_rate;
/* checks */
if (!is_id32be(0x00,sf, "PSND"))
return NULL;
/* .psn: actual extension in exes */
if (!check_extensions(sf, "psn"))
return NULL;
data_size = read_u32le(0x04, sf); /* after this field */
type = read_u32le(0x08,sf);
sample_rate = read_u16le(0x0c,sf);
switch (type) {
case 0x0030006: /* CBNK */
channels = read_u8(0xE,sf);
if (read_u8(0x0f, sf) != 16) goto fail; /* bps */
start_offset = 0x10;
break;
case 0x0000004: /* RR */
channels = 1;
start_offset = 0x0e;
break;
default:
goto fail;
}
data_size = data_size + 0x08 - start_offset;
loop_flag = 0; /* generally 22050hz music loops */
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = sample_rate;
switch (type) {
case 0x0030006:
vgmstream->coding_type = coding_PCM16LE;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x02;
vgmstream->num_samples = pcm16_bytes_to_samples(data_size, channels);
break;
case 0x0000004:
vgmstream->coding_type = coding_DVI_IMA;
vgmstream->layout_type = layout_none;
vgmstream->num_samples = ima_bytes_to_samples(data_size, channels);
break;
default:
goto fail;
}
vgmstream->loop_start_sample = 0;
vgmstream->loop_end_sample = vgmstream->num_samples;
vgmstream->meta_type = meta_PSND;
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View file

@ -4,7 +4,7 @@
/* WB - from Psychonauts (PS2) */
VGMSTREAM* init_vgmstream_pwb(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
int channels, loop_flag;
int channels, loop_flag;
uint32_t stream_offset, stream_size, loop_start, loop_end;
@ -54,7 +54,7 @@ VGMSTREAM* init_vgmstream_pwb(STREAMFILE* sf) {
}
/* build the VGMSTREAM */
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
@ -65,7 +65,7 @@ VGMSTREAM* init_vgmstream_pwb(STREAMFILE* sf) {
vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channels);
vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end, channels);
vgmstream->coding_type = coding_PSX;
vgmstream->coding_type = coding_PSX;
vgmstream->layout_type = layout_none;
vgmstream->num_streams = total_subsongs;
vgmstream->stream_size = stream_size;

View file

@ -1,149 +1,357 @@
#include "meta.h"
#include "../coding/coding.h"
#include "../util.h"
#include "../util/endianness.h"
static uint32_t rotlwi(uint32_t x, uint32_t r) {
return (x << r) | (x >> (32-r));
}
#define MAX_CHANNELS 4
typedef struct {
bool loop_flag;
int channels;
int sample_rate;
int32_t num_samples;
int32_t loop_start;
int32_t loop_end;
int16_t coefs[MAX_CHANNELS][16];
static uint32_t find_key(uint32_t firstword) {
uint32_t expected = 0x52656453;
return firstword ^ expected;
}
int total_subsongs;
/* RSD - RedSpark (MadWorld)
RS3D - RedSpark (Mario & Luigi: Dream Team I fi*/
VGMSTREAM * init_vgmstream_redspark(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
char filename[PATH_LIMIT];
off_t start_offset;
int loop_flag;
int channel_count;
int dt_flag = 0;
uint32_t key;
enum {encsize = 0x1000};
uint8_t buf[encsize];
int32_t(*get_32bit)(const uint8_t *p) = NULL;
int16_t(*get_16bit)(const uint8_t *p) = NULL;
get_16bit = get_16bitBE;
get_32bit = get_32bitBE;
uint32_t stream_offset;
uint32_t stream_size;
/* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename));
if (strcasecmp("rsd", filename_extension(filename))) goto fail;
/* decrypt into buffer */
{
uint32_t data;
int i;
if (read_streamfile(buf,0,encsize,streamFile)!=encsize) goto fail;
if (memcmp(&buf[0], "RedSpark", 8)) { //Check to see if already decrypted
key = find_key(get_32bitBE(&buf[0]));
data = get_32bitBE(&buf[0]) ^ key;
put_32bitBE(&buf[0], data);
key = rotlwi(key, 11);
bool dummy;
} redspark_header_t;
for (i = 4; i < encsize; i += 4) {
key = rotlwi(key, 3) + key;
data = get_32bitBE(&buf[i]) ^ key;
put_32bitBE(&buf[i], data);
}
}
else {
get_16bit = get_16bitLE;
get_32bit = get_32bitLE;
dt_flag = 1;
static bool parse_header(redspark_header_t* h, STREAMFILE* sf, bool is_new);
for (i = 4; i < encsize; i += 4) {
data = get_32bitBE(&buf[i]);
put_32bitBE(&buf[i], data);
}
}
/* RedSpark - Games with audio by RedSpark Ltd. (Minoru Akao) [MadWorld (Wii), Imabikisou (Wii), Mario & Luigi: Dream Team (3DS)] */
VGMSTREAM* init_vgmstream_redspark(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
bool is_new;
/* checks*/
uint32_t head_id = read_u32be(0x00, sf);
if (head_id == get_id32be("RedS")) {
is_new = true; /* M&L */
}
else if (head_id > 0x15800000 && head_id < 0x1B800000) {
is_new = false; /* others: header is encrypted but in predictable ranges to fail faster (will do extra checks later) */
}
else {
return NULL;
}
/* check header */
if (memcmp(&buf[0],"RedSpark",8))
goto fail;
if (!check_extensions(sf, "rsd"))
return NULL;
loop_flag = (buf[0x4f] != 0);
channel_count = buf[0x4e];
redspark_header_t h = {0};
if (!parse_header(&h, sf, is_new))
return NULL;
/* make sure loop info is the only two cue points */
if (loop_flag && buf[0x4f] != 2) goto fail;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(h.channels, h.loop_flag);
if (!vgmstream) goto fail;
/* fill in the vital statistics */
start_offset = 0x1000;
vgmstream->channels = channel_count;
vgmstream->sample_rate = get_32bit(&buf[0x3c]);
vgmstream->coding_type = coding_NGC_DSP;
if (dt_flag)
vgmstream->num_samples = get_32bit(&buf[0x40]);
else
vgmstream->num_samples = get_32bit(&buf[0x40])*14;
if (loop_flag) {
off_t start = 0x54;
start += channel_count*8;
if (dt_flag) {
vgmstream->loop_start_sample = get_32bit(&buf[start+4]);
vgmstream->loop_end_sample = (get_32bit(&buf[start+0xc]));
}
else {
vgmstream->loop_start_sample = get_32bit(&buf[start+4])*14;
vgmstream->loop_end_sample = (get_32bit(&buf[start+0xc])+1)*14;
}
if (vgmstream->loop_end_sample > vgmstream->num_samples) {
vgmstream->loop_end_sample = vgmstream->num_samples;
}
}
if (channel_count >= 2) {
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 8;
} else {
vgmstream->layout_type = layout_none;
}
vgmstream->meta_type = meta_REDSPARK;
vgmstream->sample_rate = h.sample_rate;
vgmstream->num_samples = h.num_samples;
vgmstream->loop_start_sample = h.loop_start;
vgmstream->loop_end_sample = h.loop_end;
vgmstream->num_streams = h.total_subsongs;
vgmstream->stream_size = h.stream_size;
{
off_t start = 0x54;
int i,j;
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x08;
start += channel_count * 8;
if (loop_flag) {
start += 16;
}
for (j = 0; j < channel_count; j++) {
for (i=0;i<16;i++) {
vgmstream->ch[j].adpcm_coef[i] =
get_16bit(&buf[start+0x2e*j+i*2]);
}
for (int ch = 0; ch < h.channels; ch++) {
for (int i = 0; i < 16; i++) {
vgmstream->ch[ch].adpcm_coef[i] = h.coefs[ch][i];
}
}
/* open the file for reading */
{
int i;
STREAMFILE * file;
file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (!file) goto fail;
for (i=0;i<channel_count;i++) {
vgmstream->ch[i].streamfile = file;
vgmstream->ch[i].channel_start_offset=
vgmstream->ch[i].offset=start_offset + i*vgmstream->interleave_block_size;
}
if (h.dummy) {
vgmstream->num_samples = h.sample_rate;
vgmstream->coding_type = coding_SILENCE;
}
if (!vgmstream_open_stream(vgmstream, sf, h.stream_offset))
goto fail;
return vgmstream;
/* clean up anything we may have opened */
fail:
if (vgmstream) close_vgmstream(vgmstream);
close_vgmstream(vgmstream);
return NULL;
}
static uint32_t rotlwi(uint32_t x, uint32_t r) {
return (x << r) | (x >> (32 - r));
}
static uint32_t decrypt_chunk(uint8_t* buf, int buf_size, uint32_t key) {
uint32_t data;
int pos = 0;
if (key == 0) {
/* initial key seems to only vary slightly between files though (doesn't seem to depend on size/name) */
key = get_u32be(buf + 0x00) ^ get_id32be("RedS");
data = get_u32be(buf + 0x00) ^ key;
put_u32be(&buf[0], data);
key = rotlwi(key, 11);
pos = 0x04;
}
for (int i = pos; i < buf_size; i += 0x04) {
key = rotlwi(key, 3) + key;
data = get_u32be(buf + i) ^ key;
put_u32be(buf + i, data);
}
return key; /* to resume decrypting if needed */
}
#define HEADER_MAX 0x2800 /* seen 0x2420 in one bank */
/* header is encrypted except in M&L 3DS so decrypt + handle in buffer; format
* base 0x30 header + subheader with tables/headers depending on type */
static bool parse_header(redspark_header_t* h, STREAMFILE* sf, bool is_new) {
uint8_t buf[HEADER_MAX];
/* LE on 3DS */
get_u16_t get_u16 = !is_new ? get_u16be : get_u16le;
get_u32_t get_u32 = !is_new ? get_u32be : get_u32le;
uint32_t curr_key = 0x00;
int target_subsong = sf->stream_index;
/* base header:
00 "RedSpark"
08 chunk size (usually data size, smaller for subspark)
0c type/chunk? 00010000=streams, 00000000=bus/se
10 ? (low number, usually same for multiple files)
14 file id or null
18 data offset
1c bank flag
1e 0909=stream, 0404=stream (MW) or RedSpark-within aka 'subspark' (Imabikisou), 0000=bus/se config (no audio)
20 data size (usually file size, or header + subheader size for subsparks)
24-30 null/reserved
*/
int base_size = 0x30;
if (read_streamfile(buf, 0x00, base_size,sf) != base_size)
return false;
if (!is_new)
curr_key = decrypt_chunk(buf, 0x30, curr_key);
/* get base header */
if (get_u64be(buf + 0x00) != get_id64be("RedSpark"))
return false;
uint32_t data_offset = get_u32(buf + 0x18);
uint32_t data_size = get_u32(buf + 0x20);
int bank_flag = get_u16(buf + 0x1c);
int type = get_u16(buf + 0x1e);
if (data_offset >= HEADER_MAX)
return false;
/* get subheader and prepare offsets */
int redspark_pos;
int sub_offset, sub_size;
int head_pos;
if (data_offset == 0x30) {
/* 'subspark', seen in a few sfx in Imabikisou (earlier version of banks) */
/* at data offset (not encrypted):
00 always 017F0100 (LE?)
04-10: null
10 file id
13 string? size
14 encrypted string? (ends with 0)
at subchunk_size:
another RedSpark with bank subflag
*/
/* base sub-RedSpark + reset flags */
redspark_pos = data_size;
if (read_streamfile(buf + redspark_pos, data_size, base_size, sf) != base_size)
return false;
if (!is_new)
curr_key = decrypt_chunk(buf + redspark_pos, base_size, 0); /* new header */
/* setup rest of header (handled below) */
uint32_t subdata_offset = get_u32(buf + redspark_pos + 0x18);
bank_flag = get_u16(buf + redspark_pos + 0x1c);
type = get_u16(buf + redspark_pos + 0x1e);
data_offset = redspark_pos + subdata_offset;
if (data_offset >= HEADER_MAX)
return false;
sub_offset = redspark_pos + base_size;
sub_size = data_offset - sub_offset;
head_pos = sub_offset;
}
else {
redspark_pos = 0x00;
sub_offset = base_size;
sub_size = data_offset - sub_offset;
head_pos = sub_offset;
}
/* read + decrypt rest of header */
if (read_streamfile(buf + sub_offset, sub_offset, sub_size, sf) != sub_size)
return false;
if (!is_new)
decrypt_chunk(buf + sub_offset, sub_size, curr_key);
//VGM_LOGB(buf, data_offset, 0);
/* bus/se config */
if (type == 0x0000) {
/* 30 number of entries (after data offset)
per entry
00 channel offset (after all entries)
per channel at the above offset
00 channel config (similar to channel config in streams but extended) */
vgm_logi("RedSpark: file has no audio\n");
return false;
}
/* main info */
uint32_t coef_offset;
if (bank_flag) {
/* bank with N subsongs (seen in M&L sfx packs and in subsparks) */
/* 00 data size
0c entries (only 1 is possible)
0e entries again
20+ table
per entry:
00 absolute offset to header
per header
00 null?
04 stream size
08 sample rate (null in subspark)
0c nibbles (old) or samples (new), (null in subspark so uses loops)
10 stream offset (from data_offset)
14 loop end (num samples if non-looped)
18 loop start of -1 it not looped
20 config?
24 config?
28 coefs + hists
5c channel config?
*/
h->total_subsongs = get_u16(buf + head_pos + 0x0c);
if (!check_subsongs(&target_subsong, h->total_subsongs))
return false;
int target_pos = head_pos + 0x20 + (target_subsong - 1) * 0x04;
if (target_pos + 0x04 >= HEADER_MAX)
return false;
target_pos = get_u32(buf + target_pos) + redspark_pos;
if (target_pos + 0x70 >= HEADER_MAX)
return false;
h->stream_size = get_u32(buf + target_pos + 0x04);
h->sample_rate = get_u32(buf + target_pos + 0x08);
h->num_samples = get_u32(buf + target_pos + 0x0c);
h->stream_offset = get_u32(buf + target_pos + 0x10) + data_offset;
h->loop_end = get_u32(buf + target_pos + 0x14);
h->loop_start = get_u32(buf + target_pos + 0x18);
coef_offset = target_pos + 0x28;
h->channels = 1;
h->loop_flag = (h->loop_start != -1); /* TODO: many files sound kind of odd */
if (h->num_samples == 0)
h->num_samples = h->loop_end;
if (h->sample_rate == 0)
h->sample_rate = 32000;
/* empty entry */
if (h->stream_size == 0)
h->dummy = true;
}
else {
/* stream */
/* 00 data size (after data offset)
04 null
08 null
0c sample rate
10 frames (old) or samples (new)
14 some chunk? (0x10000, 0xc000)
18 null
1c null
1e channels (usually 1-2, sometimes 4 in MW)
1f num loop cues (2 == loop)
20 cues again?
21 volume?
22 null
24+ variable
per channel
00 config per channel, size 0x08 (number/panning/volume/etc?)
per loop point
00 chunk size?
04 value
per channel
00 dsp coefs + hists (size 0x2E)
if cues:
00 offset?
04 null
per cue:
00 name size
01 string ("Loop Start" / "Loop End")
*/
h->sample_rate = get_u32(buf + head_pos + 0x0c);
h->num_samples = get_u32(buf + head_pos + 0x10);
h->channels = get_u8(buf + head_pos + 0x1e);
int loop_cues = get_u8(buf + head_pos + 0x1f);
head_pos += 0x24;
/* just in case to avoid bad reads outside buf */
if (h->channels > MAX_CHANNELS)
return false;
head_pos += h->channels * 0x08;
h->loop_flag = (loop_cues != 0);
if (h->loop_flag) {
/* only two cue points */
if (loop_cues != 0 && loop_cues != 2)
return false;
h->loop_start = get_u32(buf + head_pos + 0x04);
h->loop_end = get_u32(buf + head_pos + 0x0c);
head_pos += 0x10;
}
coef_offset = head_pos;
h->stream_offset = data_offset;
h->stream_size = data_size;
}
/* coefs from decrypted buf (could read hist but it's 0 in DSPs) */
for (int ch = 0; ch < h->channels; ch++) {
for (int i = 0; i < 16; i++) {
h->coefs[ch][i] = get_u16(buf + coef_offset + 0x2e * ch + i * 2);
}
}
/* fixes */
if (!is_new) {
h->loop_end += 1;
if (bank_flag) {
h->num_samples /= 2 * 0x8;
h->loop_start /= 2 * 0x8;
h->loop_end /= 2 * 0x8;
}
h->num_samples *= 14;
h->loop_start *= 14;
h->loop_end *= 14;
if (h->loop_end > h->num_samples) /* needed for some files */
h->loop_end = h->num_samples;
}
/* new + bank may need +1 */
return true;
}

View file

@ -169,6 +169,10 @@ static int read_fmt(int big_endian, STREAMFILE* sf, off_t offset, riff_fmt_chunk
case 0x0002: /* MSADPCM */
if (fmt->bps == 4) {
/* ADPCMWAVEFORMAT extra data:
* - samples per frame (16b)
* - num coefs (16b), always 7
* - N x2 coefs (configurable but in practice fixed) */
fmt->coding_type = coding_MSADPCM;
if (!msadpcm_check_coefs(sf, fmt->offset + 0x08 + 0x14))
goto fail;
@ -180,7 +184,7 @@ static int read_fmt(int big_endian, STREAMFILE* sf, off_t offset, riff_fmt_chunk
goto fail;
}
break;
case 0x003: /* floating point PCM */
case 0x0003: /* floating point PCM */
if (fmt->bps == 32) {
fmt->coding_type = coding_PCMFLOAT;
} else {
@ -190,6 +194,8 @@ static int read_fmt(int big_endian, STREAMFILE* sf, off_t offset, riff_fmt_chunk
break;
case 0x0011: /* MS-IMA ADPCM [Layton Brothers: Mystery Room (iOS/Android)] */
/* IMAADPCMWAVEFORMAT extra data:
* - samples per frame (16b) */
if (fmt->bps != 4) goto fail;
fmt->coding_type = coding_MS_IMA;
break;
@ -391,8 +397,9 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) {
* .xms: Ty the Tasmanian Tiger (Xbox)
* .mus: Burnout Legends/Dominator (PSP)
* .dat/ldat: RollerCoaster Tycoon 1/2 (PC)
* .wma/lwma: SRS: Street Racing Syndicate (Xbox), Fast and the Furious (Xbox)
*/
if (!check_extensions(sf, "wav,lwav,xwav,mwv,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,,sbv,wvx,str,at3,rws,aud,at9,ckd,saf,ima,nsa,pcm,xvag,ogg,logg,p1d,xms,mus,dat,ldat")) {
if (!check_extensions(sf, "wav,lwav,xwav,mwv,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,,sbv,wvx,str,at3,rws,aud,at9,ckd,saf,ima,nsa,pcm,xvag,ogg,logg,p1d,xms,mus,dat,ldat,wma,lwma")) {
goto fail;
}
@ -409,6 +416,9 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) {
else if (codec == 0x0069 && riff_size + 0x04 == file_size)
riff_size -= 0x04; /* [Halo 2 (PC)] (possibly bad extractor? 'Gravemind Tool') */
else if (codec == 0x0069 && riff_size + 0x10 == file_size)
riff_size += 0x08; /* [Fast and the Furious (Xbox)] ("HASH" chunk + 4 byte hash) */
else if (codec == 0x0000 && riff_size + 0x04 == file_size)
riff_size -= 0x04; /* [Headhunter (DC), Bomber hehhe (DC)] */
@ -457,6 +467,9 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) {
else if (codec == 0xFFFE && riff_size + 0x08 + 0x40 == file_size)
file_size -= 0x40; /* [Megami no Etsubo (PSP)] (has extra padding in all files) */
else if (codec == 0x0011 && file_size - riff_size - 0x08 <= 0x900 && is_id32be(riff_size + 0x08, sf, "cont"))
riff_size = file_size - 0x08; /* [Shin Megami Tensei: Imagine (PC)] (extra "cont" info 0x800/0x900 chunk) */
}
/* check for truncated RIFF */

View file

@ -4,8 +4,9 @@
/* RSTM - from Rockstar games [Midnight Club 3, Bully - Canis Canim Edit (PS2)] */
VGMSTREAM* init_vgmstream_rstm_rockstar(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
uint32_t start_offset;
int channels, loop_flag;
off_t stream_offset, loop_start, loop_end;
size_t stream_size;
int sample_rate, channels, loop_flag;
/* checks */
@ -14,30 +15,38 @@ VGMSTREAM* init_vgmstream_rstm_rockstar(STREAMFILE* sf) {
/* .rsm: in filelist
* .rstm: header id */
if (!check_extensions(sf,"rsm,rstm"))
if (!check_extensions(sf, "rsm,rstm"))
return NULL;
loop_flag = (read_s32le(0x24,sf) > 0);
channels = read_s32le(0x0C,sf);
start_offset = 0x800;
sample_rate = read_s32le(0x08, sf);
channels = read_s32le(0x0C, sf);
/* 0x10-0x18 - empty padding(?) */
stream_size = read_s32le(0x18, sf);
loop_start = read_s32le(0x1C, sf);
loop_end = read_s32le(0x20, sf);
/* other loop start/ends after here? (uncommon) */
stream_offset = 0x800;
//loop_flag = (read_s32le(0x24,sf) > 0);
loop_flag = loop_end != stream_size;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels,loop_flag);
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_RSTM_ROCKSTAR;
vgmstream->sample_rate = read_s32le(0x08,sf);
vgmstream->num_samples = ps_bytes_to_samples(read_u32le(0x20,sf),channels);
vgmstream->loop_start_sample = ps_bytes_to_samples(read_u32le(0x24,sf),channels);
vgmstream->loop_end_sample = vgmstream->num_samples;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = ps_bytes_to_samples(stream_size, channels);
vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channels);
vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end, channels);
vgmstream->coding_type = coding_PSX;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x10;
/* open the file for reading */
if ( !vgmstream_open_stream(vgmstream, sf, start_offset) )
if ( !vgmstream_open_stream(vgmstream, sf, stream_offset) )
goto fail;
return vgmstream;

View file

@ -7,7 +7,7 @@
VGMSTREAM* init_vgmstream_rws_809(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
bool big_endian;
char header_name[STREAM_NAME_SIZE], stream_name[STREAM_NAME_SIZE];
char file_name[STREAM_NAME_SIZE], header_name[STREAM_NAME_SIZE], stream_name[STREAM_NAME_SIZE];
int channels = 0, idx, interleave, loop_flag, sample_rate = 0, total_subsongs, target_subsong = sf->stream_index;
read_u32_t read_u32;
off_t chunk_offset, header_offset, misc_data_offset = 0, stream_name_offset, stream_offset = 0;
@ -136,7 +136,11 @@ VGMSTREAM* init_vgmstream_rws_809(STREAMFILE* sf) {
goto fail;
}
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%s/%s", header_name, stream_name);
get_streamfile_basename(sf, file_name, STREAM_NAME_SIZE);
if (strcmp(file_name, header_name) == 0)
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%s", stream_name);
else
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%s/%s", header_name, stream_name);
if (!vgmstream_open_stream(vgmstream, sf, stream_offset + 0x0C))
goto fail;

View file

@ -40,6 +40,7 @@ typedef struct {
size_t block_layers_size;
off_t coefs_offset;
off_t hist_offset;
char readable_name[STREAM_NAME_SIZE];
} rws_header;
@ -200,7 +201,7 @@ VGMSTREAM* init_vgmstream_rws(STREAMFILE* sf) {
for (i = 0; i < rws.total_layers; i++) {
uint32_t layer_codec = 0;
if (i+1 == rws.target_layer) {
rws.sample_rate = read_u32(offset + 0x00, sf);
rws.sample_rate = read_u32(offset + 0x00, sf);
/* 0x04: config? */
//rws.layer_size = read_u32(offset + 0x08, sf); /* same or close to usable size */
/* 0x0c: bits per sample */
@ -210,7 +211,7 @@ VGMSTREAM* init_vgmstream_rws(STREAMFILE* sf) {
/* 0x18: null or some size? */
rws.codec = read_u32(offset + 0x1c, sf); /* 128b uuid (32b-16b-16b-8b*8) but first 32b is enough */
}
layer_codec = read_u32(offset + 0x1c, sf);
layer_codec = read_u32(offset + 0x1c, sf);
offset += 0x2c;
/* DSP has an extra field per layer */
@ -219,6 +220,7 @@ VGMSTREAM* init_vgmstream_rws(STREAMFILE* sf) {
/* 0x04: approx size/loop related? (can be 0) */
if (i+1 == rws.target_layer) {
rws.coefs_offset = offset + 0x1c;
rws.hist_offset = offset + 0x40;
}
offset += 0x60;
}
@ -322,12 +324,14 @@ VGMSTREAM* init_vgmstream_rws(STREAMFILE* sf) {
break;
case 0xF86215B0: /* {F86215B0,31D5,4C29,BD,37,CD,BF,9B,D1,0C,53} DSP GC/Wii */
/* Burnout 2 (GC), Alice in Wonderland (Wii) */
/* Burnout 2 (GC), Alice in Wonderland (Wii), Call of Duty: Finest Hour (GC) */
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->interleave_block_size = rws.block_size / 2;
/* get coefs (all channels share them; also seem fixed for all RWS) */
dsp_read_coefs_be(vgmstream, sf, rws.coefs_offset, 0);
/* get initial sample history data (rarely used / often empty) */
dsp_read_hist_be(vgmstream, sf, rws.hist_offset, 0);
vgmstream->num_samples = dsp_bytes_to_samples(stream_size, rws.channels);
break;

View file

@ -74,27 +74,30 @@ fail:
return NULL;
}
#define SEGMENT_MAX 3
/* Nippon Ichi SPS wrapper (segmented) [Penny-Punching Princess (Switch), Disgaea 4 Complete (PC)] */
VGMSTREAM* init_vgmstream_sps_n1_segmented(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t segment_offset;
size_t data_size, max_size;
int loop_flag, type, sample_rate;
int i, segment;
init_vgmstream_t init_vgmstream = NULL;
const char* extension;
segmented_layout_data* data = NULL;
int segment_count, loop_start_segment, loop_end_segment;
int loop_start_segment, loop_end_segment;
/* checks */
/* .at9: Penny-Punching Princess (Switch)
type = read_u32le(0x00,sf);
if (type > 10)
return NULL;
/* .at9: Penny-Punching Princess (Switch), Labyrinth of Galleria (PC)
* .nlsd: Disgaea 4 Complete (PC) */
if (!check_extensions(sf, "at9,nlsd"))
goto fail;
return NULL;
type = read_u32le(0x00,sf);
data_size = read_u32le(0x04,sf);
sample_rate = read_u16le(0x08,sf);
/* 0x0a: flag? (stereo?) */
@ -113,23 +116,58 @@ VGMSTREAM* init_vgmstream_sps_n1_segmented(STREAMFILE* sf) {
break;
default:
goto fail;
return NULL;
}
segment_offset = 0x1c;
if (data_size + segment_offset != get_streamfile_size(sf))
goto fail;
/* segmented using 3 files (intro/loop/outro). non-segmented wrapper is the same
* but with loop samples instead of sub-sizes */
uint32_t segment_offsets[SEGMENT_MAX];
uint32_t segment_sizes[SEGMENT_MAX];
uint32_t segment_start, offset;
int segment_count, segment;
if (data_size + 0x1c == get_streamfile_size(sf)) {
/* common */
segment_start = 0x1c;
offset = segment_start;
for (int i = 0; i < SEGMENT_MAX; i++) {
uint32_t segment_size = read_u32le(0x10 + 0x04*i,sf);
segment_sizes[i] = segment_size;
segment_offsets[i] = offset;
offset += segment_sizes[i];
}
}
else if (data_size + 0x18 == get_streamfile_size(sf)) {
/* Labyrinth of Galleria (PC) */
segment_start = 0x18;
offset = segment_start;
for (int i = 0; i < SEGMENT_MAX; i++) {
uint32_t next_offset;
if (i >= 2) {
next_offset = get_streamfile_size(sf) - segment_start;
}
else {
next_offset = read_u32le(0x10 + 0x04*i,sf); /* only 2 (not sure if it can be 0) */
}
segment_sizes[i] = next_offset - offset + segment_start;
segment_offsets[i] = offset;
offset += segment_sizes[i];
}
}
else {
goto fail;
}
max_size = 0;
segment_count = 0;
for (i = 0; i < 3; i++) {
size_t segment_size = read_u32le(0x10 + 0x04*i,sf);
max_size += segment_size;
/* may only set 1 segment (Disgaea4's bgm_185) */
if (segment_size)
for (int i = 0; i < SEGMENT_MAX; i++) {
/* may only set 1 segment, with empty intro/outro (Disgaea4's bgm_185) */
if (segment_sizes[i])
segment_count++;
max_size += segment_sizes[i];
}
if (data_size != max_size)
goto fail;
@ -144,25 +182,21 @@ VGMSTREAM* init_vgmstream_sps_n1_segmented(STREAMFILE* sf) {
/* open each segment subfile */
segment = 0;
for (i = 0; i < 3; i++) {
STREAMFILE* temp_sf;
size_t segment_size = read_u32le(0x10 + 0x04*i,sf);
if (!segment_size)
for (int i = 0; i < SEGMENT_MAX; i++) {
if (!segment_sizes[i])
continue;
temp_sf = setup_subfile_streamfile(sf, segment_offset,segment_size, extension);
STREAMFILE* temp_sf = setup_subfile_streamfile(sf, segment_offsets[i],segment_sizes[i], extension);
if (!temp_sf) goto fail;
data->segments[segment] = init_vgmstream(temp_sf);
close_streamfile(temp_sf);
if (!data->segments[segment]) goto fail;
segment_offset += segment_size;
segment++;
if (type == 9) {
//todo there are some trailing samples that must be removed for smooth loops, start skip seems ok
//TODO there are some trailing samples that must be removed for smooth loops, start skip seems ok
//not correct for all files, no idea how to calculate
data->segments[segment]->num_samples -= 374;
}

View file

@ -24,14 +24,13 @@ VGMSTREAM* init_vgmstream_sqex_scd(STREAMFILE* sf) {
/* checks */
if (!check_extensions(sf, "scd"))
goto fail;
/** main header **/
if (!is_id32be(0x00,sf, "SEDB") &&
!is_id32be(0x04,sf, "SSCF"))
goto fail;
return NULL;
if (!check_extensions(sf, "scd"))
return NULL;
/** main header **/
big_endian = read_u8(0x0c,sf) == 0x01;
if (big_endian) { /* big endian flag */
//size_offset = 0x14;
@ -152,7 +151,7 @@ VGMSTREAM* init_vgmstream_sqex_scd(STREAMFILE* sf) {
#ifdef VGM_USE_VORBIS
/* special case using init_vgmstream_ogg_vorbis */
if (codec == 0x06) {
VGMSTREAM *ogg_vgmstream;
VGMSTREAM* ogg_vgmstream;
uint8_t ogg_version, ogg_byte;
ogg_vorbis_meta_info_t ovmi = {0};
@ -216,15 +215,16 @@ VGMSTREAM* init_vgmstream_sqex_scd(STREAMFILE* sf) {
read_string(vgmstream->stream_name, STREAM_NAME_SIZE, name_offset, sf);
switch (codec) {
case 0x01: /* PCM */
vgmstream->coding_type = coding_PCM16LE;
case 0x00: /* PCM BE [Drakengard 3 (PS3)] */
case 0x01: /* PCM LE */
vgmstream->coding_type = codec == 0x00 ? coding_PCM16BE : coding_PCM16LE;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x02;
vgmstream->num_samples = pcm_bytes_to_samples(stream_size, channels, 16);
vgmstream->num_samples = pcm16_bytes_to_samples(stream_size, channels);
if (loop_flag) {
vgmstream->loop_start_sample = pcm_bytes_to_samples(loop_start, channels, 16);
vgmstream->loop_end_sample = pcm_bytes_to_samples(loop_end, channels, 16);
vgmstream->loop_start_sample = pcm16_bytes_to_samples(loop_start, channels);
vgmstream->loop_end_sample = pcm16_bytes_to_samples(loop_end, channels);
}
break;

View file

@ -16,7 +16,6 @@ VGMSTREAM * init_vgmstream_sthd(STREAMFILE *sf) {
goto fail;
/* first block has special values */
if (read_u16le(0x04,sf) != 0x0800 ||
read_u32le(0x0c,sf) != 0x0001 ||
read_u32le(0x14,sf) != 0x0000)
goto fail;

View file

@ -3,19 +3,19 @@
/* STM - from Angel Studios/Rockstar San Diego games [Red Dead Revolver (PS2), Spy Hunter 2 (PS2/Xbox)] */
VGMSTREAM* init_vgmstream_stma(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset;
VGMSTREAM* vgmstream = NULL;
off_t start_offset;
int loop_flag = 0, channel_count;
int big_endian, bps, interleave, data_size, loop_start = 0, loop_end = 0;
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL;
/* checks */
/* checks */
if (!is_id32be(0x00,sf, "STMA") && /* LE */
!is_id32be(0x00,sf, "AMTS")) /* BE */
goto fail;
/* .stm: real extension
/* .stm: real extension
* .lstm: for plugins */
if (!check_extensions(sf,"stm,lstm"))
goto fail;

View file

@ -1,8 +1,10 @@
#include "meta.h"
#include "../coding/coding.h"
#include "../util/layout_utils.h"
#include "str_wav_streamfile.h"
typedef enum { PSX, DSP, XBOX, WMA, IMA, XMA2 } strwav_codec;
typedef enum { PSX, PSX_chunked, DSP, XBOX, WMA, IMA, XMA2, MPEG } strwav_codec;
typedef struct {
int tracks;
int channels;
@ -103,6 +105,29 @@ VGMSTREAM* init_vgmstream_str_wav(STREAMFILE* sf) {
vgmstream->interleave_block_size = strwav.interleave;
break;
case PSX_chunked: { /* hack */
//tracks are stereo blocks of size 0x20000 * tracks, containing 4 interleaves of 0x8000:
// | 1 2 1 2 | 3 4 3 4 | 5 6 5 6 | 1 2 1 2 | 3 4 3 4 | 5 6 5 6 | ...
vgmstream->coding_type = coding_PSX;
vgmstream->interleave_block_size = strwav.interleave;
for (int i = 0; i < strwav.tracks; i++) {
uint32_t chunk_size = 0x20000;
int layer_channels = 2;
STREAMFILE* temp_sf = setup_str_wav_streamfile(sf, 0x00, strwav.tracks, i, chunk_size);
if (!temp_sf) goto fail;
bool res = layered_add_sf(vgmstream, strwav.tracks, layer_channels, temp_sf);
close_streamfile(temp_sf);
if (!res)
goto fail;
}
if (!layered_add_done(vgmstream))
goto fail;
break;
}
case DSP:
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->layout_type = layout_interleave;
@ -173,6 +198,26 @@ VGMSTREAM* init_vgmstream_str_wav(STREAMFILE* sf) {
}
#endif
#ifdef VGM_USE_MPEG
case MPEG: {
/* regular MP3 starting with ID2, stereo tracks xN (bgm + vocals) but assuming last (or only one) could be mono */
int layers = (strwav.channels + 1) / 2;
for (int i = 0; i < layers; i++) {
uint32_t size = strwav.interleave;
uint32_t offset = i * size;
const char* ext = "mp3";
int layer_channels = ((layers % 2) && i + 1 == layers) ? 1 : 2;
layered_add_subfile(vgmstream, layers, layer_channels, sf, offset, size, ext, init_vgmstream_mpeg);
}
if (!layered_add_done(vgmstream))
goto fail;
break;
}
#endif
default:
goto fail;
}
@ -286,7 +331,7 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
/* 0x10c: header size */
strwav->codec = IMA;
strwav->interleave = strwav->tracks > 1 ? 0x8000 : 0x10000;
strwav->interleave = strwav->tracks > 1 ? 0x10000 : 0x10000;
;VGM_LOG("STR+WAV: header TAZd (PC)\n");
return 1;
}
@ -435,8 +480,8 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
strwav->codec = PSX;
strwav->interleave = strwav->tracks > 1 ? 0x8000 : 0x8000;
//todo: tracks are stereo blocks of size 0x20000*tracks, containing 4 interleaves of 0x8000:
// | 1 2 1 2 | 3 4 3 4 | 5 6 5 6 | 1 2 1 2 | 3 4 3 4 | 5 6 5 6 | ...
if (strwav->tracks > 1) /* hack */
strwav->codec = PSX_chunked;
;VGM_LOG("STR+WAV: header ZPb (PS2)\n");
return 1;
}
@ -498,6 +543,32 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
return 1;
}
/* Taz Wanted (beta) (PC)[2002] */
if ( read_u32be(0x04,sf_h) == 0x00000900 &&
read_u32le(0x0c,sf_h) != header_size &&
read_u32le(0x24,sf_h) != 0 &&
read_u32le(0xd4,sf_h) != 0 &&
read_u32le(0xdc,sf_h) == header_size
) {
/* 0x08: null */
/* 0x0c: hashname */
strwav->num_samples = read_s32le(0x20,sf_h);
strwav->sample_rate = read_s32le(0x24,sf_h);
/* 0x28: 16 bps */
strwav->flags = read_u32le(0x2c,sf_h);
strwav->loop_start = read_s32le(0x38,sf_h);
/* 0x54: number of chunks */
strwav->tracks = read_s32le(0xd4,sf_h);
/* 0xdc: header size */
strwav->loop_end = strwav->num_samples;
strwav->codec = IMA;
strwav->interleave = strwav->tracks > 1 ? 0x8000 : 0x10000;
;VGM_LOG("STR+WAV: header TAZb (PC)\n");
return 1;
}
/* Taz Wanted (PC)[2002] */
/* Zapper: One Wicked Cricket! Beta (Xbox)[2002] */
if ( read_u32be(0x04,sf_h) == 0x00000900 &&
@ -669,7 +740,7 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
/* 0x4c: ? (some low number) */
strwav->tracks = read_u8 (0x4e,sf_h);
/* 0x4f: 16 bps */
/* 0x54: channels per each track? (ex. 2 stereo track: 0x02,0x02) */
/* 0x54: channels per each track (ex. 2 stereo track: 0x02,0x02) */
/* 0x64: channels */
/* 0x70+: tables */
@ -712,6 +783,7 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
/* Tak and the Guardians of Gross (Wii)[2008] */
/* The House of the Dead: Overkill (Wii)[2009] (not Blitz but still the same format) */
/* All Star Karate (Wii)[2010] */
/* Karaoke Revolution (Wii)[2010] */
if ((read_u32be(0x04,sf_h) == 0x00000800 ||
read_u32be(0x04,sf_h) == 0x00000700) && /* rare? */
read_u32be(0x08,sf_h) != 0x00000000 &&
@ -730,11 +802,12 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
strwav->codec = DSP;
strwav->coefs_table = 0x7c;
strwav->interleave = strwav->channels > 4 ? 0x4000 : 0x8000;
;VGM_LOG("STR+WAV: header TKGG/HOTDO/ASK (Wii)\n");
;VGM_LOG("STR+WAV: header TKGG/HOTDO/ASK/KR (Wii)\n");
return 1;
}
/* The House of the Dead: Overkill (PS3)[2009] (not Blitz but still the same format) */
/* Karaoke Revolution (PS3)[2010] */
if ((read_u32be(0x04,sf_h) == 0x00000800 ||
read_u32be(0x04,sf_h) == 0x00000700) && /* rare? */
read_u32be(0x08,sf_h) != 0x00000000 &&
@ -747,10 +820,30 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
strwav->sample_rate = read_s32be(0x38,sf_h);
strwav->flags = read_u32be(0x3c,sf_h);
strwav->channels = read_s32be(0x70,sf_h); /* tracks of 1ch */
strwav->interleave = strwav->channels > 4 ? 0x4000 : 0x8000;
strwav->channels = read_s32be(0x70,sf_h);
/* other possibly-useful flags (see Karaoke Wii too):
* - 0x4b: number of tracks
* - 0x60: channels per track, ex. 020202 = 3 tracks of 2ch (max 0x08 = 8)
* - 0xa0: sizes per track (max 0x20 = 8)
* - 0xc0: samples per track (max 0x20 = 8)
* - rest: info/seek table? */
if (read_s32be(0x78,sf_h) != 0) { /* KRev */
strwav->tracks = strwav->channels / 2;
strwav->num_samples = strwav->loop_end; /* num_samples here seems to be data size */
strwav->interleave = read_s32be(0xA0,sf_h); /* one size per file, but CBR = same for all */
//C0: stream samples (same as num_samples)
strwav->codec = MPEG; /* full CBR MP3 one after other */
}
else { /* HOTD */
strwav->channels = read_s32be(0x70,sf_h); /* tracks of 1ch */
strwav->interleave = strwav->channels > 4 ? 0x4000 : 0x8000;
strwav->codec = PSX;
}
strwav->codec = PSX;
;VGM_LOG("STR+WAV: header HOTDO (PS3)\n");
return 1;
}

View file

@ -0,0 +1,21 @@
#ifndef _STR_WAV_STREAMFILE_H_
#define _STR_WAV_STREAMFILE_H_
#include "deblock_streamfile.h"
/* Deblocks streams */
static STREAMFILE* setup_str_wav_streamfile(STREAMFILE* sf, off_t stream_start, int stream_count, int stream_number, size_t interleave) {
STREAMFILE *new_sf = NULL;
deblock_config_t cfg = {0};
cfg.stream_start = stream_start;
cfg.chunk_size = interleave;
cfg.step_start = stream_number;
cfg.step_count = stream_count;
/* setup sf */
new_sf = open_wrap_streamfile(sf);
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg);
return new_sf;
}
#endif

View file

@ -841,7 +841,7 @@ static void set_body_chunk(txth_header* txth) {
if (!txth->sf_body)
return;
/* treat chunks as subsongs */
/* treat chunks as subsongs (less subsongs than chunks could be allowed to ignore some chunks but it's kinda odd) */
if (txth->subsong_count > 1 && txth->subsong_count == txth->chunk_count)
txth->chunk_number = txth->target_subsong;
if (txth->chunk_number == 0)

View file

@ -1012,7 +1012,7 @@ static int parse_values(ubi_bao_header* bao) {
}
/* set codec */
if (bao->stream_type > 0x10) {
if (bao->stream_type >= 0x10) {
VGM_LOG("UBI BAO: unknown stream_type at %x\n", (uint32_t)bao->header_offset); goto fail;
goto fail;
}

View file

@ -1818,6 +1818,7 @@ static int parse_type_audio(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) {
sb->sample_rate = read_32bit(offset + sb->cfg.audio_sample_rate, sf);
sb->stream_type = read_32bit(offset + sb->cfg.audio_stream_type, sf);
//TO-DO a handful of SC:PT PS2 streams have 0 stream offset+size, maybe should set config + allow as dummies (ex. MAPS.SM1 #14191 #14255)
if (sb->stream_size == 0) {
VGM_LOG("UBI SB: bad stream size\n");
goto fail;
@ -2414,7 +2415,8 @@ static int parse_offsets(ubi_sb_header* sb, STREAMFILE* sf) {
break;
}
if (sb->stream_offset == 0) {
/* valid in rare cases with ram-streamed but also external file (SC:PT PS2 > MAPS.RS1)*/
if (sb->stream_offset == 0 && !sb->is_external) {
VGM_LOG("UBI SM: Failed to find offset for resource %d in subblock %d in map %s\n", sb->header_index, sb->subblock_id, sb->map_name);
goto fail;
}

View file

@ -167,8 +167,8 @@ VGMSTREAM* init_vgmstream_vab(STREAMFILE* sf) {
data_size = read_u16le(waves_off + i * 0x02, sf) << 3;
if (data_size == 0 && center == 0 && shift == 0) {
// hack for empty sounds in Critical Depth
if (data_size == 0 /*&& center == 0 && shift == 0*/) {
// hack for empty sounds in rare cases (may set center/shift to 0 as well) [Critical Depth]
vgmstream = init_vgmstream_silence(1, 44100, 44100);
if (!vgmstream) goto fail;

View file

@ -25,8 +25,9 @@ VGMSTREAM* init_vgmstream_vag(STREAMFILE* sf) {
* .l/r: Crash Nitro Kart (PS2), Gradius V (PS2)
* .vas: Kingdom Hearts II (PS2)
* .xa2: Shikigami no Shiro (PS2)
* .snd: Alien Breed (Vita) */
if (!check_extensions(sf,"vag,swag,str,vig,l,r,vas,xa2,snd"))
* .snd: Alien Breed (Vita)
* .svg: ModernGroove: Ministry of Sound Edition (PS2) */
if (!check_extensions(sf,"vag,swag,str,vig,l,r,vas,xa2,snd,svg"))
return NULL;
file_size = get_streamfile_size(sf);
@ -148,6 +149,16 @@ VGMSTREAM* init_vgmstream_vag(STREAMFILE* sf) {
loop_flag = ps_find_loop_offsets(sf, start_offset, channel_size*channels, channels, interleave, &loop_start_sample, &loop_end_sample);
}
else if (version == 0x00000020 && is_id32be(0x800,sf, "VAGp")) {
/* ModernGroove: Ministry of Sound Edition (PS2) */
start_offset = 0x30;
channels = 2;
interleave = 0x800;
interleave_first = interleave - start_offset; /* includes header */
interleave_first_skip = start_offset;
loop_flag = 0;
}
else if (version == 0x40000000) {
/* Killzone (PS2) */
start_offset = 0x30;

View file

@ -0,0 +1,57 @@
#include "meta.h"
#include "../coding/coding.h"
/* .vig - from Konami/KCE Studio games [Pop'n Music 11~14 (PS2), Dance Dance Revolution SuperNova/X (PS2)] */
VGMSTREAM* init_vgmstream_vig_kces(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
uint32_t data_offset, data_size;
int loop_flag, channels, sample_rate, interleave;
uint32_t loop_start, loop_end;
/* checks */
if (read_u32be(0x00,sf) != 0x01006408)
return NULL;
/* .vig: actual extension from DDR exes */
if (!check_extensions(sf, "vig"))
return NULL;
/* note this is almost the same as GbTs, may be fused later */
/* 04: null */
data_offset = read_u32le(0x08,sf);
data_size = read_u32le(0x0C,sf); /* without padding */
loop_start = read_u32le(0x10,sf); /* (0x00 if not set) */
loop_end = read_u32le(0x14,sf); /* (0x00 if not set) */
sample_rate = read_s32le(0x18,sf);
channels = read_s32le(0x1C,sf);
/* 20: 0? */
interleave = read_u32le(0x24,sf); /* 0 for mono */
/* 30+: garbage from other data */
loop_flag = (loop_end > 0);
loop_end += loop_start; /* loop region matches PS-ADPCM flags */
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = ps_bytes_to_samples(data_size, channels);
vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channels);
vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end, channels);
vgmstream->coding_type = coding_PSX;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = interleave;
vgmstream->meta_type = meta_VIG_KCES;
if (!vgmstream_open_stream(vgmstream, sf, data_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View file

@ -1,52 +0,0 @@
#include "meta.h"
#include "../coding/coding.h"
/* VIS - from Konami games [AirForce Delta Strike (PS2) (PS2)] */
VGMSTREAM * init_vgmstream_vis(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t start_offset;
size_t data_size;
int loop_flag, channel_count;
/* checks */
if ( !check_extensions(streamFile,"vis") )
goto fail;
if (read_32bitBE(0x00,streamFile) != 0x56495341) /* "VISA" */
goto fail;
start_offset = 0x800;
data_size = get_streamfile_size(streamFile) - start_offset;
loop_flag = read_32bitLE(0x18,streamFile);
channel_count = read_32bitLE(0x20,streamFile); /* assumed */
/* 0x1c: always 0x10 */
/* 0x24: always 0x01 */
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_VIS;
vgmstream->sample_rate = read_32bitLE(0x08,streamFile);
vgmstream->num_samples = ps_bytes_to_samples(data_size,channel_count);
vgmstream->loop_start_sample = ps_bytes_to_samples(read_32bitLE(0x0c,streamFile),channel_count);
vgmstream->loop_end_sample = ps_bytes_to_samples(read_32bitLE(0x10,streamFile),channel_count);
vgmstream->coding_type = coding_PSX;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = read_32bitLE(0x14,streamFile); /* usually 0x10 or 0x4000 */
if (vgmstream->interleave_block_size)
vgmstream->interleave_last_block_size =
(data_size % (vgmstream->interleave_block_size*channel_count)) / channel_count;
read_string(vgmstream->stream_name,0x10+1, 0x28,streamFile);
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View file

@ -9,11 +9,11 @@ VGMSTREAM* init_vgmstream_waf(STREAMFILE* sf) {
/* checks */
if (!check_extensions(sf, "waf"))
goto fail;
if (!is_id32be(0x00,sf, "WAF\0"))
goto fail;
return NULL;
if (!check_extensions(sf, "waf"))
return NULL;
if (read_u32le(0x34,sf) + 0x38 != get_streamfile_size(sf))
goto fail;

View file

@ -575,6 +575,7 @@ VGMSTREAM* init_vgmstream_wwise_bnk(STREAMFILE* sf, int* p_prefetch) {
switch(ww.channel_layout) {
case mapping_7POINT1_surround: cfg.coupled_count = 3; break; /* 2ch+2ch+2ch+1ch+1ch, 5 streams */
case mapping_5POINT1_surround: /* 2ch+2ch+1ch+1ch, 4 streams */
case mapping_5POINT0_surround: /* 2ch+2ch+1ch, 3 streams [Bayonetta 3 (Switch)] */
case mapping_QUAD_side: cfg.coupled_count = 2; break; /* 2ch+2ch, 2 streams */
case mapping_2POINT1_xiph: /* 2ch+1ch, 2 streams */
case mapping_STEREO: cfg.coupled_count = 1; break; /* 2ch, 1 stream */

View file

@ -3,16 +3,15 @@
#include "../coding/coding.h"
static int xa_read_subsongs(STREAMFILE* sf, int target_subsong, off_t start, uint16_t* p_stream_config, off_t* p_stream_offset, size_t* p_stream_size, int* p_form2);
static int xa_read_subsongs(STREAMFILE* sf, int target_subsong, uint32_t start, uint32_t* p_stream_offset, uint32_t* p_stream_size);
static int xa_check_format(STREAMFILE* sf, off_t offset, int is_blocked);
/* XA - from Sony PS1 and Philips CD-i CD audio */
VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset;
uint32_t start_offset, stream_size = 0;
int loop_flag = 0, channels, sample_rate, bps;
int is_riff = 0, is_form2 = 0, is_blocked;
size_t stream_size = 0;
int total_subsongs = 0, target_subsong = sf->stream_index;
uint16_t target_config = 0;
@ -31,7 +30,7 @@ VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) {
}
else {
/* non-blocked (ISO 2048 mode1/data) or incorrectly ripped: use TXTH */
goto fail;
return NULL;
}
/* .xa: common
@ -40,9 +39,10 @@ VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) {
* .grn: Micro Machines (CDi)
* .an2: Croc (PS1) movies
* .xai: Quake II (PS1)
* .no: Incredible Crisis (PS1)
* (extensionless): bigfiles [Castlevania: Symphony of the Night (PS1)] */
if (!check_extensions(sf,"xa,str,pxa,grn,an2,,xai"))
goto fail;
return NULL;
/* Proper XA comes in raw (BIN 2352 mode2/form2) CD sectors, that contain XA subheaders.
* For headerless XA (ISO 2048 mode1/data) mode use TXTH. */
@ -53,7 +53,7 @@ VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) {
/* find subsongs as XA can interleave sectors using 'file' and 'channel' makers (see blocked_xa.c) */
if (/*!is_riff &&*/ is_blocked) {
total_subsongs = xa_read_subsongs(sf, target_subsong, start_offset, &target_config, &start_offset, &stream_size, &is_form2);
total_subsongs = xa_read_subsongs(sf, target_subsong, start_offset, &start_offset, &stream_size);
if (total_subsongs <= 0) goto fail;
}
else {
@ -63,34 +63,53 @@ VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) {
/* data is ok: parse header */
if (is_blocked) {
/* parse 0x18 sector header (also see blocked_xa.c) */
uint8_t xa_header = read_u8(start_offset + 0x13,sf);
uint32_t curr_info = read_u32be(start_offset + 0x10, sf);
uint16_t xa_config = (curr_info >> 16) & 0xFFFF; /* file+channel markers */
uint8_t xa_submode = (curr_info >> 8) & 0xFF;
uint8_t xa_header = (curr_info >> 0) & 0xFF;
/* header is repeated at 0x14 and could check if matches, but some ripped XA patch byte 0x01
* for some reason, and in rare cases has garbage [Incredible Crisis (PS1) XAPACK00.NO#5]
* (probably means a real PS1 only uses the first header, if it can play such XA) */
target_config = xa_config;
is_form2 = (xa_submode & 0x20);
switch((xa_header >> 0) & 3) { /* 0..1: mono/stereo */
case 0: channels = 1; break;
case 1: channels = 2; break;
default: goto fail;
default:
vgm_logi("XA: buggy data found\n");
goto fail;
}
switch((xa_header >> 2) & 3) { /* 2..3: sample rate */
case 0: sample_rate = 37800; break;
case 1: sample_rate = 18900; break;
default: goto fail;
default:
vgm_logi("XA: buggy data found\n");
goto fail;
}
switch((xa_header >> 4) & 3) { /* 4..5: bits per sample */
case 0: bps = 4; break; /* PS1 games only do 4-bit ADPCM */
case 1: bps = 8; break; /* Micro Machines (CDi) */
default: goto fail;
default:
vgm_logi("XA: buggy data found\n");
goto fail;
}
switch((xa_header >> 6) & 1) { /* 6: emphasis flag (should apply a de-emphasis filter) */
case 0: break;
default: /* very rare, waveform looks ok so maybe not needed [Croc (PS1) PACKx.str] */
default:
/* very rare, waveform looks ok so maybe not needed [Croc (PS1) PACKx.str] */
vgm_logi("XA: emphasis found\n");
break;
}
switch((xa_header >> 7) & 1) { /* 7: reserved */
case 0: break;
default:
vgm_logi("XA: unknown reserved bit found\n");
goto fail;
/* very rare, found in all regions and xa channel's headers but probably a mastering
* bug since repeated header is wrong [Incredible Crisis (PS1) XAPACK00.NO#5] */
vgm_logi("XA: reserved bit found\n");
break;
}
}
else {
@ -133,15 +152,16 @@ fail:
static int xa_check_format(STREAMFILE *sf, off_t offset, int is_blocked) {
uint8_t frame_hdr[0x10];
int i, j, sector = 0, skip = 0;
off_t test_offset = offset;
const size_t sector_size = (is_blocked ? 0x900 : 0x800);
const size_t extra_size = (is_blocked ? 0x18 : 0x00);
const size_t frame_size = 0x80;
const int sector_max = 3;
const int skip_max = 32; /* videos interleave 7 or 15 sectors + 1 audio sector, maybe 31 too */
uint8_t frame_hdr[0x10];
int sector = 0, skip = 0;
uint32_t test_offset = offset;
/* test frames inside CD sectors */
while (sector < sector_max) {
if (is_blocked) {
@ -159,11 +179,11 @@ static int xa_check_format(STREAMFILE *sf, off_t offset, int is_blocked) {
test_offset += extra_size; /* header */
for (i = 0; i < (sector_size / frame_size); i++) {
for (int i = 0; i < (sector_size / frame_size); i++) {
read_streamfile(frame_hdr, test_offset, sizeof(frame_hdr), sf);
/* XA frame checks: filter indexes should be 0..3, and shifts 0..C */
for (j = 0; j < 16; j++) {
for (int j = 0; j < 16; j++) {
uint8_t header = get_u8(frame_hdr + j);
if (((header >> 4) & 0xF) > 0x03)
goto fail;
@ -175,11 +195,10 @@ static int xa_check_format(STREAMFILE *sf, off_t offset, int is_blocked) {
if (get_u32be(frame_hdr+0x00) != get_u32be(frame_hdr+0x04) ||
get_u32be(frame_hdr+0x08) != get_u32be(frame_hdr+0x0c))
goto fail;
/* blank frames should always use 0x0c0c0c0c (due to how shift works) */
if (get_u32be(frame_hdr+0x00) == 0 &&
get_u32be(frame_hdr+0x04) == 0 &&
get_u32be(frame_hdr+0x08) == 0 &&
get_u32be(frame_hdr+0x0c) == 0)
/* blank frames should always use 0x0c0c0c0c due to how shift works, (in rare file-channels some frames may be blank though) */
if (i == 0 &&
get_u32be(frame_hdr+0x00) == 0 && get_u32be(frame_hdr+0x04) == 0 &&
get_u32be(frame_hdr+0x08) == 0 && get_u32be(frame_hdr+0x0c) == 0)
goto fail;
test_offset += 0x80;
@ -196,14 +215,11 @@ fail:
}
#define XA_SUBSONG_MAX 1024 /* +500 found in bigfiles like Castlevania SOTN */
#define XA_MAX_CHANNELS 32 /* usually 08-16, seen ~24 in Langrisser V (PS1) */
typedef struct xa_subsong_t {
uint16_t config;
off_t start;
int form2;
int sectors;
int end_flag;
typedef struct {
uint32_t info;
int subsong;
} xa_subsong_t;
/* Get subsong info, as real XA data interleaves N sectors/subsongs (often 8/16). Extractors deinterleave
@ -211,17 +227,38 @@ typedef struct xa_subsong_t {
* usable sectors for bytes-to-samples.
*
* Bigfiles that paste tons of XA together are slow to parse since we need to read every sector to
* count totals, but XA subsong handling is mainly for educational purposes. */
static int xa_read_subsongs(STREAMFILE* sf, int target_subsong, off_t start, uint16_t* p_stream_config, off_t* p_stream_offset, size_t* p_stream_size, int* p_form2) {
xa_subsong_t *cur_subsong = NULL;
xa_subsong_t subsongs[XA_SUBSONG_MAX] = {0};
* count totals, but XA subsong handling is mainly for educational purposes.
*
* Raw XA CD sectors are interleaved and classified into "files" and "channels" due to how CD driver/audio buffer works.
* Devs select one file+channel (or just channel?) to play and CD ignores non-target sectors.
* "files" can be any number in any order (but usually 00~64), and "channels" seem to be max 00~0F.
* file+channel (=song) ends with a flag or when file changes; simplified example (upper=file, lower=channel):
* 0101 0102 0101 0102 0201 0202 0201 0202 0101 0102
* adapted to subsongs:
* 0101 #1 (all 0101 sectors until file change = 2 sectors)
* 0102 #2
* 0201 #3
* 0202 #4
* 0101 #5 (different than first subsong since there was another file in between, 1 sector)
* 0102 #6
*
* For video + audio the layout is the same with extra flags to detect video/audio sectors:
* 0101v 0101v 0101v 0101v 0101v 0101v 0101v 0101a (usually 7 video sectors per 1 audio sector)
*
* CDs can't have 0101 0101 0101 ... audio sectors (need to interleave other channels, or repeat data),
* but can be seen in demuxed XA. Combinations like a 0101 after 0201 probably only happen when devs
* paste many XAs into a bigfile, which likely would jump via offsets in exe to the XA start (can be
* split), but they are detected here for convenience.
*/
static int xa_read_subsongs(STREAMFILE* sf, int target_subsong, uint32_t start, uint32_t* p_stream_offset, uint32_t* p_stream_size) {
const size_t sector_size = 0x930;
uint16_t prev_config;
int i, subsongs_count = 0;
size_t file_size;
off_t offset;
STREAMFILE *sf_test = NULL;
uint8_t header[4];
int subsongs_count = 0;
uint32_t offset, file_size;
STREAMFILE* sf_test = NULL;
xa_subsong_t xa_subsongs[XA_MAX_CHANNELS] = {0};
uint32_t target_start = 0xFFFFFFFFu;
int target_sectors = 0;
/* buffer to speed up header reading; bigger (+0x8000) is actually faster than very small (~0x10),
@ -229,99 +266,72 @@ static int xa_read_subsongs(STREAMFILE* sf, int target_subsong, off_t start, uin
sf_test = reopen_streamfile(sf, 0x10000);
if (!sf_test) goto fail;
prev_config = 0xFFFFu;
file_size = get_streamfile_size(sf);
offset = start;
if (target_subsong == 0) target_subsong = 1;
/* read XA sectors */
while (offset < file_size) {
uint16_t xa_config;
uint8_t xa_submode;
int is_audio, is_eof;
uint32_t curr_info = read_u32be(offset + 0x10, sf_test);
//uint8_t xa_file = (curr_info >> 24) & 0xFF;
uint8_t xa_chan = (curr_info >> 16) & 0xFF;
uint8_t xa_submode = (curr_info >> 8) & 0xFF;
//uint8_t xa_header = (curr_info >> 0) & 0xFF;
bool is_audio = !(xa_submode & 0x08) && (xa_submode & 0x04) && !(xa_submode & 0x02);
bool is_eof = (xa_submode & 0x80);
bool is_target = false;
read_streamfile(header, offset + 0x10, sizeof(header), sf_test);
xa_config = get_u16be(header + 0x00); /* file+channel markers */
xa_submode = get_u8 (header + 0x02); /* flags */
is_audio = !(xa_submode & 0x08) && (xa_submode & 0x04) && !(xa_submode & 0x02);
is_eof = (xa_submode & 0x80);
if (xa_chan >= XA_MAX_CHANNELS) {
VGM_LOG("XA: too many channels: %x\n", xa_chan);
goto fail;
}
VGM_ASSERT((xa_submode & 0x01), "XA: end of audio at %lx\n", offset); /* rare, signals last sector [Tetris (CD-i)] */
//;VGM_ASSERT(is_eof, "XA: eof at %lx\n", offset);
//;VGM_ASSERT(!is_audio, "XA: not audio at %lx\n", offset);
//;VGM_ASSERT((xa_submode & 0x01), "XA: end of audio at %x\n", offset); /* rare, signals last sector [Tetris (CD-i), Langrisser V (PS1)] */
//;VGM_ASSERT(is_eof, "XA: eof %02x%02x at %x\n", xa_file, xa_chan, offset); /* this sector still has data */
//;VGM_ASSERT(!is_audio, "XA: not audio at %x\n", offset);
if (!is_audio) {
offset += sector_size;
continue;
}
/* detect file+channel change = sector of new subsong or existing subsong
* (happens on every sector for interleaved XAs but only once for deint'd or video XAs) */
if (xa_config != prev_config)
{
/* find if this sector/config belongs to known subsong that hasn't ended
* (unsure if repeated configs+end flag is possible but probably in giant XAs) */
cur_subsong = NULL;
for (i = 0; i < subsongs_count; i++) { /* search algo could be improved, meh */
if (subsongs[i].config == xa_config && !subsongs[i].end_flag) {
cur_subsong = &subsongs[i];
break;
}
}
/* use info without submode to detect new subsongs */
curr_info = curr_info & 0xFFFF00FF;
/* old subsong not found = add new to list */
if (cur_subsong == NULL) {
uint8_t xa_channel = get_u8(header + 0x01);
/* when file+channel changes mark prev subsong of the same channel as finished
* (this ensures reused ids in bigfiles are handled properly, ex.: 0101... 0201... 0101...) */
for (i = subsongs_count - 1; i >= 0; i--) {
uint16_t subsong_config = subsongs[i].config;
uint8_t subsong_channel = subsong_config & 0xFF;
if (xa_config != subsong_config && xa_channel == subsong_channel) {
subsongs[i].end_flag = 1;
break;
}
}
cur_subsong = &subsongs[subsongs_count];
subsongs_count++;
if (subsongs_count >= XA_SUBSONG_MAX) goto fail;
cur_subsong->config = xa_config;
cur_subsong->form2 = (xa_submode & 0x20);
cur_subsong->start = offset;
//cur_subsong->sectors = 0;
//cur_subsong->end_flag = 0;
//;VGM_LOG("XA: new subsong %i with config %04x at %lx\n", subsongs_count, xa_config, offset);
}
prev_config = xa_config;
}
else {
if (cur_subsong == NULL) goto fail;
/* changes for a current channel = new subsong */
if (xa_subsongs[xa_chan].info != curr_info) {
subsongs_count++;
xa_subsongs[xa_chan].info = curr_info;
xa_subsongs[xa_chan].subsong = subsongs_count;
}
cur_subsong->sectors++;
if (is_eof)
cur_subsong->end_flag = 1;
/* current channel is still from expected subsong */
is_target = xa_subsongs[xa_chan].subsong == target_subsong;
if (is_target) {
if (target_start == 0xFFFFFFFFu)
target_start = offset;
target_sectors++;
}
/* remove info (after handling sector) so next comparison for this channel adds a subsong */
if (is_eof) {
xa_subsongs[xa_chan].info = 0;
xa_subsongs[xa_chan].subsong = 0;
}
offset += sector_size;
}
VGM_ASSERT(subsongs_count < 1, "XA: no audio found\n");
VGM_ASSERT(subsongs_count < 1, "XA: no audio found\n"); /* probably not possible even in videos */
if (target_subsong == 0) target_subsong = 1;
if (target_subsong < 0 || target_subsong > subsongs_count || subsongs_count < 1) goto fail;
if (target_sectors == 0) goto fail;
cur_subsong = &subsongs[target_subsong - 1];
*p_stream_config = cur_subsong->config;
*p_stream_offset = cur_subsong->start;
*p_stream_size = cur_subsong->sectors * sector_size;
*p_form2 = cur_subsong->form2;
*p_stream_offset = target_start;
*p_stream_size = target_sectors * sector_size;
//;VGM_LOG("XA: subsong config=%x, offset=%lx, size=%x, form2=%i\n", *p_stream_config, *p_stream_offset, *p_stream_size, *p_form2);
//;VGM_LOG("XA: subsong offset=%x, size=%x\n", *p_stream_offset, *p_stream_size);
close_streamfile(sf_test);
return subsongs_count;

View file

@ -24,6 +24,13 @@ const char* filename_extension(const char* pathname) {
}
/* math helpers */
uint32_t clamp_u32(uint32_t v, uint32_t min, uint32_t max) {
if (v < min) return min;
if (v > max) return max;
return v;
}
int round10(int val) {
int round_val = val % 10;
if (round_val < 5) /* half-down rounding */
@ -32,6 +39,15 @@ int round10(int val) {
return val + (10 - round_val);
}
size_t align_size_to_block(size_t value, size_t block_align) {
if (!block_align)
return 0;
size_t extra_size = value % block_align;
if (extra_size == 0) return value;
return (value + block_align - extra_size);
}
/* length is maximum length of dst. dst will always be null-terminated if
* length > 0 */
void concatn(int length, char * dst, const char * src) {
@ -43,11 +59,10 @@ void concatn(int length, char * dst, const char * src) {
dst[i]='\0';
}
size_t align_size_to_block(size_t value, size_t block_align) {
if (!block_align)
return 0;
size_t extra_size = value % block_align;
if (extra_size == 0) return value;
return (value + block_align - extra_size);
bool check_subsongs(int* target_subsong, int total_subsongs) {
if (*target_subsong == 0)
*target_subsong = 1;
if (*target_subsong < 0 || *target_subsong > total_subsongs || total_subsongs < 1)
return false;
return true;
}

View file

@ -48,14 +48,19 @@ static inline /*const*/ uint64_t get_id64be(const char* s) {
/* less common functions, no need to inline */
uint32_t clamp_u32(uint32_t v, uint32_t min, uint32_t max);
int round10(int val);
size_t align_size_to_block(size_t value, size_t block_align);
/* return a file's extension (a pointer to the first character of the
* extension in the original filename or the ending null byte if no extension */
const char* filename_extension(const char* pathname);
void concatn(int length, char * dst, const char * src);
size_t align_size_to_block(size_t value, size_t block_align);
/* checks max subsongs and setups target */
bool check_subsongs(int* target_subsong, int total_subsongs);
#endif

View file

@ -1,10 +1,11 @@
#ifndef _BITSTREAM_LSB_H
#define _BITSTREAM_LSB_H
#include "../streamtypes.h"
#include <stdint.h>
/* Simple bitreader for Vorbis' bit style, in 'least significant byte' (LSB) format.
* Example: 0x12345678 is read as 12,34,56,78 (continuous).
* Example: with 0x1234 = 00010010 00110100, reading 5b + 6b = 10010 100000
* (first lower 5b, then next upper 3b and next lower 3b = 6b)
* Kept in .h since it's slightly faster (compiler can optimize statics better using default compile flags). */
@ -23,7 +24,7 @@ static inline void bl_setup(bitstream_t* b, uint8_t* buf, size_t bufsize) {
/* same as (1 << bits) - 1, but that seems to trigger some nasty UB when bits = 32
* (though in theory (1 << 32) = 0, 0 - 1 = UINT_MAX, but gives 0 compiling in some cases, but not always) */
static const uint32_t MASK_TABLE[33] = {
static const uint32_t MASK_TABLE_LSB[33] = {
0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff,
0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff, 0x0001ffff,
0x0003ffff, 0x0007ffff, 0x000fffff, 0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff, 0x01ffffff, 0x03ffffff,
@ -40,7 +41,7 @@ static inline int bl_get(bitstream_t* ib, uint32_t bits, uint32_t* value) {
pos = ib->b_off / 8; /* byte offset */
shift = ib->b_off % 8; /* bit sub-offset */
mask = MASK_TABLE[bits]; /* to remove upper in highest byte */
mask = MASK_TABLE_LSB[bits]; /* to remove upper in highest byte */
val = ib->buf[pos+0] >> shift;
if (bits + shift > 8) {

View file

@ -1,10 +1,11 @@
#ifndef _BITSTREAM_MSB_H
#define _BITSTREAM_MSB_H
#include "../streamtypes.h"
#include <stdint.h>
/* Simple bitreader for MPEG/standard bit style, in 'most significant byte' (MSB) format.
* Example: 0x12345678 is read as 78,56,34,12 then each byte's bits.
* Example: with 0x1234 = 00010010 00110100, reading 5b + 6b = 00010 010001
* (first upper 5b, then next lower 3b and next upper 3b = 6b)
* Kept in .h since it's slightly faster (compiler can optimize statics better using default compile flags). */
typedef struct {
@ -14,7 +15,7 @@ typedef struct {
uint32_t b_off; /* current offset in bits inside buffer */
} bitstream_t;
/* convenience util */
static inline void bm_setup(bitstream_t* bs, uint8_t* buf, size_t bufsize) {
bs->buf = buf;
bs->bufsize = bufsize;
@ -60,10 +61,20 @@ static inline int bm_pos(bitstream_t* bs) {
return bs->b_off;
}
/* same as (1 << bits) - 1, but that seems to trigger some nasty UB when bits = 32
* (though in theory (1 << 32) = 0, 0 - 1 = UINT_MAX, but gives 0 compiling in some cases, but not always) */
static const uint32_t MASK_TABLE_MSB[33] = {
0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff,
0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff, 0x0001ffff,
0x0003ffff, 0x0007ffff, 0x000fffff, 0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff, 0x01ffffff, 0x03ffffff,
0x07ffffff, 0x0fffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff
};
/* Read bits (max 32) from buf and update the bit offset. Order is BE (MSB). */
static inline int bm_get(bitstream_t* ib, uint32_t bits, uint32_t* value) {
uint32_t shift, pos, val;
int i, bit_buf, bit_val;
uint32_t shift, pos, mask;
uint64_t val; //TODO: could use u32 with some shift fiddling
int left;
if (bits > 32 || ib->b_off + bits > ib->b_max)
goto fail;
@ -71,9 +82,11 @@ static inline int bm_get(bitstream_t* ib, uint32_t bits, uint32_t* value) {
pos = ib->b_off / 8; /* byte offset */
shift = ib->b_off % 8; /* bit sub-offset */
#if 1 //naive approach
#if 0 //naive approach
int bit_val, bit_buf;
val = 0;
for (i = 0; i < bits; i++) {
for (int i = 0; i < bits; i++) {
bit_buf = (1U << (8-1-shift)) & 0xFF; /* bit check for buf */
bit_val = (1U << (bits-1-i)); /* bit to set in value */
@ -86,12 +99,10 @@ static inline int bm_get(bitstream_t* ib, uint32_t bits, uint32_t* value) {
pos++;
}
}
#else //has bugs
pos = ib->b_off / 8; /* byte offset */
shift = ib->b_off % 8; /* bit sub-offset */
uint32_t mask = MASK_TABLE[bits]; /* to remove upper in highest byte */
#else
mask = MASK_TABLE_MSB[bits]; /* to remove upper in highest byte */
int left = 0;
left = 0;
if (bits == 0)
val = 0;
else
@ -102,12 +113,12 @@ static inline int bm_get(bitstream_t* ib, uint32_t bits, uint32_t* value) {
left = 16 - (bits + shift);
if (bits + shift > 16) {
val = (val << 8u) | ib->buf[pos+2];
left = 32 - (bits + shift);
left = 24 - (bits + shift);
if (bits + shift > 24) {
val = (val << 8u) | ib->buf[pos+3];
left = 32 - (bits + shift);
if (bits + shift > 32) {
val = (val << 8u) | ib->buf[pos+4]; /* upper bits are lost (shifting over 32) */ TO-DO
val = (val << 8u) | ib->buf[pos+4];
left = 40 - (bits + shift);
}
}

Some files were not shown because too many files have changed in this diff Show more