vgmstream/src/meta/aax.c
bnnm 5672cac597 Move some meta STREAMFILEs to separate file for visibility
The code may be standardized and extended in the future for some meta
needing similar features, so this is just to signal its existence
2017-12-06 12:15:27 +01:00

748 lines
24 KiB
C

#include "meta.h"
#include "aax_streamfile.h"
struct utf_query
{
/* if 0 */
const char *name;
int index;
};
struct offset_size_pair
{
uint32_t offset;
uint32_t size;
};
struct utf_query_result
{
int valid; /* table is valid */
int found;
int type; /* one of COLUMN_TYPE_* */
union
{
uint64_t value_u64;
uint32_t value_u32;
uint16_t value_u16;
uint8_t value_u8;
float value_float;
struct offset_size_pair value_data;
uint32_t value_string;
} value;
/* info for the queried table */
uint32_t rows;
uint32_t name_offset;
uint32_t string_table_offset;
uint32_t data_offset;
};
static struct utf_query_result analyze_utf(STREAMFILE *infile, long offset,
const struct utf_query *query);
static struct utf_query_result query_utf(STREAMFILE *infile, long offset,
const struct utf_query *query);
static struct utf_query_result query_utf_nofail(STREAMFILE *infile, const long offset,
const struct utf_query *query, int *error);
static struct utf_query_result query_utf_key(STREAMFILE *infile, const long offset,
int index, const char *name, int *error);
static uint8_t query_utf_1byte(STREAMFILE *infile, const long offset,
int index, const char *name, int *error);
static struct offset_size_pair query_utf_data(STREAMFILE *infile, const long offset,
int index, const char *name, int *error);
#define COLUMN_STORAGE_MASK 0xf0
#define COLUMN_STORAGE_PERROW 0x50
#define COLUMN_STORAGE_CONSTANT 0x30
#define COLUMN_STORAGE_ZERO 0x10
#define COLUMN_TYPE_MASK 0x0f
#define COLUMN_TYPE_DATA 0x0b
#define COLUMN_TYPE_STRING 0x0a
#define COLUMN_TYPE_FLOAT 0x08
#define COLUMN_TYPE_8BYTE 0x06
#define COLUMN_TYPE_4BYTE 0x04
#define COLUMN_TYPE_2BYTE2 0x03
#define COLUMN_TYPE_2BYTE 0x02
#define COLUMN_TYPE_1BYTE2 0x01
#define COLUMN_TYPE_1BYTE 0x00
struct utf_column_info
{
uint8_t type;
const char *column_name;
long constant_offset;
};
struct utf_table_info
{
long table_offset;
uint32_t table_size;
uint32_t schema_offset;
uint32_t rows_offset;
uint32_t string_table_offset;
uint32_t data_offset;
const char *string_table;
const char *table_name;
uint16_t columns;
uint16_t row_width;
uint32_t rows;
const struct utf_column_info *schema;
};
/* AAX - segmented ADX [Padora's Tower (Wii)] */
VGMSTREAM * init_vgmstream_aax(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
STREAMFILE * streamFileAAX = NULL;
STREAMFILE * streamFileADX = NULL;
char filename[PATH_LIMIT];
off_t *segment_offset = NULL;
off_t *segment_size = NULL;
int32_t sample_count;
int table_error = 0;
int loop_flag = 0;
int32_t loop_start_sample=0;
int32_t loop_end_sample=0;
int loop_segment = 0;
aax_codec_data *data = NULL;
const long AAX_offset = 0;
int channel_count = 0, segment_count;
int sample_rate = 0;
int i;
long aax_data_offset;
/* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename));
if (strcasecmp("aax",filename_extension(filename))) goto fail;
/* get AAX entry count, data offset */
{
struct utf_query_result result;
long aax_string_table_offset;
long aax_string_table_size;
result = query_utf_nofail(streamFile, AAX_offset, NULL, &table_error);
if (table_error) goto fail;
segment_count = result.rows;
aax_string_table_offset = AAX_offset + 8 + result.string_table_offset;
aax_data_offset = AAX_offset + 8 + result.data_offset;
aax_string_table_size = aax_data_offset - aax_string_table_offset;
if (result.name_offset+4 > aax_string_table_size) goto fail;
if (read_32bitBE(aax_string_table_offset + result.name_offset,
streamFile) != 0x41415800) /* "AAX\0" */
goto fail;
}
segment_offset = calloc(segment_count,sizeof(off_t));
if (!segment_offset)
goto fail;
segment_size = calloc(segment_count,sizeof(off_t));
if (!segment_size)
goto fail;
/* get offsets of constituent ADXs */
for (i = 0; i < segment_count; i++)
{
struct offset_size_pair offset_size;
offset_size = query_utf_data(streamFile, AAX_offset, i, "data", &table_error);
if (table_error) goto fail;
segment_offset[i] = aax_data_offset + offset_size.offset;
segment_size[i] = offset_size.size;
}
streamFileAAX = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (!streamFileAAX) goto fail;
data = malloc(sizeof(aax_codec_data));
if (!data) goto fail;
data->segment_count = segment_count;
data->adxs = malloc(sizeof(STREAMFILE *)*segment_count);
if (!data->adxs) goto fail;
for (i=0;i<segment_count;i++) {
data->adxs[i] = NULL;
}
data->sample_counts = calloc(segment_count,sizeof(int32_t));
if (!data->sample_counts) goto fail;
/* for each segment */
for (i = 0; i < segment_count; i++)
{
VGMSTREAM *adx;
/*printf("try opening segment %d/%d %x\n",i,segment_count,segment_offset[i]);*/
streamFileADX = open_aax_with_STREAMFILE(streamFileAAX,segment_offset[i],segment_size[i]);
if (!streamFileADX) goto fail;
adx = data->adxs[i] = init_vgmstream_adx(streamFileADX);
if (!adx)
goto fail;
data->sample_counts[i] = adx->num_samples;
close_streamfile(streamFileADX); streamFileADX = NULL;
if (i == 0)
{
channel_count = adx->channels;
sample_rate = adx->sample_rate;
}
else
{
if (channel_count != adx->channels)
goto fail;
if (sample_rate != adx->sample_rate)
goto fail;
}
if (adx->loop_flag != 0)
goto fail;
/* save start things so we can restart for seeking/looping */
/* copy the channels */
memcpy(adx->start_ch,adx->ch,sizeof(VGMSTREAMCHANNEL)*adx->channels);
/* copy the whole VGMSTREAM */
memcpy(adx->start_vgmstream,adx,sizeof(VGMSTREAM));
}
sample_count = 0;
loop_flag = 0;
for (i = 0; i < segment_count; i++)
{
int segment_loop_flag = query_utf_1byte(streamFile, AAX_offset, i,
"lpflg", &table_error);
if (table_error) segment_loop_flag = 0;
if (!loop_flag && segment_loop_flag)
{
loop_start_sample = sample_count;
loop_segment = i;
}
sample_count += data->sample_counts[i];
if (!loop_flag && segment_loop_flag)
{
loop_end_sample = sample_count;
loop_flag = 1;
}
}
vgmstream = allocate_vgmstream(channel_count,loop_flag);
vgmstream->num_samples = sample_count;
vgmstream->sample_rate = sample_rate;
vgmstream->loop_start_sample = loop_start_sample;
vgmstream->loop_end_sample = loop_end_sample;
vgmstream->coding_type = data->adxs[0]->coding_type;
vgmstream->layout_type = layout_aax;
vgmstream->meta_type = meta_AAX;
vgmstream->ch[0].streamfile = streamFileAAX;
data->current_segment = 0;
data->loop_segment = loop_segment;
vgmstream->codec_data = data;
free(segment_offset);
free(segment_size);
return vgmstream;
/* clean up anything we may have opened */
fail:
if (streamFileAAX) close_streamfile(streamFileAAX);
if (streamFileADX) close_streamfile(streamFileADX);
if (vgmstream) close_vgmstream(vgmstream);
if (segment_offset) free(segment_offset);
if (segment_size) free(segment_size);
if (data) {
if (data->adxs)
{
int i;
for (i=0;i<data->segment_count;i++)
if (data->adxs)
close_vgmstream(data->adxs[i]);
free(data->adxs);
}
if (data->sample_counts)
{
free(data->sample_counts);
}
free(data);
}
return NULL;
}
/* @UTF table reading, abridged */
static struct utf_query_result analyze_utf(STREAMFILE *infile, const long offset, const struct utf_query *query)
{
unsigned char buf[4];
struct utf_table_info table_info;
char *string_table = NULL;
struct utf_column_info * schema = NULL;
struct utf_query_result result;
uint32_t table_name_string;
int string_table_size;
result.valid = 0;
table_info.table_offset = offset;
/* check header */
{
static const char UTF_signature[4] = "@UTF"; /* intentionally unterminated */
if (4 != read_streamfile(buf, offset, 4, infile)) goto cleanup_error;
if (memcmp(buf, UTF_signature, sizeof(UTF_signature)))
{
goto cleanup_error;
}
}
/* get table size */
table_info.table_size = read_32bitBE(offset+4, infile);
table_info.schema_offset = 0x20;
table_info.rows_offset = read_32bitBE(offset+8, infile);
table_info.string_table_offset = read_32bitBE(offset+0xc,infile);
table_info.data_offset = read_32bitBE(offset+0x10,infile);
table_name_string = read_32bitBE(offset+0x14,infile);
table_info.columns = read_16bitBE(offset+0x18,infile);
table_info.row_width = read_16bitBE(offset+0x1a,infile);
table_info.rows = read_32bitBE(offset+0x1c,infile);
/* allocate for string table */
string_table_size = table_info.data_offset-table_info.string_table_offset;
string_table = malloc(string_table_size+1);
if (!string_table) goto cleanup_error;
table_info.string_table = string_table;
memset(string_table, 0, string_table_size+1);
/* load schema */
schema = malloc(sizeof(struct utf_column_info) * table_info.columns);
if (!schema) goto cleanup_error;
{
int i;
long schema_current_offset = table_info.schema_offset;
for (i = 0; i < table_info.columns; i++)
{
schema[i].type = read_8bit(schema_current_offset,infile);
schema_current_offset ++;
schema[i].column_name = string_table + read_32bitBE(schema_current_offset,infile);
schema_current_offset += 4;
if ((schema[i].type & COLUMN_STORAGE_MASK) == COLUMN_STORAGE_CONSTANT)
{
schema[i].constant_offset = schema_current_offset;
switch (schema[i].type & COLUMN_TYPE_MASK)
{
case COLUMN_TYPE_8BYTE:
case COLUMN_TYPE_DATA:
schema_current_offset+=8;
break;
case COLUMN_TYPE_STRING:
case COLUMN_TYPE_FLOAT:
case COLUMN_TYPE_4BYTE:
schema_current_offset+=4;
break;
case COLUMN_TYPE_2BYTE2:
case COLUMN_TYPE_2BYTE:
schema_current_offset+=2;
break;
case COLUMN_TYPE_1BYTE2:
case COLUMN_TYPE_1BYTE:
schema_current_offset++;
break;
default:
goto cleanup_error;
}
}
}
}
table_info.schema = schema;
/* read string table */
read_streamfile((unsigned char *)string_table,
table_info.string_table_offset+8+offset,
string_table_size, infile);
table_info.table_name = table_info.string_table+table_name_string;
/* fill in the default stuff */
result.found = 0;
result.rows = table_info.rows;
result.name_offset = table_name_string;
result.string_table_offset = table_info.string_table_offset;
result.data_offset = table_info.data_offset;
/* explore the values */
if (query) {
int i, j;
for (i = 0; i < table_info.rows; i++)
{
uint32_t row_offset =
table_info.table_offset + 8 + table_info.rows_offset +
i * table_info.row_width;
const uint32_t row_start_offset = row_offset;
if (query && i != query->index) continue;
for (j = 0; j < table_info.columns; j++)
{
uint8_t type = table_info.schema[j].type;
long constant_offset = table_info.schema[j].constant_offset;
int constant = 0;
int qthis = (query && i == query->index &&
!strcmp(table_info.schema[j].column_name, query->name));
if (qthis)
{
result.found = 1;
result.type = schema[j].type & COLUMN_TYPE_MASK;
}
switch (schema[j].type & COLUMN_STORAGE_MASK)
{
case COLUMN_STORAGE_PERROW:
break;
case COLUMN_STORAGE_CONSTANT:
constant = 1;
break;
case COLUMN_STORAGE_ZERO:
if (qthis)
{
memset(&result.value, 0,
sizeof(result.value));
}
continue;
default:
goto cleanup_error;
}
if (1)
{
long data_offset;
int bytes_read;
if (constant)
{
data_offset = constant_offset;
}
else
{
data_offset = row_offset;
}
switch (type & COLUMN_TYPE_MASK)
{
case COLUMN_TYPE_STRING:
{
uint32_t string_offset;
string_offset = read_32bitBE(data_offset, infile);
bytes_read = 4;
if (qthis)
{
result.value.value_string = string_offset;
}
}
break;
case COLUMN_TYPE_DATA:
{
uint32_t vardata_offset, vardata_size;
vardata_offset = read_32bitBE(data_offset, infile);
vardata_size = read_32bitBE(data_offset+4, infile);
bytes_read = 8;
if (qthis)
{
result.value.value_data.offset = vardata_offset;
result.value.value_data.size = vardata_size;
}
}
break;
case COLUMN_TYPE_8BYTE:
{
uint64_t value =
read_32bitBE(data_offset, infile);
value <<= 32;
value |=
read_32bitBE(data_offset+4, infile);
if (qthis)
{
result.value.value_u64 = value;
}
bytes_read = 8;
break;
}
case COLUMN_TYPE_4BYTE:
{
uint32_t value =
read_32bitBE(data_offset, infile);
if (qthis)
{
result.value.value_u32 = value;
}
bytes_read = 4;
}
break;
case COLUMN_TYPE_2BYTE2:
case COLUMN_TYPE_2BYTE:
{
uint16_t value =
read_16bitBE(data_offset, infile);
if (qthis)
{
result.value.value_u16 = value;
}
bytes_read = 2;
}
break;
case COLUMN_TYPE_FLOAT:
if (sizeof(float) == 4)
{
union {
float float_value;
uint32_t int_value;
} int_float;
int_float.int_value = read_32bitBE(data_offset, infile);
if (qthis)
{
result.value.value_float = int_float.float_value;
}
}
else
{
read_32bitBE(data_offset, infile);
if (qthis)
{
goto cleanup_error;
}
}
bytes_read = 4;
break;
case COLUMN_TYPE_1BYTE2:
case COLUMN_TYPE_1BYTE:
{
uint8_t value =
read_8bit(data_offset, infile);
if (qthis)
{
result.value.value_u8 = value;
}
bytes_read = 1;
}
break;
default:
goto cleanup_error;
}
if (!constant)
{
row_offset += bytes_read;
}
} /* useless if end */
} /* column for loop end */
if (row_offset - row_start_offset != table_info.row_width)
goto cleanup_error;
if (query && i >= query->index) break;
} /* row for loop end */
} /* explore values block end */
//cleanup:
result.valid = 1;
cleanup_error:
if (string_table)
{
free(string_table);
string_table = NULL;
}
if (schema)
{
free(schema);
schema = NULL;
}
return result;
}
static struct utf_query_result query_utf(STREAMFILE *infile, const long offset, const struct utf_query *query)
{
return analyze_utf(infile, offset, query);
}
static struct utf_query_result query_utf_nofail(STREAMFILE *infile, const long offset, const struct utf_query *query, int *error)
{
const struct utf_query_result result = query_utf(infile, offset, query);
if (error)
{
*error = 0;
if (!result.valid) *error = 1;
if (query && !result.found) *error = 1;
}
return result;
}
static struct utf_query_result query_utf_key(STREAMFILE *infile, const long offset, int index, const char *name, int *error)
{
struct utf_query query;
query.index = index;
query.name = name;
return query_utf_nofail(infile, offset, &query, error);
}
static uint8_t query_utf_1byte(STREAMFILE *infile, const long offset, int index, const char *name, int *error)
{
struct utf_query_result result = query_utf_key(infile, offset, index, name, error);
if (error)
{
if (result.type != COLUMN_TYPE_1BYTE) *error = 1;
}
return result.value.value_u8;
}
static uint32_t query_utf_4byte(STREAMFILE *infile, const long offset, int index, const char *name, int *error)
{
struct utf_query_result result = query_utf_key(infile, offset, index, name, error);
if (error)
{
if (result.type != COLUMN_TYPE_4BYTE) *error = 1;
}
return result.value.value_u32;
}
static struct offset_size_pair query_utf_data(STREAMFILE *infile, const long offset,
int index, const char *name, int *error)
{
struct utf_query_result result = query_utf_key(infile, offset, index, name, error);
if (error)
{
if (result.type != COLUMN_TYPE_DATA) *error = 1;
}
return result.value.value_data;
}
/* CRI's UTF wrapper around DSP */
VGMSTREAM * init_vgmstream_utf_dsp(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
char filename[PATH_LIMIT];
int table_error = 0;
int loop_flag = 0;
const long top_offset = 0;
int channel_count;
int sample_rate;
long sample_count;
long top_data_offset, segment_count;
long body_offset, body_size;
long header_offset, header_size;
/* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename));
//if (strcasecmp("aax",filename_extension(filename))) goto fail;
/* get entry count, data offset */
{
struct utf_query_result result;
long top_string_table_offset;
long top_string_table_size;
long name_offset;
result = query_utf_nofail(streamFile, top_offset, NULL, &table_error);
if (table_error) goto fail;
segment_count = result.rows;
if (segment_count != 1) goto fail; // only simple stuff for now
top_string_table_offset = top_offset + 8 + result.string_table_offset;
top_data_offset = top_offset + 8 + result.data_offset;
top_string_table_size = top_data_offset - top_string_table_offset;
if (result.name_offset+10 > top_string_table_size) goto fail;
name_offset = top_string_table_offset + result.name_offset;
if (read_32bitBE(name_offset, streamFile) != 0x41445043 ||// "ADPC"
read_32bitBE(name_offset+4, streamFile) != 0x4D5F5749 ||// "M_WI"
read_16bitBE(name_offset+8, streamFile) != 0x4900) // "I\0"
goto fail;
}
{
struct offset_size_pair offset_size;
offset_size = query_utf_data(streamFile, top_offset, 0, "data", &table_error);
if (table_error) goto fail;
body_offset = top_data_offset + offset_size.offset;
body_size = offset_size.size;
offset_size = query_utf_data(streamFile, top_offset, 0, "header", &table_error);
if (table_error) goto fail;
header_offset = top_data_offset + offset_size.offset;
header_size = offset_size.size;
}
channel_count = query_utf_1byte(streamFile, top_offset, 0, "nch", &table_error);
sample_count = query_utf_4byte(streamFile, top_offset, 0, "nsmpl", &table_error);
sample_rate = query_utf_4byte(streamFile, top_offset, 0, "sfreq", &table_error);
if (table_error) goto fail;
if (channel_count != 1 && channel_count != 2) goto fail;
if (header_size != channel_count * 0x60) goto fail;
vgmstream = allocate_vgmstream(channel_count,loop_flag);
vgmstream->num_samples = sample_count;
vgmstream->sample_rate = sample_rate;
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->layout_type = layout_none;
vgmstream->meta_type = meta_UTF_DSP;
{
int i,j;
long channel_size = (body_size+7)/8*8/channel_count;
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 = body_offset + i * channel_size;
for (j=0;j<16;j++)
{
vgmstream->ch[i].adpcm_coef[j] =
read_16bitBE(header_offset + 0x60*i + 0x1c + j*2, streamFile);
}
}
}
return vgmstream;
/* clean up anything we may have opened */
fail:
if (vgmstream) close_vgmstream(vgmstream);
return NULL;
}