diff --git a/src/coding/coding.h b/src/coding/coding.h index ef9db19b..3e9ff7e5 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -203,9 +203,16 @@ void flush_ea_mt(VGMSTREAM *vgmstream); void seek_ea_mt(VGMSTREAM * vgmstream, int32_t num_sample); void free_ea_mt(ea_mt_codec_data *data, int channels); +/* relic_decoder */ +relic_codec_data* init_relic(int channels, int bitrate, int codec_rate); +void decode_relic(VGMSTREAMCHANNEL* stream, relic_codec_data* data, sample_t* outbuf, int32_t samples_to_do); +void reset_relic(relic_codec_data* data); +void seek_relic(relic_codec_data* data, int32_t num_sample); +void free_relic(relic_codec_data* data); + /* hca_decoder */ hca_codec_data *init_hca(STREAMFILE *streamFile); -void decode_hca(hca_codec_data * data, sample * outbuf, int32_t samples_to_do); +void decode_hca(hca_codec_data * data, sample_t * outbuf, int32_t samples_to_do); void reset_hca(hca_codec_data * data); void loop_hca(hca_codec_data * data, int32_t num_sample); void free_hca(hca_codec_data * data); diff --git a/src/coding/relic_decoder.c b/src/coding/relic_decoder.c new file mode 100644 index 00000000..d10b421c --- /dev/null +++ b/src/coding/relic_decoder.c @@ -0,0 +1,500 @@ +#include "coding.h" + +/* Relic Codec decoder, a fairly simple mono-interleave DCT-based codec. + * + * Decompiled from Relic's dec.exe with some info from Homeworld source code .h/lib + * files (released around 2003 through Relic Dev Network), accurate with minor +-1 + * samples due to double<>float ops or maybe original compiler (Intel's) diffs. + * + * TODO: clean API, improve validations (can segfault on bad data) and naming + */ + +/* mixfft.c */ +extern void fft(int n, float *xRe, float *xIm, float *yRe, float *yIm); + +static relic_codec_data* init_codec(int channels, int bitrate, int codec_rate); +static int decode_frame_next(VGMSTREAMCHANNEL* stream, relic_codec_data* data); +static void copy_samples(relic_codec_data* data, sample_t* outbuf, int32_t samples_to_get); +static void reset_codec(relic_codec_data* data); + +#define RELIC_MAX_CHANNELS 2 +#define RELIC_MAX_SCALES 6 +#define RELIC_BASE_SCALE 10.0f +#define RELIC_FREQUENCY_MASKING_FACTOR 1.0f +#define RELIC_CRITICAL_BAND_COUNT 27 +#define RELIC_PI 3.14159265358979323846f +#define RELIC_SIZE_LOW 128 +#define RELIC_SIZE_MID 256 +#define RELIC_SIZE_HIGH 512 +#define RELIC_MAX_SIZE RELIC_SIZE_HIGH +#define RELIC_MAX_FREQ (RELIC_MAX_SIZE / 2) +#define RELIC_MAX_FFT (RELIC_MAX_SIZE / 4) +#define RELIC_BITRATE_22 256 +#define RELIC_BITRATE_44 512 +#define RELIC_BITRATE_88 1024 +#define RELIC_BITRATE_176 2048 +#define RELIC_MAX_FRAME_SIZE ((RELIC_BITRATE_176 / 8) + 0x04) /* extra 0x04 for the bitreader */ + + +struct relic_codec_data { + /* decoder info */ + int channels; + int frame_size; + int wave_size; + int freq_size; + int dct_mode; + int samples_mode; + /* decoder init state */ + float scales[RELIC_MAX_SCALES]; /* quantization scales */ + float dct[RELIC_MAX_SIZE]; + float window[RELIC_MAX_SIZE]; + /* decoder frame state */ + uint8_t exponents[RELIC_MAX_CHANNELS][RELIC_MAX_FREQ]; /* quantization/scale indexes */ + float freq1[RELIC_MAX_FREQ]; /* dequantized spectrum */ + float freq2[RELIC_MAX_FREQ]; + float wave_cur[RELIC_MAX_CHANNELS][RELIC_MAX_SIZE]; /* current frame samples */ + float wave_prv[RELIC_MAX_CHANNELS][RELIC_MAX_SIZE]; /* previous frame samples */ + + /* sample state */ + int32_t samples_discard; + int32_t samples_consumed; + int32_t samples_filled; +}; + +/* ************************************* */ + + +relic_codec_data* init_relic(int channels, int bitrate, int codec_rate) { + return init_codec(channels, bitrate, codec_rate); +} + +void decode_relic(VGMSTREAMCHANNEL* stream, relic_codec_data* data, sample_t* outbuf, int32_t samples_to_do) { + + while (samples_to_do > 0) { + + if (data->samples_consumed < data->samples_filled) { + /* consume samples */ + int samples_to_get = (data->samples_filled - data->samples_consumed); + + if (data->samples_discard) { + /* discard samples for looping */ + if (samples_to_get > data->samples_discard) + samples_to_get = data->samples_discard; + data->samples_discard -= samples_to_get; + } + else { + /* get max samples and copy */ + if (samples_to_get > samples_to_do) + samples_to_get = samples_to_do; + + copy_samples(data, outbuf, samples_to_get); + + samples_to_do -= samples_to_get; + outbuf += samples_to_get * data->channels; + } + + /* mark consumed samples */ + data->samples_consumed += samples_to_get; + } + else { + int ok = decode_frame_next(stream, data); + if (!ok) goto decode_fail; + } + } + return; + +decode_fail: + /* on error just put some 0 samples */ + VGM_LOG("RELIC: decode fail, missing %i samples\n", samples_to_do); + memset(outbuf, 0, samples_to_do * data->channels * sizeof(sample)); +} + +void reset_relic(relic_codec_data* data) { + if (!data) return; + + reset_codec(data); + data->samples_filled = 0; + data->samples_consumed = 0; + data->samples_discard = 0; +} + +void seek_relic(relic_codec_data* data, int32_t num_sample) { + if (!data) return; + + reset_relic(data); + data->samples_discard = num_sample; +} + +void free_relic(relic_codec_data* data) { + if (!data) return; + + free(data); +} + +/* ***************************************** */ + +static const int16_t critical_band_data[RELIC_CRITICAL_BAND_COUNT] = { + 0, 1, 2, 3, 4, 5, 6, 7, + 9, 11, 13, 15, 17, 20, 23, 27, + 31, 37, 43, 51, 62, 74, 89, 110, + 139, 180, 256 +}; + +static void init_dct(float *dct, int dct_size) { + int i; + int dct_quarter = dct_size >> 2; + + for (i = 0; i < dct_quarter; i++) { + double temp = ((float)i + 0.125f) * (RELIC_PI * 2.0f) * (1.0f / (float)dct_size); + dct[i] = sin(temp); + dct[dct_quarter + i] = cos(temp); + } +} + +static int apply_idct(const float *freq, float *wave, const float *dct, int dct_size) { + int i; + float factor; + float out_re[RELIC_MAX_FFT]; + float out_im[RELIC_MAX_FFT]; + float in_re[RELIC_MAX_FFT]; + float in_im[RELIC_MAX_FFT]; + float wave_tmp[RELIC_MAX_SIZE]; + int dct_half = dct_size >> 1; + int dct_quarter = dct_size >> 2; + int dct_3quarter = 3 * (dct_size >> 2); + + /* prerotation? */ + for (i = 0; i < dct_quarter; i++) { + float coef1 = freq[2 * i] * 0.5f; + float coef2 = freq[dct_half - 1 - 2 * i] * 0.5f; + in_re[i] = coef1 * dct[dct_quarter + i] + coef2 * dct[i]; + in_im[i] = -coef1 * dct[i] + coef2 * dct[dct_quarter + i]; + } + + /* main FFT */ + fft(dct_quarter, in_re, in_im, out_re, out_im); + + /* postrotation, window and reorder? */ + factor = 8.0 / sqrt(dct_size); + for (i = 0; i < dct_quarter; i++) { + float out_re_i = out_re[i]; + out_re[i] = (out_re[i] * dct[dct_quarter + i] + out_im[i] * dct[i]) * factor; + out_im[i] = (-out_re_i * dct[i] + out_im[i] * dct[dct_quarter + i]) * factor; + wave_tmp[i * 2] = out_re[i]; + wave_tmp[i * 2 + dct_half] = out_im[i]; + } + for (i = 1; i < dct_size; i += 2) { + wave_tmp[i] = -wave_tmp[dct_size - 1 - i]; + } + + /* wave mix thing? */ + for (i = 0; i < dct_3quarter; i++) { + wave[i] = wave_tmp[dct_quarter + i]; + } + for (i = dct_3quarter; i < dct_size; i++) { + wave[i] = -wave_tmp[i - dct_3quarter]; + } + return 0; +} + +static void decode_frame(const float *freq1, const float *freq2, float *wave_cur, float *wave_prv, const float *dct, const float *window, int dct_size) { + int i; + float wave_tmp[RELIC_MAX_SIZE]; + int dct_half = dct_size >> 1; + + /* copy for first half(?) */ + memcpy(wave_cur, wave_prv, RELIC_MAX_SIZE * sizeof(float)); + + /* transform frequency domain to time domain with DCT/FFT */ + apply_idct(freq1, wave_tmp, dct, dct_size); + apply_idct(freq2, wave_prv, dct, dct_size); + + /* overlap and apply window function to filter this block's beginning */ + for (i = 0; i < dct_half; i++) { + wave_cur[dct_half + i] = wave_tmp[i] * window[i] + wave_cur[dct_half + i] * window[dct_half + i]; + wave_prv[i] = wave_prv[i] * window[i] + wave_tmp[dct_half + i] * window[dct_half + i]; + } +} + +static void init_window(float *window, int dct_size) { + int i; + + for (i = 0; i < dct_size; i++) { + window[i] = sin((float)i * (RELIC_PI / dct_size)); + } +} + +static void decode_frame_base(const float *freq1, const float *freq2, float *wave_cur, float *wave_prv, const float *dct, const float *window, int dct_mode, int samples_mode) { + int i; + float wave_tmp[RELIC_MAX_SIZE]; + + /* dec_relic only uses 512/512 mode, source references 256/256 (effects only?) too */ + + if (samples_mode == RELIC_SIZE_LOW) { + { + /* 128 DCT to 128 samples */ + decode_frame(freq1, freq2, wave_cur, wave_prv, dct, window, RELIC_SIZE_LOW); + } + } + else if (samples_mode == RELIC_SIZE_MID) { + if (dct_mode == RELIC_SIZE_LOW) { + /* 128 DCT to 256 samples (repeat sample x2) */ + decode_frame(freq1, freq2, wave_tmp, wave_prv, dct, window, RELIC_SIZE_LOW); + for (i = 0; i < 256 - 1; i += 2) { + wave_cur[i + 0] = wave_tmp[i >> 1]; + wave_cur[i + 1] = wave_tmp[i >> 1]; + } + } + else { + /* 256 DCT to 256 samples */ + decode_frame(freq1, freq2, wave_cur, wave_prv, dct, window, RELIC_SIZE_MID); + } + } + else if (samples_mode == RELIC_SIZE_HIGH) { + if (dct_mode == RELIC_SIZE_LOW) { + /* 128 DCT to 512 samples (repeat sample x4) */ + decode_frame(freq1, freq2, wave_tmp, wave_prv, dct, window, RELIC_SIZE_LOW); + for (i = 0; i < 512 - 1; i += 4) { + wave_cur[i + 0] = wave_tmp[i >> 2]; + wave_cur[i + 1] = wave_tmp[i >> 2]; + wave_cur[i + 2] = wave_tmp[i >> 2]; + wave_cur[i + 3] = wave_tmp[i >> 2]; + } + } + else if (dct_mode == RELIC_SIZE_MID) { + /* 256 DCT to 512 samples (repeat sample x2) */ + decode_frame(freq1, freq2, wave_tmp, wave_prv, dct, window, RELIC_SIZE_MID); + for (i = 0; i < 512 - 1; i += 2) { + wave_cur[i + 0] = wave_tmp[i >> 1]; + wave_cur[i + 1] = wave_tmp[i >> 1]; + } + } + else { + /* 512 DCT to 512 samples */ + decode_frame(freq1, freq2, wave_cur, wave_prv, dct, window, RELIC_SIZE_HIGH); + } + } +} + + +/* reads 32b max, packed in LSB order per byte (like Vorbis), ex. + * with 0x45 6A=01000101 01101010 could read 4b=0101, 6b=100100, 3b=010 ... + * assumes buf has enough extra bits to read 32b (size +0x04) */ +static uint32_t read_ubits(uint8_t bits, uint32_t offset, uint8_t *buf) { + uint32_t shift, mask, pos, val; + + shift = offset - 8 * (offset / 8); + mask = (1 << bits) - 1; + pos = offset / 8; + val = (buf[pos+0]) | (buf[pos+1]<<8) | (buf[pos+2]<<16) | (buf[pos+3]<<24); + return (val >> shift) & mask; +} +static int read_sbits(uint8_t bits, uint32_t offset, uint8_t *buf) { + uint32_t val = read_ubits(bits, offset, buf); + if (val >> (bits - 1) == 1) { /* upper bit = sign */ + uint32_t mask = (1 << (bits - 1)) - 1; + return -(val & mask); + } + return val; +} + +static void init_dequantization(float* scales) { + int i; + + scales[0] = RELIC_BASE_SCALE; + for (i = 1; i < RELIC_MAX_SCALES; i++) { + scales[i] = scales[i - 1] * scales[0]; + } + for (i = 0; i < RELIC_MAX_SCALES; i++) { + scales[i] = RELIC_FREQUENCY_MASKING_FACTOR / (double) ((1 << (i + 1)) - 1) * scales[i]; + } +} + +static void unpack_frame(uint8_t *buf, int buf_size, float *freq1, float *freq2, const float* scales, uint8_t *exponents, int freq_size) { + uint8_t flags, cb_bits, ev_bits, ei_bits, qv_bits; + int qv; + uint8_t ev; + uint8_t move, pos; + uint32_t bit_offset; + int i, j; + int freq_half = freq_size >> 1; + + + memset(freq1, 0, RELIC_MAX_FREQ * sizeof(float)); + memset(freq2, 0, RELIC_MAX_FREQ * sizeof(float)); + + flags = read_ubits(2u, 0u, buf); + cb_bits = read_ubits(3u, 2u, buf); + ev_bits = read_ubits(2u, 5u, buf); + ei_bits = read_ubits(4u, 7u, buf); + bit_offset = 11; + + /* reset exponents indexes */ + if ((flags & 1) == 1) { + memset(exponents, 0, RELIC_MAX_FREQ); + } + + /* read packed exponents indexes for all bands */ + if (cb_bits > 0 && ev_bits > 0) { + pos = 0; + for (i = 0; i < RELIC_CRITICAL_BAND_COUNT - 1; i++) { + if (bit_offset >= 8*buf_size) + break; + + move = read_ubits(cb_bits, bit_offset, buf); + bit_offset += cb_bits; + if (i > 0 && move == 0) + break; + pos += move; + + ev = read_ubits(ev_bits, bit_offset, buf); + bit_offset += ev_bits; + for (j = critical_band_data[pos]; j < critical_band_data[pos + 1]; j++) + exponents[j] = ev; + } + } + + /* read quantized values */ + if (freq_half > 0 && ei_bits > 0) { + + /* read first part */ + pos = 0; + for (i = 0; i < RELIC_MAX_FREQ; i++) { + if (bit_offset >= 8*buf_size) + break; + + move = read_ubits(ei_bits, bit_offset, buf); + bit_offset += ei_bits; + if (i > 0 && move == 0) + break; + pos += move; + + qv_bits = exponents[pos]; + qv = read_sbits(qv_bits + 2u, bit_offset, buf); + bit_offset += qv_bits + 2u; + + if (qv != 0 && pos < freq_half && qv_bits < 6) + freq1[pos] = (float)qv * scales[qv_bits]; + } + + /* read second part, or clone it */ + if ((flags & 2) == 2) { + memcpy(freq2, freq1, RELIC_MAX_FREQ * sizeof(float)); + } + else { + pos = 0; + for (i = 0; i < RELIC_MAX_FREQ; i++) { + if (bit_offset >= 8*buf_size) + break; + + move = read_ubits(ei_bits, bit_offset, buf); + bit_offset += ei_bits; + if (i > 0 && move == 0) + break; + pos += move; + + qv_bits = exponents[pos]; + qv = read_sbits(qv_bits + 2u, bit_offset, buf); + bit_offset += qv_bits + 2u; + + if (qv != 0 && pos < freq_half && qv_bits < 6) + freq2[pos] = (float)qv * scales[qv_bits]; + } + } + } +} + +static int decode_frame_next(VGMSTREAMCHANNEL* stream, relic_codec_data* data) { + int ch; + int bytes; + uint8_t buf[RELIC_MAX_FRAME_SIZE]; + + for (ch = 0; ch < data->channels; ch++) { + /* clean extra bytes for bitreader */ + memset(buf + data->frame_size, 0, RELIC_MAX_FRAME_SIZE - data->frame_size); + + bytes = read_streamfile(buf, stream->offset, data->frame_size, stream->streamfile); + if (bytes != data->frame_size) goto fail; + stream->offset += data->frame_size; + + unpack_frame(buf, sizeof(buf), data->freq1, data->freq2, data->scales, data->exponents[ch], data->freq_size); + + decode_frame_base(data->freq1, data->freq2, data->wave_cur[ch], data->wave_prv[ch], data->dct, data->window, data->dct_mode, data->samples_mode); + } + + data->samples_consumed = 0; + data->samples_filled = data->wave_size; + return 1; +fail: + return 0; +} + +static void copy_samples(relic_codec_data* data, sample_t* outbuf, int32_t samples) { + int s, ch; + int ichs = data->channels; + int skip = data->samples_consumed; + for (ch = 0; ch < ichs; ch++) { + for (s = 0; s < samples; s++) { + double d64_sample = data->wave_cur[ch][skip + s]; + int pcm_sample = clamp16(d64_sample); + + /* f32 in PCM 32767.0 .. -32768.0 format, original code + * does some custom double-to-int rint() though */ + //FQ_BNUM ((float)(1<<26)*(1<<26)*1.5) + //rint(x) ((d64 = (double)(x)+FQ_BNUM), *(int*)(&d64)) + + outbuf[s*ichs + ch] = pcm_sample; + } + } +} + +static relic_codec_data* init_codec(int channels, int bitrate, int codec_rate) { + relic_codec_data *data = NULL; + + if (channels > RELIC_MAX_CHANNELS) + goto fail; + + data = calloc(1, sizeof(relic_codec_data)); + if (!data) goto fail; + + data->channels = channels; + + /* dequantized freq1+2 size (separate from DCT) */ + if (codec_rate < 22050) /* probably 11025 only */ + data->freq_size = RELIC_SIZE_LOW; + if (codec_rate == 22050) + data->freq_size = RELIC_SIZE_MID; + if (codec_rate > 22050) /* probably 44100 only */ + data->freq_size = RELIC_SIZE_HIGH; + + /* default for streams (only a few mode combos are valid, see decode) */ + data->wave_size = RELIC_SIZE_HIGH; + data->dct_mode = RELIC_SIZE_HIGH; + data->samples_mode = RELIC_SIZE_HIGH; + + init_dct(data->dct, RELIC_SIZE_HIGH); + init_window(data->window, RELIC_SIZE_HIGH); + init_dequantization(data->scales); + memset(data->wave_prv, 0, RELIC_MAX_CHANNELS * RELIC_MAX_SIZE * sizeof(float)); + + switch(bitrate) { + case RELIC_BITRATE_22: + case RELIC_BITRATE_44: + case RELIC_BITRATE_88: + case RELIC_BITRATE_176: + data->frame_size = (bitrate / 8); /* 0x100 and 0x80 are common */ + break; + default: + goto fail; + } + + + return data; +fail: + free_relic(data); + return NULL; +} + +static void reset_codec(relic_codec_data* data) { + memset(data->wave_prv, 0, RELIC_MAX_CHANNELS * RELIC_MAX_SIZE * sizeof(float)); +} diff --git a/src/coding/relic_decoder_mixfft.c b/src/coding/relic_decoder_mixfft.c new file mode 100644 index 00000000..0e977e73 --- /dev/null +++ b/src/coding/relic_decoder_mixfft.c @@ -0,0 +1,618 @@ +/* Original Relic code uses mixfft.c v1 by Jens Jorgen Nielsen, though was + * modified to use floats instead of doubles. This is a 100% decompilation + * that somehow resulted in the exact same code (no compiler optims set?), + * so restores comments back but removes/cleans globals (could be simplified). */ + +#include +#include +#include +#include + +/* ------------------------------------------------------------------------- */ + +/************************************************************************ + fft(int n, double xRe[], double xIm[], double yRe[], double yIm[]) + ------------------------------------------------------------------------ + NOTE : This is copyrighted material, Not public domain. See below. + ------------------------------------------------------------------------ + Input/output: + int n transformation length. + double xRe[] real part of input sequence. + double xIm[] imaginary part of input sequence. + double yRe[] real part of output sequence. + double yIm[] imaginary part of output sequence. + ------------------------------------------------------------------------ + Function: + The procedure performs a fast discrete Fourier transform (FFT) of + a complex sequence, x, of an arbitrary length, n. The output, y, + is also a complex sequence of length n. + + y[k] = sum(x[m]*exp(-i*2*pi*k*m/n), m=0..(n-1)), k=0,...,(n-1) + + The largest prime factor of n must be less than or equal to the + constant maxPrimeFactor defined below. + ------------------------------------------------------------------------ + Author: + Jens Joergen Nielsen For non-commercial use only. + Bakkehusene 54 A $100 fee must be paid if used + DK-2970 Hoersholm commercially. Please contact. + DENMARK + + E-mail : jjn@get2net.dk All rights reserved. October 2000. + Homepage : http://home.get2net.dk/jjn + ------------------------------------------------------------------------ + Implementation notes: + The general idea is to factor the length of the DFT, n, into + factors that are efficiently handled by the routines. + + A number of short DFT's are implemented with a minimum of + arithmetical operations and using (almost) straight line code + resulting in very fast execution when the factors of n belong + to this set. Especially radix-10 is optimized. + + Prime factors, that are not in the set of short DFT's are handled + with direct evaluation of the DFP expression. + + Please report any problems to the author. + Suggestions and improvements are welcomed. + ------------------------------------------------------------------------ + Benchmarks: + The Microsoft Visual C++ compiler was used with the following + compile options: + /nologo /Gs /G2 /W4 /AH /Ox /D "NDEBUG" /D "_DOS" /FR + and the FFTBENCH test executed on a 50MHz 486DX : + + Length Time [s] Accuracy [dB] + + 128 0.0054 -314.8 + 256 0.0116 -309.8 + 512 0.0251 -290.8 + 1024 0.0567 -313.6 + 2048 0.1203 -306.4 + 4096 0.2600 -291.8 + 8192 0.5800 -305.1 + 100 0.0040 -278.5 + 200 0.0099 -280.3 + 500 0.0256 -278.5 + 1000 0.0540 -278.5 + 2000 0.1294 -280.6 + 5000 0.3300 -278.4 + 10000 0.7133 -278.5 + ------------------------------------------------------------------------ + The following procedures are used : + factorize : factor the transformation length. + transTableSetup : setup table with sofar-, actual-, and remainRadix. + permute : permutation allows in-place calculations. + twiddleTransf : twiddle multiplications and DFT's for one stage. + initTrig : initialise sine/cosine table. + fft_4 : length 4 DFT, a la Nussbaumer. + fft_5 : length 5 DFT, a la Nussbaumer. + fft_10 : length 10 DFT using prime factor FFT. + fft_odd : length n DFT, n odd. +*************************************************************************/ + +#define maxPrimeFactor 37 +#define maxPrimeFactorDiv2 ((maxPrimeFactor+1)/2) +#define maxFactorCount 20 + +static const float c3_1 = -1.5f; /* c3_1 = cos(2*pi/3)-1; */ +static const float c3_2 = 0.866025388240814208984375f; /* c3_2 = sin(2*pi/3); */ + +// static const float u5 = 1.256637096405029296875f; /* u5 = 2*pi/5; */ +static const float c5_1 = -1.25f; /* c5_1 = (cos(u5)+cos(2*u5))/2-1;*/ +static const float c5_2 = 0.559017002582550048828125f; /* c5_2 = (cos(u5)-cos(2*u5))/2; */ +static const float c5_3 = -0.951056540012359619140625f; /* c5_3 = -sin(u5); */ +static const float c5_4 = -1.538841724395751953125f; /* c5_4 = -(sin(u5)+sin(2*u5)); */ +static const float c5_5 = 0.3632712662220001220703125f; /* c5_5 = (sin(u5)-sin(2*u5)); */ +static const float c8 = 0.707106769084930419921875f; /* c8 = 1/sqrt(2); */ + +static const float pi = 3.1415927410125732421875f; + +#if 0 /* extra */ +static int groupOffset,dataOffset,adr; //,blockOffset +static int groupNo,dataNo,blockNo,twNo; +static float omega, tw_re,tw_im; +static float twiddleRe[maxPrimeFactor], twiddleIm[maxPrimeFactor], + trigRe[maxPrimeFactor], trigIm[maxPrimeFactor], + zRe[maxPrimeFactor], zIm[maxPrimeFactor]; +static float vRe[maxPrimeFactorDiv2], vIm[maxPrimeFactorDiv2]; +static float wRe[maxPrimeFactorDiv2], wIm[maxPrimeFactorDiv2]; +#endif + + +static void factorize(int n, int *nFact, int *fact) +{ + int i,j,k; + int nRadix; + int radices[7]; + int factors[maxFactorCount]; + + nRadix = 6; + radices[1]= 2; + radices[2]= 3; + radices[3]= 4; + radices[4]= 5; + radices[5]= 8; + radices[6]= 10; + + radices[0]= 1; /* extra (assumed) */ + factors[0]= 0; /* extra (assumed) */ + fact[0]= 0; /* extra (assumed) */ + fact[1]= 0; /* extra (assumed) */ + + if (n==1) + { + j=1; + factors[1]=1; + } + else j=0; + i=nRadix; + while ((n>1) && (i>0)) + { + if ((n % radices[i]) == 0) + { + n=n / radices[i]; + j=j+1; + factors[j]=radices[i]; + } + else i=i-1; + } + if (factors[j] == 2) /*substitute factors 2*8 with 4*4 */ + { + i = j-1; + while ((i>0) && (factors[i] != 8)) i--; + if (i>0) + { + factors[j] = 4; + factors[i] = 4; + } + } + if (n>1) + { + for (k=2; k1) + { + j=j+1; + factors[j]=n; + } + } + for (i=1; i<=j; i++) + { + fact[i] = factors[j-i+1]; + } + *nFact=j; +} /* factorize */ + +/**************************************************************************** + After N is factored the parameters that control the stages are generated. + For each stage we have: + sofar : the product of the radices so far. + actual : the radix handled in this stage. + remain : the product of the remaining radices. + ****************************************************************************/ + +static void transTableSetup(int *sofar, int *actual, int *remain, + int *nFact, + int *nPoints) +{ + int i; + + factorize(*nPoints, nFact, actual); + if (actual[*nFact] > maxPrimeFactor) + { +#if 0 /* extra */ + printf("\nPrime factor of FFT length too large : %6d", actual[*nFact]); + exit(1); +#endif + actual[*nFact] = maxPrimeFactor - 1; /* extra */ + } + remain[0]=*nPoints; + sofar[1]=1; + remain[1]=*nPoints / actual[1]; + for (i=2; i<=*nFact; i++) + { + sofar[i]=sofar[i-1]*actual[i-1]; + remain[i]=remain[i-1] / actual[i]; + } +} /* transTableSetup */ + +/**************************************************************************** + The sequence y is the permuted input sequence x so that the following + transformations can be performed in-place, and the final result is the + normal order. + ****************************************************************************/ + +static void permute(int nPoint, int nFact, + int *fact, int *remain, + float *xRe, float *xIm, + float *yRe, float *yIm) + +{ + int i,j,k; + int count[maxFactorCount]; + + for (i=1; i<=nFact; i++) count[i]=0; + k=0; + for (i=0; i<=nPoint-2; i++) + { + yRe[i] = xRe[k]; + yIm[i] = xIm[k]; + j=1; + k=k+remain[j]; + count[1] = count[1]+1; + while (count[j] >= fact[j]) + { + count[j]=0; + k=k-remain[j-1]+remain[j+1]; + j=j+1; + count[j]=count[j]+1; + } + } + yRe[nPoint-1]=xRe[nPoint-1]; + yIm[nPoint-1]=xIm[nPoint-1]; +} /* permute */ + + +/**************************************************************************** + Twiddle factor multiplications and transformations are performed on a + group of data. The number of multiplications with 1 are reduced by skipping + the twiddle multiplication of the first stage and of the first group of the + following stages. + ***************************************************************************/ + +static void initTrig(int radix, float *trigRe, float*trigIm) +{ + int i; + float w,xre,xim; + + w=2*pi/radix; + trigRe[0]=1; trigIm[0]=0; + xre=cos(w); + xim=-sin(w); + trigRe[1]=xre; trigIm[1]=xim; + for (i=2; i= n) k = k - n; + } + } + for (j=1; j < max; j++) + { + zRe[0]=zRe[0] + vRe[j]; + zIm[0]=zIm[0] + wIm[j]; + } +} /* fft_odd */ + + +static void twiddleTransf(int sofarRadix, int radix, int remainRadix, + float *yRe, float *yIm) + +{ /* twiddleTransf */ + float cosw, sinw, gem; + float t1_re,t1_im, t2_re,t2_im, t3_re,t3_im; + float t4_re,t4_im, t5_re,t5_im; + float m1_re,m1_im, m2_re,m2_im, m3_re,m3_im; + float m4_re,m4_im, m5_re,m5_im; + float s1_re,s1_im, s2_re,s2_im, s3_re,s3_im; + float s4_re,s4_im, s5_re,s5_im; + int groupOffset,dataOffset,adr; //,blockOffset /* extra */ + int groupNo,dataNo,blockNo,twNo; /* extra */ + float omega, tw_re,tw_im; /* extra */ + float twiddleRe[maxPrimeFactor] = {0}, twiddleIm[maxPrimeFactor] = {0}, /* extra */ + trigRe[maxPrimeFactor] = {0}, trigIm[maxPrimeFactor] = {0}, /* extra */ + zRe[maxPrimeFactor] = {0}, zIm[maxPrimeFactor] = {0}; /* extra */ + + + initTrig(radix, trigRe, trigIm); + omega = 2*pi/(double)(sofarRadix*radix); + cosw = cos(omega); + sinw = -sin(omega); + tw_re = 1.0; + tw_im = 0; + dataOffset=0; + groupOffset=dataOffset; + adr=groupOffset; + for (dataNo=0; dataNo1) + { + twiddleRe[0] = 1.0; + twiddleIm[0] = 0.0; + twiddleRe[1] = tw_re; + twiddleIm[1] = tw_im; + for (twNo=2; twNo1) && (dataNo > 0)) + { + zRe[0]=yRe[adr]; + zIm[0]=yIm[adr]; + blockNo=1; + do { + adr = adr + sofarRadix; + zRe[blockNo]= twiddleRe[blockNo] * yRe[adr] + - twiddleIm[blockNo] * yIm[adr]; + zIm[blockNo]= twiddleRe[blockNo] * yIm[adr] + + twiddleIm[blockNo] * yRe[adr]; + + blockNo++; + } while (blockNo < radix); + } + else { + for (blockNo=0; blockNochannels; break; + case 0x53484F43: /* "SHOC" (a generic block but hopefully has PC sounds) */ - header_size = 0x14; //todo the first block is 0x18 - channel_size = (block_size - header_size) / vgmstream->channels; + if (read_32bit(block_offset+0x10, streamFile) == 0x53444154) { /* "SDAT" */ + header_size = 0x14; + channel_size = (block_size - header_size) / vgmstream->channels; + } + else { + header_size = 0; + channel_size = 0; + } break; - case 0x46494C4C: /* "FILL" (FILLs do that up to 0x6000, but at 0x5FFC don't actually have size) */ + case 0x46494C4C: /* "FILL" (FILLs do that up to a block size, but near end don't actually have size) */ if ((block_offset + 0x04) % 0x6000 == 0) block_size = 0x04; + else if ((block_offset + 0x04) % 0x10000 == 0) + block_size = 0x04; + else if (block_size > 0x100000) { /* other unknown block sizes */ + VGM_LOG("EA SWVR: bad block size at 0x%lx\n", block_offset); + block_size = 0x04; + } header_size = 0x08; break; - + case 0xFFFFFFFF: channel_size = -1; /* signal bad block */ break; diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 6af1b153..ae53f10d 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -1422,6 +1422,10 @@ RelativePath=".\meta\fag.c" > + + @@ -2073,6 +2077,14 @@ + + + + + @@ -570,6 +571,8 @@ + + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index 4e827b52..508b1a82 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -877,6 +877,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files @@ -1237,6 +1240,12 @@ coding\Source Files + + coding\Source Files + + + coding\Source Files + coding\Source Files diff --git a/src/meta/aifc.c b/src/meta/aifc.c index d9b2a46a..58546894 100644 --- a/src/meta/aifc.c +++ b/src/meta/aifc.c @@ -1,15 +1,17 @@ #include "meta.h" #include "../layout/layout.h" +#include "../coding/coding.h" /* for reading integers inexplicably packed into 80-bit ('double extended') floats */ static uint32_t read80bitSANE(off_t offset, STREAMFILE *streamFile) { - uint8_t buf[10]; + uint8_t buf[0x0a]; int32_t exponent; int32_t mantissa; int i; - if (read_streamfile(buf,offset,10,streamFile) != 10) return 0; + if (read_streamfile(buf,offset,0x0a,streamFile) != 0x0a) + return 0; exponent = ((buf[0]<<8)|(buf[1]))&0x7fff; exponent -= 16383; @@ -26,13 +28,13 @@ static uint32_t read80bitSANE(off_t offset, STREAMFILE *streamFile) { return mantissa*((buf[0]&0x80)?-1:1); } -static uint32_t find_marker(STREAMFILE *streamFile, off_t MarkerChunkOffset, int marker_id) { +static uint32_t find_marker(STREAMFILE *streamFile, off_t mark_offset, int marker_id) { uint16_t marker_count; int i; off_t marker_offset; - marker_count = read_16bitBE(MarkerChunkOffset+8,streamFile); - marker_offset = MarkerChunkOffset+10; + marker_count = read_16bitBE(mark_offset+8,streamFile); + marker_offset = mark_offset+10; for (i=0;i= sizeof(comp_name) - 1) goto fail; + + read_streamfile(comp_name, current_chunk + 0x1f, comp_size, streamFile); + if (memcmp(comp_name, "Relic Codec v1.6", comp_size) == 0) { /* Homeworld 2 (PC) */ + coding_type = coding_RELIC; + sample_count = sample_count * 512; + } + else { + goto fail; + } + break; + } + default: VGM_LOG("AIFC: unknown codec\n"); goto fail; @@ -192,25 +216,25 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) { case 0x53534E44: /* "SSND" (main data) */ case 0x4150434D: /* "APCM" (main data for XA) */ - if (SoundDataChunkFound) goto fail; - SoundDataChunkFound = 1; + if (data_found) goto fail; + data_found = 1; start_offset = current_chunk + 0x10 + read_32bitBE(current_chunk+0x08,streamFile); /* when "APCM" XA frame size is at 0x0c, fixed to 0x914 */ break; case 0x4D41524B: /* "MARK" (loops) */ - if (MarkerChunkFound) goto fail; - MarkerChunkFound = 1; + if (mark_found) goto fail; + mark_found = 1; - MarkerChunkOffset = current_chunk; + mark_offset = current_chunk; break; case 0x494E5354: /* "INST" (loops) */ - if (InstrumentChunkFound) goto fail; - InstrumentChunkFound = 1; + if (inst_found) goto fail; + inst_found = 1; - InstrumentChunkOffset = current_chunk; + inst_offset = current_chunk; break; default: @@ -223,28 +247,28 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) { } if (is_aifc) { - if (!FormatVersionChunkFound || !CommonChunkFound || !SoundDataChunkFound) + if (!fver_found || !comm_found || !data_found) goto fail; } else if (is_aiff) { - if (!CommonChunkFound || !SoundDataChunkFound) + if (!comm_found || !data_found) goto fail; } /* read loop points */ - if (InstrumentChunkFound && MarkerChunkFound) { + if (inst_found && mark_found) { int start_marker; int end_marker; /* use the sustain loop */ /* if playMode=ForwardLooping */ - if (read_16bitBE(InstrumentChunkOffset+16,streamFile) == 1) { - start_marker = read_16bitBE(InstrumentChunkOffset+18,streamFile); - end_marker = read_16bitBE(InstrumentChunkOffset+20,streamFile); + if (read_16bitBE(inst_offset+16,streamFile) == 1) { + start_marker = read_16bitBE(inst_offset+18,streamFile); + end_marker = read_16bitBE(inst_offset+20,streamFile); /* check for sustain markers != 0 (invalid marker no) */ if (start_marker && end_marker) { /* find start marker */ - loop_start = find_marker(streamFile,MarkerChunkOffset,start_marker); - loop_end = find_marker(streamFile,MarkerChunkOffset,end_marker); + loop_start = find_marker(streamFile,mark_offset,start_marker); + loop_end = find_marker(streamFile,mark_offset,end_marker); /* find_marker is type uint32_t as the spec says that's the type * of the position value, but it returns a -1 on error, and the @@ -257,6 +281,8 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) { loop_flag = 0; } } + + /* Relic has "beg loop" "end loop" comments but no actual looping? */ } @@ -270,14 +296,28 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) { vgmstream->loop_end_sample = loop_end; vgmstream->coding_type = coding_type; - if (coding_type == coding_XA) { - vgmstream->layout_type = layout_blocked_xa_aiff; - /* AIFF XA can use sample rates other than 37800/18900 */ - /* some Crusader: No Remorse tracks have XA headers with incorrect 0xFF, rip bug/encoder feature? */ - } - else { - vgmstream->layout_type = (channel_count > 1) ? layout_interleave : layout_none; - vgmstream->interleave_block_size = interleave; + switch(coding_type) { + case coding_XA: + vgmstream->layout_type = layout_blocked_xa_aiff; + /* AIFF XA can use sample rates other than 37800/18900 */ + /* some Crusader: No Remorse tracks have XA headers with incorrect 0xFF, rip bug/encoder feature? */ + break; + + case coding_RELIC: { + int bitrate = read_16bitBE(start_offset, streamFile); + start_offset += 0x02; + + vgmstream->codec_data = init_relic(channel_count, bitrate, sample_rate); + if (!vgmstream->codec_data) goto fail; + vgmstream->layout_type = layout_none; + + vgmstream->sample_rate = 44100; /* fixed output */ + break; + } + default: + vgmstream->layout_type = (channel_count > 1) ? layout_interleave : layout_none; + vgmstream->interleave_block_size = interleave; + break; } if (is_aifc) diff --git a/src/meta/csb.c b/src/meta/csb.c index bd429694..a795ba9b 100644 --- a/src/meta/csb.c +++ b/src/meta/csb.c @@ -89,7 +89,6 @@ VGMSTREAM * init_vgmstream_csb(STREAMFILE *streamFile) { goto fail; if (!utf_query_s8(utf_sdl, i, "fmt", &fmt)) goto fail; - VGM_LOG("fmt=%i\n", fmt); /* also nch/sfreq/nsmpl info */ found = 1; @@ -98,7 +97,7 @@ VGMSTREAM * init_vgmstream_csb(STREAMFILE *streamFile) { if (!found) goto fail; if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; -VGM_LOG("5: %x, %x\n", sdl_offset, offset); + subfile_offset = /*sdl_offset +*/ offset; subfile_size = size; @@ -107,14 +106,12 @@ VGM_LOG("5: %x, %x\n", sdl_offset, offset); goto fail; } - ;VGM_LOG("CSB: subfile offset=%lx + %x\n", subfile_offset, subfile_size); + //;VGM_LOG("CSB: subfile offset=%lx + %x\n", subfile_offset, subfile_size); temp_sf = setup_subfile_streamfile(streamFile, subfile_offset, subfile_size, "aax"); if (!temp_sf) goto fail; - VGM_LOG("7: %i\n", fmt); - switch(fmt) { case 0: /* AAX */ case 6: /* HCA */ @@ -132,8 +129,6 @@ VGM_LOG("5: %x, %x\n", sdl_offset, offset); goto fail; } - VGM_LOG("8\n"); - vgmstream->num_streams = total_subsongs; strncpy(vgmstream->stream_name, stream_name, STREAM_NAME_SIZE-1); diff --git a/src/meta/ea_eaac.c b/src/meta/ea_eaac.c index 5cdfe46d..faf6a1f8 100644 --- a/src/meta/ea_eaac.c +++ b/src/meta/ea_eaac.c @@ -39,7 +39,7 @@ static VGMSTREAM *parse_s10a_header(STREAMFILE *streamFile, off_t offset, uint16 VGMSTREAM * init_vgmstream_gin_header(STREAMFILE *streamFile, off_t offset); -/* .SNR+SNS - from EA latest games (~2008-2013), v0 header */ +/* .SNR+SNS - from EA latest games (~2005-2010), v0 header */ VGMSTREAM * init_vgmstream_ea_snr_sns(STREAMFILE * streamFile) { /* check extension, case insensitive */ if (!check_extensions(streamFile,"snr")) @@ -51,7 +51,7 @@ fail: return NULL; } -/* .SPS - from EA latest games (~2014), v1 header */ +/* .SPS - from EA latest games (~2010~present), v1 header */ VGMSTREAM * init_vgmstream_ea_sps(STREAMFILE * streamFile) { /* check extension, case insensitive */ if (!check_extensions(streamFile,"sps")) @@ -1556,6 +1556,7 @@ static layered_layout_data* build_layered_eaaudiocore(STREAMFILE *sf_data, eaac_ uint8_t buf[0x100]; int bytes, block_size, block_count; size_t stream_size; + int is_xma1; temp_sf = setup_eaac_audio_streamfile(sf_data, eaac->version, eaac->codec, eaac->streamed,i,layers, start_offset); if (!temp_sf) goto fail; @@ -1564,7 +1565,14 @@ static layered_layout_data* build_layered_eaaudiocore(STREAMFILE *sf_data, eaac_ block_size = 0x10000; /* unused */ block_count = stream_size / block_size + (stream_size % block_size ? 1 : 0); - bytes = ffmpeg_make_riff_xma2(buf, 0x100, data->layers[i]->num_samples, stream_size, data->layers[i]->channels, data->layers[i]->sample_rate, block_count, block_size); + /* EA adopted XMA2 when it appeared around 2006, but detection isn't so easy + * (SNS with XMA2 do exist). Decoder should work when playing XMA1 as XMA2, but + * the other way around can cause issues, so it's safer to just use XMA2. */ + is_xma1 = 0; //eaac->version == EAAC_VERSION_V0; /* approximate */ + if (is_xma1) + bytes = ffmpeg_make_riff_xma1(buf, 0x100, data->layers[i]->num_samples, stream_size, data->layers[i]->channels, data->layers[i]->sample_rate, 0); + else + bytes = ffmpeg_make_riff_xma2(buf, 0x100, data->layers[i]->num_samples, stream_size, data->layers[i]->channels, data->layers[i]->sample_rate, block_count, block_size); data->layers[i]->codec_data = init_ffmpeg_header_offset(temp_sf, buf,bytes, 0x00, stream_size); if (!data->layers[i]->codec_data) goto fail; diff --git a/src/meta/ea_swvr.c b/src/meta/ea_swvr.c index da1f1070..a82e52a3 100644 --- a/src/meta/ea_swvr.c +++ b/src/meta/ea_swvr.c @@ -102,9 +102,14 @@ VGMSTREAM * init_vgmstream_ea_swvr(STREAMFILE *streamFile) { sample_rate = 14008; break; case 0x53484F43: /* "SHOC" (a generic block but hopefully has PC sounds) */ - coding = coding_PCM8_U_int; - channel_count = 1; - sample_rate = 14008; + if (read_32bit(start_offset+0x10, streamFile) == 0x53484F43) { /* SHDR */ + coding = coding_PCM8_U_int; //todo there are other codecs + channel_count = 1; + sample_rate = 14008; + } + else { + goto fail; + } break; default: VGM_LOG("EA SWVR: unknown block id\n"); diff --git a/src/meta/fda.c b/src/meta/fda.c new file mode 100644 index 00000000..57c9a0e1 --- /dev/null +++ b/src/meta/fda.c @@ -0,0 +1,96 @@ +#include "meta.h" +#include "../coding/coding.h" + + +/* FDA - from Relic Entertainment games [Warhammer 4000: Dawn of War (PC)] */ +VGMSTREAM * init_vgmstream_fda(STREAMFILE *sf) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count, bitrate, sample_rate, num_samples; + + + /* checks */ + if (!check_extensions(sf, "fda")) + goto fail; + + if (read_u32be(0x00, sf) != 0x52656C69 || /* "Reli" */ + read_u32be(0x04, sf) != 0x63204368 || /* "c Ch" */ + read_u32be(0x08, sf) != 0x756E6B79 || /* "unky" */ + read_u32be(0x0c, sf) != 0x0D0A1A00) /* "\r\n\1a\00"*/ + goto fail; + + /* version? (later .fda change this) */ + if (read_u32le(0x10, sf) != 1 || + read_u32le(0x14, sf) != 1) + goto fail; + + /* read chunks (abridged) */ + { + off_t offset = 0x18; + size_t chunk_size, name_size, data_size; + + /* Relic Chunkys have: type, subtype, version, data size, name size, then + * name (optional) and data. But format has fixed chunks so we'll simplify a bit. */ + + /* DATA-FBIF (file info) */ + chunk_size = read_u32le(offset + 0x0c, sf); + name_size = read_u32le(offset + 0x10, sf); + offset += 0x14 + name_size + chunk_size; + + /* FOLD-FDA (folder of chunks) */ + if (read_u32be(offset + 0x04, sf) != 0x46444120) /* "FDA " */ + goto fail; + offset += 0x14; + + /* DATA-INFO (header) */ + if (read_u32be(offset + 0x04, sf) != 0x494E464F) /* "INFO" */ + goto fail; + chunk_size = read_u32le(offset + 0x0c, sf); + name_size = read_u32le(offset + 0x10, sf); + offset += 0x14 + name_size; + + channel_count = read_s32le(offset + 0x00, sf); + /* 0x04: bps */ + bitrate = read_s32le(offset + 0x08, sf); + sample_rate = read_s32le(offset + 0x0c, sf); + /* 0x10: loop start? (always 0) */ + /* 0x14: loop end? (always -1) */ + /* 0x18: loop offset? (always 0) */ + loop_flag = 0; /* never set? */ + offset += chunk_size; + + /* DATA-DATA (data) */ + if (read_u32be(offset + 0x04, sf) != 0x44415441) /* "DATA" */ + goto fail; + chunk_size = read_u32le(offset + 0x0c, sf); + name_size = read_u32le(offset + 0x10, sf); + offset += 0x14 + name_size; + + data_size = read_s32le(offset + 0x00, sf); + + start_offset = offset + 0x04; + num_samples = data_size / channel_count / (bitrate / 8) * 512; + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_FDA; + vgmstream->sample_rate = 44100; /* fixed output */ + vgmstream->num_samples = num_samples; + + vgmstream->codec_data = init_relic(channel_count, bitrate, sample_rate); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_RELIC; + vgmstream->layout_type = layout_none; + + if (!vgmstream_open_stream(vgmstream, sf, start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/meta.h b/src/meta/meta.h index e9f5aa75..fcf269e2 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -877,4 +877,6 @@ VGMSTREAM* init_vgmstream_xma_ue3(STREAMFILE *sf); VGMSTREAM* init_vgmstream_csb(STREAMFILE *sf); +VGMSTREAM* init_vgmstream_fda(STREAMFILE *sf); + #endif /*_META_H*/ diff --git a/src/meta/ubi_hx.c b/src/meta/ubi_hx.c index 454f8c85..37446233 100644 --- a/src/meta/ubi_hx.c +++ b/src/meta/ubi_hx.c @@ -1,717 +1,726 @@ -#include "meta.h" -#include "../layout/layout.h" -#include "../coding/coding.h" - - -typedef enum { PCM, UBI, PSX, DSP, XIMA, ATRAC3, XMA2 } ubi_hx_codec; - -typedef struct { - int big_endian; - int total_subsongs; - - int codec_id; - ubi_hx_codec codec; /* unified codec */ - int header_index; /* entry number within section2 */ - off_t header_offset; /* entry offset within internal .HXx */ - size_t header_size; /* entry offset within internal .HXx */ - char class_name[255]; - size_t class_size; - size_t stream_mode; - - off_t stream_offset; /* data offset within external stream */ - size_t stream_size; /* data size within external stream */ - uint32_t cuuid1; /* usually "Res" id1: class (1=Event, 3=Wave), id2: group id+sound id, */ - uint32_t cuuid2; /* others have some complex id (not hash), id1: parent id?, id2: file id? */ - - int loop_flag; - int channels; - int sample_rate; - int num_samples; - - int is_external; - char resource_name[0x28]; /* filename to the external stream */ - char internal_name[255]; /* WavRes's assigned name */ - char readable_name[255]; /* final subsong name */ - -} ubi_hx_header; - - -static int parse_hx(ubi_hx_header * hx, STREAMFILE *sf, int target_subsong); -static VGMSTREAM * init_vgmstream_ubi_hx_header(ubi_hx_header *hx, STREAMFILE *sf); - -/* .HXx - banks from Ubisoft's HXAudio engine games [Rayman Arena, Rayman 3, XIII] */ -VGMSTREAM * init_vgmstream_ubi_hx(STREAMFILE *streamFile) { - VGMSTREAM* vgmstream = NULL; - ubi_hx_header hx = {0}; - int target_subsong = streamFile->stream_index; - - - /* checks */ - /* .hxd: Rayman M/Arena (all), PK: Out of Shadows (all) - * .hxc: Rayman 3 (PC), XIII (PC) - * .hx2: Rayman 3 (PS2), XIII (PS2) - * .hxg: Rayman 3 (GC), XIII (GC) - * .hxx: Rayman 3 (Xbox), Rayman 3 HD (X360) - * .hx3: Rayman 3 HD (PS3) */ - if (!check_extensions(streamFile, "hxd,hxc,hx2,hxg,hxx,hx3")) - goto fail; - - /* .HXx is a slightly less bizarre bank with various resource classes (events, streams, etc, not unlike other Ubi's engines) - * then an index to those types. Some games leave a companion .bnh with text info, probably leftover from their tools. - * Game seems to play files by calling linked ids: EventResData (play/stop/etc) > Random/Program/Wav ResData (1..N refs) > FileIdObj */ - - /* HX CONFIG */ - hx.big_endian = guess_endianness32bit(0x00, streamFile); - - /* HX HEADER */ - if (!parse_hx(&hx, streamFile, target_subsong)) - goto fail; - - /* CREATE VGMSTREAM */ - vgmstream = init_vgmstream_ubi_hx_header(&hx, streamFile); - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - - -static void build_readable_name(char * buf, size_t buf_size, ubi_hx_header * hx) { - const char *grp_name; - - if (hx->is_external) - grp_name = hx->resource_name; - else - grp_name = "internal"; - - if (hx->internal_name[0]) - snprintf(buf,buf_size, "%s/%i/%08x-%08x/%s/%s", "hx", hx->header_index, hx->cuuid1,hx->cuuid2, grp_name, hx->internal_name); - else - snprintf(buf,buf_size, "%s/%i/%08x-%08x/%s", "hx", hx->header_index, hx->cuuid1,hx->cuuid2, grp_name); -} - -#define TXT_LINE_MAX 0x1000 - -/* get name */ -static int parse_name_bnh(ubi_hx_header * hx, STREAMFILE *sf, uint32_t cuuid1, uint32_t cuuid2) { - STREAMFILE *sf_t; - off_t txt_offset = 0; - char line[TXT_LINE_MAX]; - char cuuid[40]; - - sf_t = open_streamfile_by_ext(sf,"bnh"); - if (sf_t == NULL) goto fail; - - snprintf(cuuid,sizeof(cuuid), "cuuid( 0x%08x, 0x%08x )", cuuid1, cuuid2); - - /* each .bnh line has a cuuid, a bunch of repeated fields and name (sometimes name is filename or "bad name") */ - while (txt_offset < get_streamfile_size(sf)) { - int line_ok, bytes_read; - - bytes_read = read_line(line, sizeof(line), txt_offset, sf_t, &line_ok); - if (!line_ok) break; - txt_offset += bytes_read; - - if (strncmp(line,cuuid,31) != 0) - continue; - if (bytes_read <= 79) - goto fail; - - /* cuuid found, copy name (lines are fixed and always starts from the same position) */ - strcpy(hx->internal_name, &line[79]); - - close_streamfile(sf_t); - return 1; - } - -fail: - close_streamfile(sf_t); - return 0; -} - - -/* get referenced name from WavRes, using the index again (abridged) */ -static int parse_name(ubi_hx_header * hx, STREAMFILE *sf) { - int32_t (*read_32bit)(off_t,STREAMFILE*) = hx->big_endian ? read_32bitBE : read_32bitLE; - off_t index_offset, offset; - int i, index_entries; - char class_name[255]; - - - index_offset = read_32bit(0x00, sf); - index_entries = read_32bit(index_offset + 0x08, sf); - offset = index_offset + 0x0c; - for (i = 0; i < index_entries; i++) { - off_t header_offset; - size_t class_size; - int j, link_count, language_count, is_found = 0; - uint32_t cuuid1, cuuid2; - - - class_size = read_32bit(offset + 0x00, sf); - if (class_size > sizeof(class_name)+1) goto fail; - read_string(class_name,class_size+1, offset + 0x04, sf); /* not null-terminated */ - offset += 0x04 + class_size; - - cuuid1 = (uint32_t)read_32bit(offset + 0x00, sf); - cuuid2 = (uint32_t)read_32bit(offset + 0x04, sf); - - header_offset = read_32bit(offset + 0x08, sf); - offset += 0x10; - - //unknown_count = read_32bit(offset + 0x00, sf); - offset += 0x04; - - link_count = read_32bit(offset + 0x00, sf); - offset += 0x04; - for (j = 0; j < link_count; j++) { - uint32_t link_id1 = (uint32_t)read_32bit(offset + 0x00, sf); - uint32_t link_id2 = (uint32_t)read_32bit(offset + 0x04, sf); - - if (link_id1 == hx->cuuid1 && link_id2 == hx->cuuid2) { - is_found = 1; - } - offset += 0x08; - } - - language_count = read_32bit(offset + 0x00, sf); - offset += 0x04; - for (j = 0; j < language_count; j++) { - uint32_t link_id1 = (uint32_t)read_32bit(offset + 0x08, sf); - uint32_t link_id2 = (uint32_t)read_32bit(offset + 0x0c, sf); - - if (link_id1 == hx->cuuid1 && link_id2 == hx->cuuid2) { - is_found = 1; - } - - offset += 0x10; - } - - /* identify all possible names so unknown platforms fail */ - if (is_found && ( - strcmp(class_name, "CPCWavResData") == 0 || - strcmp(class_name, "CPS2WavResData") == 0 || - strcmp(class_name, "CGCWavResData") == 0 || - strcmp(class_name, "CXBoxWavResData") == 0 || - strcmp(class_name, "CPS3WavResData") == 0)) { - size_t resclass_size, internal_size; - off_t wavres_offset = header_offset; - - /* parse WavRes header */ - resclass_size = read_32bit(wavres_offset, sf); - wavres_offset += 0x04 + resclass_size + 0x08 + 0x04; /* skip class + cuiid + flags */ - - internal_size = read_32bit(wavres_offset + 0x00, sf); - if (internal_size > sizeof(hx->internal_name)+1) goto fail; - - /* usually 0 in consoles */ - if (internal_size != 0) { - read_string(hx->internal_name,internal_size+1, wavres_offset + 0x04, sf); - return 1; - } - else { - parse_name_bnh(hx, sf, cuuid1, cuuid2); - return 1; /* ignore error */ - } - } - } - -fail: - return 0; -} - - -/* parse a single known header resource at offset */ -static int parse_header(ubi_hx_header * hx, STREAMFILE *sf, off_t offset, size_t size, int index) { - int32_t (*read_32bit)(off_t,STREAMFILE*) = hx->big_endian ? read_32bitBE : read_32bitLE; - int16_t (*read_16bit)(off_t,STREAMFILE*) = hx->big_endian ? read_16bitBE : read_16bitLE; - off_t riff_offset, riff_size, chunk_offset, stream_adjust = 0, resource_size; - size_t chunk_size; - int cue_flag = 0; - - //todo cleanup/unify common readings - - //;VGM_LOG("UBI HX: header o=%lx, s=%x\n\n", offset, size); - - hx->header_index = index; - hx->header_offset = offset; - hx->header_size = size; - - hx->class_size = read_32bit(offset + 0x00, sf); - if (hx->class_size > sizeof(hx->class_name)+1) goto fail; - read_string(hx->class_name,hx->class_size+1, offset + 0x04, sf); - offset += 0x04 + hx->class_size; - - hx->cuuid1 = (uint32_t)read_32bit(offset + 0x00, sf); - hx->cuuid2 = (uint32_t)read_32bit(offset + 0x04, sf); - offset += 0x08; - - if (strcmp(hx->class_name, "CPCWaveFileIdObj") == 0 || - strcmp(hx->class_name, "CPS2WaveFileIdObj") == 0 || - strcmp(hx->class_name, "CGCWaveFileIdObj") == 0) { - uint32_t flag_type = read_32bit(offset + 0x00, sf); - - if (flag_type == 0x01 || flag_type == 0x02) { /* Rayman Arena */ - if (read_32bit(offset + 0x04, sf) != 0x00) goto fail; - hx->stream_mode = read_32bit(offset + 0x08, sf); /* flag: 0=internal, 1=external */ - /* 0x0c: flag: 0=static, 1=stream */ - offset += 0x10; - } - else if (flag_type == 0x03) { /* others */ - /* 0x04: some kind of parent id shared by multiple Waves, or 0 */ - offset += 0x08; - - if (strcmp(hx->class_name, "CGCWaveFileIdObj") == 0) { - if (read_32bit(offset + 0x00, sf) != read_32bit(offset + 0x04, sf)) goto fail; /* meaning? */ - hx->stream_mode = read_32bit(offset + 0x04, sf); - offset += 0x08; - } - else { - hx->stream_mode = read_8bit(offset, sf); - offset += 0x01; - } - } - else { - VGM_LOG("UBI HX: unknown flag-type\n"); - goto fail; - } - - /* get bizarro adjust (found in XIII external files) */ - if (hx->stream_mode == 0x0a) { - stream_adjust = read_32bit(offset, sf); /* what */ - offset += 0x04; - } - - //todo probably a flag: &1=external, &2=stream, &8=has adjust (XIII), &4=??? (XIII PS2, small, mono) - switch(hx->stream_mode) { - case 0x00: /* memory (internal file) */ - riff_offset = offset; - riff_size = read_32bit(riff_offset + 0x04, sf) + 0x08; - break; - - case 0x01: /* static (smaller external file) */ - case 0x03: /* stream (bigger external file) */ - case 0x07: /* static? */ - case 0x0a: /* static? */ - resource_size = read_32bit(offset + 0x00, sf); - if (resource_size > sizeof(hx->resource_name)+1) goto fail; - read_string(hx->resource_name,resource_size+1, offset + 0x04, sf); - - riff_offset = offset + 0x04 + resource_size; - riff_size = read_32bit(riff_offset + 0x04, sf) + 0x08; - - hx->is_external = 1; - break; - - default: - goto fail; - } - - - /* parse pseudo-RIFF "fmt" */ - if (read_32bit(riff_offset, sf) != 0x46464952) /* "RIFF" in machine endianness */ - goto fail; - - hx->codec_id = read_16bit(riff_offset + 0x14 , sf); - switch(hx->codec_id) { - case 0x01: hx->codec = PCM; break; - case 0x02: hx->codec = UBI; break; - case 0x03: hx->codec = PSX; break; - case 0x04: hx->codec = DSP; break; - default: goto fail; - } - hx->channels = read_16bit(riff_offset + 0x16, sf); - hx->sample_rate = read_32bit(riff_offset + 0x18, sf); - - /* find "datx" (external) or "data" (internal) also in machine endianness */ - if (hx->is_external) { - if (find_chunk_riff_ve(sf, 0x78746164,riff_offset + 0x0c,riff_size - 0x0c, &chunk_offset,NULL, hx->big_endian)) { - hx->stream_size = read_32bit(chunk_offset + 0x00, sf); - hx->stream_offset = read_32bit(chunk_offset + 0x04, sf) + stream_adjust; - } - else if ((flag_type == 0x01 || flag_type == 0x02) && /* Rayman M (not Arena) uses "data" instead */ - find_chunk_riff_ve(sf, 0x61746164,riff_offset + 0x0c,riff_size - 0x0c, &chunk_offset,&chunk_size, hx->big_endian)) { - hx->stream_size = chunk_size; - hx->stream_offset = read_32bit(chunk_offset + 0x00, sf) + stream_adjust; - } - else { - goto fail; - } - } - else { - if (!find_chunk_riff_ve(sf, 0x61746164,riff_offset + 0x0c,riff_size - 0x0c, &chunk_offset,NULL, hx->big_endian)) - goto fail; - hx->stream_offset = chunk_offset; - hx->stream_size = riff_size - (chunk_offset - riff_offset); - } - - /* can contain other RIFF stuff like "cue ", "labl" and "ump3" - * XIII music uses cue/labl to play/loop dynamic sections */ - } - else if (strcmp(hx->class_name, "CXBoxStaticHWWaveFileIdObj") == 0 || - strcmp(hx->class_name, "CXBoxStreamHWWaveFileIdObj") == 0 || - strcmp(hx->class_name, "CPS3StaticAC3WaveFileIdObj") == 0 || - strcmp(hx->class_name, "CPS3StreamAC3WaveFileIdObj") == 0) { - - hx->stream_offset = read_32bit(offset + 0x00, sf); - hx->stream_size = read_32bit(offset + 0x04, sf); - offset += 0x08; - - //todo some dummy files have 0 size - - if (read_32bit(offset + 0x00, sf) != 0x01) goto fail; - /* 0x04: some kind of parent id shared by multiple Waves, or 0 */ - offset += 0x08; - - hx->stream_mode = read_8bit(offset, sf); - offset += 0x01; - - if ((strcmp(hx->class_name, "CXBoxStaticHWWaveFileIdObj") == 0 || - strcmp(hx->class_name, "CXBoxStreamHWWaveFileIdObj") == 0) && !hx->big_endian) { - /* micro header: some mix of channels + block size + sample rate + flags, unsure of which bits */ - hx->codec = XIMA; - hx->channels = (uint8_t)read_8bit(offset + 0x01, sf); - switch(hx->channels) { /* upper 2 bits? */ - case 0x48: hx->channels = 1; break; - case 0x90: hx->channels = 2; break; - default: goto fail; - } - hx->sample_rate = (uint16_t)(read_16bit(offset + 0x02, sf) & 0x7FFF) << 1; /* ??? */ - cue_flag = (uint8_t) read_8bit (offset + 0x03, sf) & (1<<7); - offset += 0x04; - } - else if ((strcmp(hx->class_name, "CXBoxStaticHWWaveFileIdObj") == 0 || - strcmp(hx->class_name, "CXBoxStreamHWWaveFileIdObj") == 0) && hx->big_endian) { - /* fake fmt chunk */ - hx->codec = XMA2; - hx->channels = (uint16_t)read_16bit(offset + 0x02, sf); - hx->sample_rate = read_32bit(offset + 0x04, sf); - hx->num_samples = read_32bit(offset + 0x18, sf) / 0x02 / hx->channels; - cue_flag = read_32bit(offset + 0x34, sf); - offset += 0x38; - } - else { - /* MSFC header */ - hx->codec = ATRAC3; - hx->codec_id = read_32bit(offset + 0x04, sf); - hx->channels = read_32bit(offset + 0x08, sf); - hx->sample_rate = read_32bit(offset + 0x10, sf); - cue_flag = read_32bit(offset + 0x40, sf); - offset += 0x44; - } - - /* skip cue table that sometimes exists in streams */ - if (cue_flag) { - int j; - - size_t cue_count = read_32bit(offset, sf); - offset += 0x04; - for (j = 0; j < cue_count; j++) { - /* 0x00: id? */ - size_t description_size = read_32bit(offset + 0x04, sf); /* for next string */ - offset += 0x08 + description_size; - } - } - - - switch(hx->stream_mode) { - case 0x01: /* static (smaller external file) */ - case 0x03: /* stream (bigger external file) */ - case 0x07: /* stream? */ - resource_size = read_32bit(offset + 0x00, sf); - if (resource_size > sizeof(hx->resource_name)+1) goto fail; - read_string(hx->resource_name,resource_size+1, offset + 0x04, sf); - - hx->is_external = 1; - break; - - default: - goto fail; - } - } - else { - goto fail; - } - - return 1; -fail: - VGM_LOG("UBI HX: error parsing header at %lx\n", hx->header_offset); - return 0; -} - - -/* parse a bank index and its possible audio headers (some info from Droolie's .bms) */ -static int parse_hx(ubi_hx_header * hx, STREAMFILE *sf, int target_subsong) { - int32_t (*read_32bit)(off_t,STREAMFILE*) = hx->big_endian ? read_32bitBE : read_32bitLE; - off_t index_offset, offset; - int i, index_entries; - char class_name[255]; - - - index_offset = read_32bit(0x00, sf); - if (read_32bit(index_offset + 0x00, sf) != 0x58444E49) /* "XDNI" (INDX in given endianness) */ - goto fail; - if (read_32bit(index_offset + 0x04, sf) != 0x02) /* type? */ - goto fail; - - if (target_subsong == 0) target_subsong = 1; - - index_entries = read_32bit(index_offset + 0x08, sf); - offset = index_offset + 0x0c; - for (i = 0; i < index_entries; i++) { - off_t header_offset; - size_t class_size, header_size; - int j, unknown_count, link_count, language_count; - - //;VGM_LOG("UBI HX: index %i at %lx\n", i, offset); - - /* parse index entries: offset to actual header plus some extra info also in the header */ - - class_size = read_32bit(offset + 0x00, sf); - if (class_size > sizeof(class_name)+1) goto fail; - - read_string(class_name,class_size+1, offset + 0x04, sf); /* not null-terminated */ - offset += 0x04 + class_size; - - /* 0x00: id1+2 */ - header_offset = read_32bit(offset + 0x08, sf); - header_size = read_32bit(offset + 0x0c, sf); - offset += 0x10; - - /* not seen */ - unknown_count = read_32bit(offset + 0x00, sf); - if (unknown_count != 0) { - VGM_LOG("UBI HX: found unknown near %lx\n", offset); - goto fail; - } - offset += 0x04; - - /* ids that this object directly points to (ex. Event > Random) */ - link_count = read_32bit(offset + 0x00, sf); - offset += 0x04 + 0x08 * link_count; - - /* localized id list of WavRes (can use this list instead of the prev one) */ - language_count = read_32bit(offset + 0x00, sf); - offset += 0x04; - for (j = 0; j < language_count; j++) { - /* 0x00: lang code, in reverse endianness: "en ", "fr ", etc */ - /* 0x04: possibly count of ids for this lang */ - /* 0x08: id1+2 */ - - if (read_32bit(offset + 0x04, sf) != 1) { - VGM_LOG("UBI HX: wrong lang count near %lx\n", offset); - goto fail; /* WavRes doesn't have this field */ - } - offset += 0x10; - } - - //todo figure out CProgramResData sequences - // Format is pretty complex list of values and some offsets in between, then field names - // then more values and finally a list of linked IDs Links are the same as in the index, - // but doesn't seem to be a straight sequence list. Seems it can be used for other config too. - - /* identify all possible names so unknown platforms fail */ - if (strcmp(class_name, "CEventResData") == 0 || /* play/stop/etc event */ - strcmp(class_name, "CProgramResData") == 0 || /* some kind of map/object-like config to make sequences in some cases? */ - strcmp(class_name, "CActorResData") == 0 || /* same? */ - strcmp(class_name, "CRandomResData") == 0 || /* chooses random WavRes from a list */ - strcmp(class_name, "CTreeBank") == 0 || /* points to TreeRes? */ - strcmp(class_name, "CTreeRes") == 0 || /* points to TreeBank? */ - strcmp(class_name, "CSwitchResData") == 0 || /* big list of WavRes */ - strcmp(class_name, "CPCWavResData") == 0 || /* points to WaveFileIdObj */ - strcmp(class_name, "CPS2WavResData") == 0 || - strcmp(class_name, "CGCWavResData") == 0 || - strcmp(class_name, "CXBoxWavResData") == 0 || - strcmp(class_name, "CPS3WavResData") == 0) { - continue; - } - else if (strcmp(class_name, "CPCWaveFileIdObj") == 0 || - strcmp(class_name, "CPS2WaveFileIdObj") == 0 || - strcmp(class_name, "CGCWaveFileIdObj") == 0 || - strcmp(class_name, "CXBoxStaticHWWaveFileIdObj") == 0 || - strcmp(class_name, "CXBoxStreamHWWaveFileIdObj") == 0 || - strcmp(class_name, "CPS3StaticAC3WaveFileIdObj") == 0 || - strcmp(class_name, "CPS3StreamAC3WaveFileIdObj") == 0) { - ; - } - else { - VGM_LOG("UBI HX: unknown type: %s\n", class_name); - goto fail; - } - - if (link_count != 0) { - VGM_LOG("UBI HX: found links in wav object\n"); - goto fail; - } - - hx->total_subsongs++; - if (hx->total_subsongs != target_subsong) - continue; - - if (!parse_header(hx, sf, header_offset, header_size, i)) - goto fail; - if (!parse_name(hx, sf)) - goto fail; - - build_readable_name(hx->readable_name,sizeof(hx->readable_name), hx); - } - - if (target_subsong < 0 || target_subsong > hx->total_subsongs || hx->total_subsongs < 1) goto fail; - - - return 1; -fail: - return 0; -} - - -static STREAMFILE * open_hx_streamfile(ubi_hx_header *hx, STREAMFILE *sf) { - STREAMFILE *streamData = NULL; - - - if (!hx->is_external) - return NULL; - - streamData = open_streamfile_by_filename(sf, hx->resource_name); - if (streamData == NULL) { - VGM_LOG("UBI HX: external stream '%s' not found\n", hx->resource_name); - goto fail; - } - - /* streams often have a "RIFF" with "fmt" and "data" but stream offset/size is already adjusted to skip them */ - - return streamData; -fail: - return NULL; -} - -static VGMSTREAM * init_vgmstream_ubi_hx_header(ubi_hx_header *hx, STREAMFILE *streamFile) { - STREAMFILE *streamTemp = NULL; - STREAMFILE *streamData = NULL; - VGMSTREAM* vgmstream = NULL; - - - if (hx->is_external) { - streamTemp = open_hx_streamfile(hx, streamFile); - if (streamTemp == NULL) goto fail; - streamData = streamTemp; - } - else { - streamData = streamFile; - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(hx->channels, hx->loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_UBI_HX; - vgmstream->sample_rate = hx->sample_rate; - vgmstream->num_streams = hx->total_subsongs; - vgmstream->stream_size = hx->stream_size; - - switch(hx->codec) { - case PCM: - vgmstream->coding_type = coding_PCM16LE; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x02; - - vgmstream->num_samples = pcm_bytes_to_samples(hx->stream_size, hx->channels, 16); - break; - - case UBI: - vgmstream->codec_data = init_ubi_adpcm(streamData, hx->stream_offset, vgmstream->channels); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_UBI_ADPCM; - vgmstream->layout_type = layout_none; - - vgmstream->num_samples = ubi_adpcm_get_samples(vgmstream->codec_data); - /* XIII has 6-bit stereo music, Rayman 3 4-bit music, both use 6-bit mono) */ - break; - - case PSX: - vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x10; - - vgmstream->num_samples = ps_bytes_to_samples(hx->stream_size, hx->channels); - break; - - case DSP: - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x08; - - /* dsp header at start offset */ - vgmstream->num_samples = read_32bitBE(hx->stream_offset + 0x00, streamData); - dsp_read_coefs_be(vgmstream, streamData, hx->stream_offset + 0x1c, 0x60); - dsp_read_hist_be (vgmstream, streamData, hx->stream_offset + 0x40, 0x60); - hx->stream_offset += 0x60 * hx->channels; - hx->stream_size -= 0x60 * hx->channels; - break; - - case XIMA: - vgmstream->coding_type = coding_XBOX_IMA; - vgmstream->layout_type = layout_none; - - vgmstream->num_samples = xbox_ima_bytes_to_samples(hx->stream_size, hx->channels); - break; - -#ifdef VGM_USE_FFMPEG - case XMA2: { - int bytes, block_count, block_size; - uint8_t buf[0x200]; - - block_size = 0x800; - block_count = hx->stream_size / block_size; - - bytes = ffmpeg_make_riff_xma2(buf,0x200, hx->num_samples, hx->stream_size, hx->channels, hx->sample_rate, block_count, block_size); - vgmstream->codec_data = init_ffmpeg_header_offset(streamData, buf,bytes, hx->stream_offset,hx->stream_size); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_none; - - vgmstream->num_samples = hx->num_samples; - - xma_fix_raw_samples_ch(vgmstream, streamData, hx->stream_offset,hx->stream_size, hx->channels, 0,0); - break; - } - - case ATRAC3: { - int block_align, encoder_delay; - - encoder_delay = 1024 + 69*2; - switch(hx->codec_id) { - case 4: block_align = 0x60 * vgmstream->channels; break; - case 5: block_align = 0x98 * vgmstream->channels; break; - case 6: block_align = 0xC0 * vgmstream->channels; break; - default: goto fail; - } - - vgmstream->num_samples = atrac3_bytes_to_samples(hx->stream_size, block_align) - encoder_delay; - - vgmstream->codec_data = init_ffmpeg_atrac3_raw(streamData, hx->stream_offset,hx->stream_size, vgmstream->num_samples,vgmstream->channels,vgmstream->sample_rate, block_align, encoder_delay); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_none; - break; - } -#endif - - default: - goto fail; - } - - strcpy(vgmstream->stream_name, hx->readable_name); - - if (!vgmstream_open_stream(vgmstream, streamData, hx->stream_offset)) - goto fail; - close_streamfile(streamTemp); - return vgmstream; - -fail: - close_streamfile(streamTemp); - close_vgmstream(vgmstream); - return NULL; -} +#include "meta.h" +#include "../layout/layout.h" +#include "../coding/coding.h" + + +typedef enum { PCM, UBI, PSX, DSP, XIMA, ATRAC3, XMA2 } ubi_hx_codec; + +typedef struct { + int big_endian; + int total_subsongs; + + int codec_id; + ubi_hx_codec codec; /* unified codec */ + int header_index; /* entry number within section2 */ + off_t header_offset; /* entry offset within internal .HXx */ + size_t header_size; /* entry offset within internal .HXx */ + char class_name[255]; + size_t class_size; + size_t stream_mode; + + off_t stream_offset; /* data offset within external stream */ + size_t stream_size; /* data size within external stream */ + uint32_t cuuid1; /* usually "Res" id1: class (1=Event, 3=Wave), id2: group id+sound id, */ + uint32_t cuuid2; /* others have some complex id (not hash), id1: parent id?, id2: file id? */ + + int loop_flag; + int channels; + int sample_rate; + int num_samples; + + int is_external; + char resource_name[0x28]; /* filename to the external stream */ + char internal_name[255]; /* WavRes's assigned name */ + char readable_name[255]; /* final subsong name */ + +} ubi_hx_header; + + +static int parse_hx(ubi_hx_header * hx, STREAMFILE *sf, int target_subsong); +static VGMSTREAM * init_vgmstream_ubi_hx_header(ubi_hx_header *hx, STREAMFILE *sf); + +/* .HXx - banks from Ubisoft's HXAudio engine games [Rayman Arena, Rayman 3, XIII] */ +VGMSTREAM * init_vgmstream_ubi_hx(STREAMFILE *streamFile) { + VGMSTREAM* vgmstream = NULL; + ubi_hx_header hx = {0}; + int target_subsong = streamFile->stream_index; + + + /* checks */ + /* .hxd: Rayman M/Arena (all), PK: Out of Shadows (all) + * .hxc: Rayman 3 (PC), XIII (PC) + * .hx2: Rayman 3 (PS2), XIII (PS2) + * .hxg: Rayman 3 (GC), XIII (GC) + * .hxx: Rayman 3 (Xbox), Rayman 3 HD (X360) + * .hx3: Rayman 3 HD (PS3) */ + if (!check_extensions(streamFile, "hxd,hxc,hx2,hxg,hxx,hx3")) + goto fail; + + /* .HXx is a slightly less bizarre bank with various resource classes (events, streams, etc, not unlike other Ubi's engines) + * then an index to those types. Some games leave a companion .bnh with text info, probably leftover from their tools. + * Game seems to play files by calling linked ids: EventResData (play/stop/etc) > Random/Program/Wav ResData (1..N refs) > FileIdObj */ + + /* HX CONFIG */ + hx.big_endian = guess_endianness32bit(0x00, streamFile); + + /* HX HEADER */ + if (!parse_hx(&hx, streamFile, target_subsong)) + goto fail; + + /* CREATE VGMSTREAM */ + vgmstream = init_vgmstream_ubi_hx_header(&hx, streamFile); + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + + +static void build_readable_name(char * buf, size_t buf_size, ubi_hx_header * hx) { + const char *grp_name; + + if (hx->is_external) + grp_name = hx->resource_name; + else + grp_name = "internal"; + + if (hx->internal_name[0]) + snprintf(buf,buf_size, "%s/%i/%08x-%08x/%s/%s", "hx", hx->header_index, hx->cuuid1,hx->cuuid2, grp_name, hx->internal_name); + else + snprintf(buf,buf_size, "%s/%i/%08x-%08x/%s", "hx", hx->header_index, hx->cuuid1,hx->cuuid2, grp_name); +} + +#define TXT_LINE_MAX 0x1000 + +/* get name */ +static int parse_name_bnh(ubi_hx_header * hx, STREAMFILE *sf, uint32_t cuuid1, uint32_t cuuid2) { + STREAMFILE *sf_t; + off_t txt_offset = 0; + char line[TXT_LINE_MAX]; + char cuuid[40]; + + sf_t = open_streamfile_by_ext(sf,"bnh"); + if (sf_t == NULL) goto fail; + + snprintf(cuuid,sizeof(cuuid), "cuuid( 0x%08x, 0x%08x )", cuuid1, cuuid2); + + /* each .bnh line has a cuuid, a bunch of repeated fields and name (sometimes name is filename or "bad name") */ + while (txt_offset < get_streamfile_size(sf)) { + int line_ok, bytes_read; + + bytes_read = read_line(line, sizeof(line), txt_offset, sf_t, &line_ok); + if (!line_ok) break; + txt_offset += bytes_read; + + if (strncmp(line,cuuid,31) != 0) + continue; + if (bytes_read <= 79) + goto fail; + + /* cuuid found, copy name (lines are fixed and always starts from the same position) */ + strcpy(hx->internal_name, &line[79]); + + close_streamfile(sf_t); + return 1; + } + +fail: + close_streamfile(sf_t); + return 0; +} + + +/* get referenced name from WavRes, using the index again (abridged) */ +static int parse_name(ubi_hx_header * hx, STREAMFILE *sf) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = hx->big_endian ? read_32bitBE : read_32bitLE; + off_t index_offset, offset; + int i, index_entries; + char class_name[255]; + + + index_offset = read_32bit(0x00, sf); + index_entries = read_32bit(index_offset + 0x08, sf); + offset = index_offset + 0x0c; + for (i = 0; i < index_entries; i++) { + off_t header_offset; + size_t class_size; + int j, link_count, language_count, is_found = 0; + uint32_t cuuid1, cuuid2; + + + class_size = read_32bit(offset + 0x00, sf); + if (class_size > sizeof(class_name)+1) goto fail; + read_string(class_name,class_size+1, offset + 0x04, sf); /* not null-terminated */ + offset += 0x04 + class_size; + + cuuid1 = (uint32_t)read_32bit(offset + 0x00, sf); + cuuid2 = (uint32_t)read_32bit(offset + 0x04, sf); + + header_offset = read_32bit(offset + 0x08, sf); + offset += 0x10; + + //unknown_count = read_32bit(offset + 0x00, sf); + offset += 0x04; + + link_count = read_32bit(offset + 0x00, sf); + offset += 0x04; + for (j = 0; j < link_count; j++) { + uint32_t link_id1 = (uint32_t)read_32bit(offset + 0x00, sf); + uint32_t link_id2 = (uint32_t)read_32bit(offset + 0x04, sf); + + if (link_id1 == hx->cuuid1 && link_id2 == hx->cuuid2) { + is_found = 1; + } + offset += 0x08; + } + + language_count = read_32bit(offset + 0x00, sf); + offset += 0x04; + for (j = 0; j < language_count; j++) { + uint32_t link_id1 = (uint32_t)read_32bit(offset + 0x08, sf); + uint32_t link_id2 = (uint32_t)read_32bit(offset + 0x0c, sf); + + if (link_id1 == hx->cuuid1 && link_id2 == hx->cuuid2) { + is_found = 1; + } + + offset += 0x10; + } + + /* identify all possible names so unknown platforms fail */ + if (is_found && ( + strcmp(class_name, "CPCWavResData") == 0 || + strcmp(class_name, "CPS2WavResData") == 0 || + strcmp(class_name, "CGCWavResData") == 0 || + strcmp(class_name, "CXBoxWavResData") == 0 || + strcmp(class_name, "CPS3WavResData") == 0)) { + size_t resclass_size, internal_size; + off_t wavres_offset = header_offset; + + /* parse WavRes header */ + resclass_size = read_32bit(wavres_offset, sf); + wavres_offset += 0x04 + resclass_size + 0x08 + 0x04; /* skip class + cuiid + flags */ + + internal_size = read_32bit(wavres_offset + 0x00, sf); + /* Xbox has some kind of big size and "flags" has a value of 2, instead of 3/4 like other platforms */ + if (strcmp(class_name, "CXBoxWavResData") == 0 && internal_size > 0x100) + return 1; + if (internal_size > sizeof(hx->internal_name)+1) + goto fail; + + /* usually 0 in consoles */ + if (internal_size != 0) { + read_string(hx->internal_name,internal_size+1, wavres_offset + 0x04, sf); + return 1; + } + else { + parse_name_bnh(hx, sf, cuuid1, cuuid2); + return 1; /* ignore error */ + } + } + } + +fail: + return 0; +} + + +/* parse a single known header resource at offset */ +static int parse_header(ubi_hx_header * hx, STREAMFILE *sf, off_t offset, size_t size, int index) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = hx->big_endian ? read_32bitBE : read_32bitLE; + int16_t (*read_16bit)(off_t,STREAMFILE*) = hx->big_endian ? read_16bitBE : read_16bitLE; + off_t riff_offset, riff_size, chunk_offset, stream_adjust = 0, resource_size; + size_t chunk_size; + int cue_flag = 0; + + //todo cleanup/unify common readings + + //;VGM_LOG("UBI HX: header o=%lx, s=%x\n\n", offset, size); + + hx->header_index = index; + hx->header_offset = offset; + hx->header_size = size; + + hx->class_size = read_32bit(offset + 0x00, sf); + if (hx->class_size > sizeof(hx->class_name)+1) goto fail; + read_string(hx->class_name,hx->class_size+1, offset + 0x04, sf); + offset += 0x04 + hx->class_size; + + hx->cuuid1 = (uint32_t)read_32bit(offset + 0x00, sf); + hx->cuuid2 = (uint32_t)read_32bit(offset + 0x04, sf); + offset += 0x08; + + if (strcmp(hx->class_name, "CPCWaveFileIdObj") == 0 || + strcmp(hx->class_name, "CPS2WaveFileIdObj") == 0 || + strcmp(hx->class_name, "CGCWaveFileIdObj") == 0 || + strcmp(hx->class_name, "CXBoxWaveFileIdObj") == 0) { + uint32_t flag_type = read_32bit(offset + 0x00, sf); + + if (flag_type == 0x01 || flag_type == 0x02) { /* Rayman Arena */ + if (read_32bit(offset + 0x04, sf) != 0x00) goto fail; + hx->stream_mode = read_32bit(offset + 0x08, sf); /* flag: 0=internal, 1=external */ + /* 0x0c: flag: 0=static, 1=stream */ + offset += 0x10; + } + else if (flag_type == 0x03) { /* others */ + /* 0x04: some kind of parent id shared by multiple Waves, or 0 */ + offset += 0x08; + + if (strcmp(hx->class_name, "CGCWaveFileIdObj") == 0) { + if (read_32bit(offset + 0x00, sf) != read_32bit(offset + 0x04, sf)) goto fail; /* meaning? */ + hx->stream_mode = read_32bit(offset + 0x04, sf); + offset += 0x08; + } + else { + hx->stream_mode = read_8bit(offset, sf); + offset += 0x01; + } + } + else { + VGM_LOG("UBI HX: unknown flag-type\n"); + goto fail; + } + + /* get bizarro adjust (found in XIII external files) */ + if (hx->stream_mode == 0x0a) { + stream_adjust = read_32bit(offset, sf); /* what */ + offset += 0x04; + } + + //todo probably a flag: &1=external, &2=stream, &8=has adjust (XIII), &4=??? (XIII PS2, small, mono) + switch(hx->stream_mode) { + case 0x00: /* memory (internal file) */ + riff_offset = offset; + riff_size = read_32bit(riff_offset + 0x04, sf) + 0x08; + break; + + case 0x01: /* static (smaller external file) */ + case 0x03: /* stream (bigger external file) */ + case 0x07: /* static? */ + case 0x0a: /* static? */ + resource_size = read_32bit(offset + 0x00, sf); + if (resource_size > sizeof(hx->resource_name)+1) goto fail; + read_string(hx->resource_name,resource_size+1, offset + 0x04, sf); + + riff_offset = offset + 0x04 + resource_size; + riff_size = read_32bit(riff_offset + 0x04, sf) + 0x08; + + hx->is_external = 1; + break; + + default: + goto fail; + } + + + /* parse pseudo-RIFF "fmt" */ + if (read_32bit(riff_offset, sf) != 0x46464952) /* "RIFF" in machine endianness */ + goto fail; + + hx->codec_id = read_16bit(riff_offset + 0x14 , sf); + switch(hx->codec_id) { + case 0x01: hx->codec = PCM; break; + case 0x02: hx->codec = UBI; break; + case 0x03: hx->codec = PSX; break; + case 0x04: hx->codec = DSP; break; + case 0x05: hx->codec = XIMA; break; + default: + VGM_LOG("UBI HX: unknown codec %i\n", hx->codec_id); + goto fail; + } + hx->channels = read_16bit(riff_offset + 0x16, sf); + hx->sample_rate = read_32bit(riff_offset + 0x18, sf); + + /* find "datx" (external) or "data" (internal) also in machine endianness */ + if (hx->is_external) { + if (find_chunk_riff_ve(sf, 0x78746164,riff_offset + 0x0c,riff_size - 0x0c, &chunk_offset,NULL, hx->big_endian)) { + hx->stream_size = read_32bit(chunk_offset + 0x00, sf); + hx->stream_offset = read_32bit(chunk_offset + 0x04, sf) + stream_adjust; + } + else if ((flag_type == 0x01 || flag_type == 0x02) && /* Rayman M (not Arena) uses "data" instead */ + find_chunk_riff_ve(sf, 0x61746164,riff_offset + 0x0c,riff_size - 0x0c, &chunk_offset,&chunk_size, hx->big_endian)) { + hx->stream_size = chunk_size; + hx->stream_offset = read_32bit(chunk_offset + 0x00, sf) + stream_adjust; + } + else { + goto fail; + } + } + else { + if (!find_chunk_riff_ve(sf, 0x61746164,riff_offset + 0x0c,riff_size - 0x0c, &chunk_offset,NULL, hx->big_endian)) + goto fail; + hx->stream_offset = chunk_offset; + hx->stream_size = riff_size - (chunk_offset - riff_offset); + } + + /* can contain other RIFF stuff like "cue ", "labl" and "ump3" + * XIII music uses cue/labl to play/loop dynamic sections */ + } + else if (strcmp(hx->class_name, "CXBoxStaticHWWaveFileIdObj") == 0 || + strcmp(hx->class_name, "CXBoxStreamHWWaveFileIdObj") == 0 || + strcmp(hx->class_name, "CPS3StaticAC3WaveFileIdObj") == 0 || + strcmp(hx->class_name, "CPS3StreamAC3WaveFileIdObj") == 0) { + + hx->stream_offset = read_32bit(offset + 0x00, sf); + hx->stream_size = read_32bit(offset + 0x04, sf); + offset += 0x08; + + //todo some dummy files have 0 size + + if (read_32bit(offset + 0x00, sf) != 0x01) goto fail; + /* 0x04: some kind of parent id shared by multiple Waves, or 0 */ + offset += 0x08; + + hx->stream_mode = read_8bit(offset, sf); + offset += 0x01; + + if ((strcmp(hx->class_name, "CXBoxStaticHWWaveFileIdObj") == 0 || + strcmp(hx->class_name, "CXBoxStreamHWWaveFileIdObj") == 0) && !hx->big_endian) { + /* micro header: some mix of channels + block size + sample rate + flags, unsure of which bits */ + hx->codec = XIMA; + hx->channels = (uint8_t)read_8bit(offset + 0x01, sf); + switch(hx->channels) { /* upper 2 bits? */ + case 0x48: hx->channels = 1; break; + case 0x90: hx->channels = 2; break; + default: goto fail; + } + hx->sample_rate = (uint16_t)(read_16bit(offset + 0x02, sf) & 0x7FFF) << 1; /* ??? */ + cue_flag = (uint8_t) read_8bit (offset + 0x03, sf) & (1<<7); + offset += 0x04; + } + else if ((strcmp(hx->class_name, "CXBoxStaticHWWaveFileIdObj") == 0 || + strcmp(hx->class_name, "CXBoxStreamHWWaveFileIdObj") == 0) && hx->big_endian) { + /* fake fmt chunk */ + hx->codec = XMA2; + hx->channels = (uint16_t)read_16bit(offset + 0x02, sf); + hx->sample_rate = read_32bit(offset + 0x04, sf); + hx->num_samples = read_32bit(offset + 0x18, sf) / 0x02 / hx->channels; + cue_flag = read_32bit(offset + 0x34, sf); + offset += 0x38; + } + else { + /* MSFC header */ + hx->codec = ATRAC3; + hx->codec_id = read_32bit(offset + 0x04, sf); + hx->channels = read_32bit(offset + 0x08, sf); + hx->sample_rate = read_32bit(offset + 0x10, sf); + cue_flag = read_32bit(offset + 0x40, sf); + offset += 0x44; + } + + /* skip cue table that sometimes exists in streams */ + if (cue_flag) { + int j; + + size_t cue_count = read_32bit(offset, sf); + offset += 0x04; + for (j = 0; j < cue_count; j++) { + /* 0x00: id? */ + size_t description_size = read_32bit(offset + 0x04, sf); /* for next string */ + offset += 0x08 + description_size; + } + } + + + switch(hx->stream_mode) { + case 0x01: /* static (smaller external file) */ + case 0x03: /* stream (bigger external file) */ + case 0x07: /* stream? */ + resource_size = read_32bit(offset + 0x00, sf); + if (resource_size > sizeof(hx->resource_name)+1) goto fail; + read_string(hx->resource_name,resource_size+1, offset + 0x04, sf); + + hx->is_external = 1; + break; + + default: + goto fail; + } + } + else { + goto fail; + } + + return 1; +fail: + VGM_LOG("UBI HX: error parsing header at %lx\n", hx->header_offset); + return 0; +} + + +/* parse a bank index and its possible audio headers (some info from Droolie's .bms) */ +static int parse_hx(ubi_hx_header * hx, STREAMFILE *sf, int target_subsong) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = hx->big_endian ? read_32bitBE : read_32bitLE; + off_t index_offset, offset; + int i, index_entries; + char class_name[255]; + + + index_offset = read_32bit(0x00, sf); + if (read_32bit(index_offset + 0x00, sf) != 0x58444E49) /* "XDNI" (INDX in given endianness) */ + goto fail; + if (read_32bit(index_offset + 0x04, sf) != 0x02) /* type? */ + goto fail; + + if (target_subsong == 0) target_subsong = 1; + + index_entries = read_32bit(index_offset + 0x08, sf); + offset = index_offset + 0x0c; + for (i = 0; i < index_entries; i++) { + off_t header_offset; + size_t class_size, header_size; + int j, unknown_count, link_count, language_count; + + //;VGM_LOG("UBI HX: index %i at %lx\n", i, offset); + + /* parse index entries: offset to actual header plus some extra info also in the header */ + + class_size = read_32bit(offset + 0x00, sf); + if (class_size > sizeof(class_name)+1) goto fail; + + read_string(class_name,class_size+1, offset + 0x04, sf); /* not null-terminated */ + offset += 0x04 + class_size; + + /* 0x00: id1+2 */ + header_offset = read_32bit(offset + 0x08, sf); + header_size = read_32bit(offset + 0x0c, sf); + offset += 0x10; + + /* not seen */ + unknown_count = read_32bit(offset + 0x00, sf); + if (unknown_count != 0) { + VGM_LOG("UBI HX: found unknown near %lx\n", offset); + goto fail; + } + offset += 0x04; + + /* ids that this object directly points to (ex. Event > Random) */ + link_count = read_32bit(offset + 0x00, sf); + offset += 0x04 + 0x08 * link_count; + + /* localized id list of WavRes (can use this list instead of the prev one) */ + language_count = read_32bit(offset + 0x00, sf); + offset += 0x04; + for (j = 0; j < language_count; j++) { + /* 0x00: lang code, in reverse endianness: "en ", "fr ", etc */ + /* 0x04: possibly count of ids for this lang */ + /* 0x08: id1+2 */ + + if (read_32bit(offset + 0x04, sf) != 1) { + VGM_LOG("UBI HX: wrong lang count near %lx\n", offset); + goto fail; /* WavRes doesn't have this field */ + } + offset += 0x10; + } + + //todo figure out CProgramResData sequences + // Format is pretty complex list of values and some offsets in between, then field names + // then more values and finally a list of linked IDs Links are the same as in the index, + // but doesn't seem to be a straight sequence list. Seems it can be used for other config too. + + /* identify all possible names so unknown platforms fail */ + if (strcmp(class_name, "CEventResData") == 0 || /* play/stop/etc event */ + strcmp(class_name, "CProgramResData") == 0 || /* some kind of map/object-like config to make sequences in some cases? */ + strcmp(class_name, "CActorResData") == 0 || /* same? */ + strcmp(class_name, "CRandomResData") == 0 || /* chooses random WavRes from a list */ + strcmp(class_name, "CTreeBank") == 0 || /* points to TreeRes? */ + strcmp(class_name, "CTreeRes") == 0 || /* points to TreeBank? */ + strcmp(class_name, "CSwitchResData") == 0 || /* big list of WavRes */ + strcmp(class_name, "CPCWavResData") == 0 || /* points to WaveFileIdObj */ + strcmp(class_name, "CPS2WavResData") == 0 || + strcmp(class_name, "CGCWavResData") == 0 || + strcmp(class_name, "CXBoxWavResData") == 0 || + strcmp(class_name, "CPS3WavResData") == 0) { + continue; + } + else if (strcmp(class_name, "CPCWaveFileIdObj") == 0 || + strcmp(class_name, "CPS2WaveFileIdObj") == 0 || + strcmp(class_name, "CGCWaveFileIdObj") == 0 || + strcmp(class_name, "CXBoxWaveFileIdObj") == 0 || + strcmp(class_name, "CXBoxStaticHWWaveFileIdObj") == 0 || + strcmp(class_name, "CXBoxStreamHWWaveFileIdObj") == 0 || + strcmp(class_name, "CPS3StaticAC3WaveFileIdObj") == 0 || + strcmp(class_name, "CPS3StreamAC3WaveFileIdObj") == 0) { + ; + } + else { + VGM_LOG("UBI HX: unknown type: %s\n", class_name); + goto fail; + } + + if (link_count != 0) { + VGM_LOG("UBI HX: found links in wav object\n"); + goto fail; + } + + hx->total_subsongs++; + if (hx->total_subsongs != target_subsong) + continue; + + if (!parse_header(hx, sf, header_offset, header_size, i)) + goto fail; + if (!parse_name(hx, sf)) + goto fail; + + build_readable_name(hx->readable_name,sizeof(hx->readable_name), hx); + } + + if (target_subsong < 0 || target_subsong > hx->total_subsongs || hx->total_subsongs < 1) goto fail; + + + return 1; +fail: + return 0; +} + + +static STREAMFILE * open_hx_streamfile(ubi_hx_header *hx, STREAMFILE *sf) { + STREAMFILE *streamData = NULL; + + + if (!hx->is_external) + return NULL; + + streamData = open_streamfile_by_filename(sf, hx->resource_name); + if (streamData == NULL) { + VGM_LOG("UBI HX: external stream '%s' not found\n", hx->resource_name); + goto fail; + } + + /* streams often have a "RIFF" with "fmt" and "data" but stream offset/size is already adjusted to skip them */ + + return streamData; +fail: + return NULL; +} + +static VGMSTREAM * init_vgmstream_ubi_hx_header(ubi_hx_header *hx, STREAMFILE *streamFile) { + STREAMFILE *streamTemp = NULL; + STREAMFILE *streamData = NULL; + VGMSTREAM* vgmstream = NULL; + + + if (hx->is_external) { + streamTemp = open_hx_streamfile(hx, streamFile); + if (streamTemp == NULL) goto fail; + streamData = streamTemp; + } + else { + streamData = streamFile; + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(hx->channels, hx->loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_UBI_HX; + vgmstream->sample_rate = hx->sample_rate; + vgmstream->num_streams = hx->total_subsongs; + vgmstream->stream_size = hx->stream_size; + + switch(hx->codec) { + case PCM: + vgmstream->coding_type = coding_PCM16LE; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x02; + + vgmstream->num_samples = pcm_bytes_to_samples(hx->stream_size, hx->channels, 16); + break; + + case UBI: + vgmstream->codec_data = init_ubi_adpcm(streamData, hx->stream_offset, vgmstream->channels); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_UBI_ADPCM; + vgmstream->layout_type = layout_none; + + vgmstream->num_samples = ubi_adpcm_get_samples(vgmstream->codec_data); + /* XIII has 6-bit stereo music, Rayman 3 4-bit music, both use 6-bit mono) */ + break; + + case PSX: + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x10; + + vgmstream->num_samples = ps_bytes_to_samples(hx->stream_size, hx->channels); + break; + + case DSP: + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x08; + + /* dsp header at start offset */ + vgmstream->num_samples = read_32bitBE(hx->stream_offset + 0x00, streamData); + dsp_read_coefs_be(vgmstream, streamData, hx->stream_offset + 0x1c, 0x60); + dsp_read_hist_be (vgmstream, streamData, hx->stream_offset + 0x40, 0x60); + hx->stream_offset += 0x60 * hx->channels; + hx->stream_size -= 0x60 * hx->channels; + break; + + case XIMA: + vgmstream->coding_type = coding_XBOX_IMA; + vgmstream->layout_type = layout_none; + + vgmstream->num_samples = xbox_ima_bytes_to_samples(hx->stream_size, hx->channels); + break; + +#ifdef VGM_USE_FFMPEG + case XMA2: { + int bytes, block_count, block_size; + uint8_t buf[0x200]; + + block_size = 0x800; + block_count = hx->stream_size / block_size; + + bytes = ffmpeg_make_riff_xma2(buf,0x200, hx->num_samples, hx->stream_size, hx->channels, hx->sample_rate, block_count, block_size); + vgmstream->codec_data = init_ffmpeg_header_offset(streamData, buf,bytes, hx->stream_offset,hx->stream_size); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + vgmstream->num_samples = hx->num_samples; + + xma_fix_raw_samples_ch(vgmstream, streamData, hx->stream_offset,hx->stream_size, hx->channels, 0,0); + break; + } + + case ATRAC3: { + int block_align, encoder_delay; + + encoder_delay = 1024 + 69*2; + switch(hx->codec_id) { + case 4: block_align = 0x60 * vgmstream->channels; break; + case 5: block_align = 0x98 * vgmstream->channels; break; + case 6: block_align = 0xC0 * vgmstream->channels; break; + default: goto fail; + } + + vgmstream->num_samples = atrac3_bytes_to_samples(hx->stream_size, block_align) - encoder_delay; + + vgmstream->codec_data = init_ffmpeg_atrac3_raw(streamData, hx->stream_offset,hx->stream_size, vgmstream->num_samples,vgmstream->channels,vgmstream->sample_rate, block_align, encoder_delay); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + break; + } +#endif + + default: + goto fail; + } + + strcpy(vgmstream->stream_name, hx->readable_name); + + if (!vgmstream_open_stream(vgmstream, streamData, hx->stream_offset)) + goto fail; + close_streamfile(streamTemp); + return vgmstream; + +fail: + close_streamfile(streamTemp); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/vgmstream.c b/src/vgmstream.c index 22be6fb5..3094144b 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -486,6 +486,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_xssb, init_vgmstream_xma_ue3, init_vgmstream_csb, + init_vgmstream_fda, /* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */ init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */ @@ -656,6 +657,10 @@ void reset_vgmstream(VGMSTREAM * vgmstream) { } #endif + if (vgmstream->coding_type == coding_RELIC) { + reset_relic(vgmstream->codec_data); + } + if (vgmstream->coding_type == coding_CRI_HCA) { reset_hca(vgmstream->codec_data); } @@ -823,6 +828,11 @@ void close_vgmstream(VGMSTREAM * vgmstream) { } #endif + if (vgmstream->coding_type == coding_RELIC) { + free_relic(vgmstream->codec_data); + vgmstream->codec_data = NULL; + } + if (vgmstream->coding_type == coding_CRI_HCA) { free_hca(vgmstream->codec_data); vgmstream->codec_data = NULL; @@ -1273,6 +1283,8 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) { return 0; /* varies per mode */ case coding_EA_MT: return 0; /* 432, but variable in looped files */ + case coding_RELIC: + return 0; /* 512 */ case coding_CRI_HCA: return 0; /* 1024 - delay/padding (which can be bigger than 1024) */ #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) @@ -1757,6 +1769,10 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to samples_to_do,vgmstream->channels); break; #endif + case coding_RELIC: + decode_relic(&vgmstream->ch[0], vgmstream->codec_data, buffer+samples_written*vgmstream->channels, + samples_to_do); + break; case coding_CRI_HCA: decode_hca(vgmstream->codec_data, buffer+samples_written*vgmstream->channels, samples_to_do); @@ -2191,6 +2207,10 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) { /* prepare certain codecs' internal state for looping */ + if (vgmstream->coding_type == coding_RELIC) { + seek_relic(vgmstream->codec_data, vgmstream->loop_sample); + } + if (vgmstream->coding_type == coding_CRI_HCA) { loop_hca(vgmstream->codec_data, vgmstream->loop_sample); } diff --git a/src/vgmstream.h b/src/vgmstream.h index b8dc34c7..9f26911c 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -183,6 +183,7 @@ typedef enum { coding_EA_MT, /* Electronic Arts MicroTalk (linear-predictive speech codec) */ + coding_RELIC, /* Relic Codec (DCT-based) */ coding_CRI_HCA, /* CRI High Compression Audio (MDCT-based) */ #ifdef VGM_USE_VORBIS @@ -718,6 +719,7 @@ typedef enum { meta_ISB, meta_XSSB, meta_XMA_UE3, + meta_FDA, } meta_t; @@ -1147,6 +1149,7 @@ typedef struct { NWAData *nwa; } nwa_codec_data; +typedef struct relic_codec_data relic_codec_data; typedef struct { STREAMFILE *streamfile;