Updated VGMStream to r1050-2696-g0a04a738
parent
8e9f8237e3
commit
90ac083705
|
@ -89,6 +89,8 @@
|
|||
8323894B1D22419B00482226 /* clHCA.h in Headers */ = {isa = PBXBuildFile; fileRef = 832389491D22419B00482226 /* clHCA.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
832389501D2246C300482226 /* hca.c in Sources */ = {isa = PBXBuildFile; fileRef = 8323894F1D2246C300482226 /* hca.c */; };
|
||||
832389521D224C0800482226 /* hca_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 832389511D224C0800482226 /* hca_decoder.c */; };
|
||||
83269DD22399F5DE00F49FE3 /* nus3bank_streamfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 83269DD02399F5DD00F49FE3 /* nus3bank_streamfile.h */; };
|
||||
83269DD32399F5DE00F49FE3 /* ivag.c in Sources */ = {isa = PBXBuildFile; fileRef = 83269DD12399F5DE00F49FE3 /* ivag.c */; };
|
||||
83299FD01E7660C7003A3242 /* bik.c in Sources */ = {isa = PBXBuildFile; fileRef = 83299FCE1E7660C7003A3242 /* bik.c */; };
|
||||
83299FD11E7660C7003A3242 /* dsp_adx.c in Sources */ = {isa = PBXBuildFile; fileRef = 83299FCF1E7660C7003A3242 /* dsp_adx.c */; };
|
||||
832BF7FF21E050B7006F50F1 /* circus_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 832BF7FC21E050B6006F50F1 /* circus_decoder.c */; };
|
||||
|
@ -378,7 +380,6 @@
|
|||
836F700E18BDC2190095E648 /* ps2_xa2.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ED218BDC2190095E648 /* ps2_xa2.c */; };
|
||||
836F700F18BDC2190095E648 /* ps2_xa30.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ED318BDC2190095E648 /* ps2_xa30.c */; };
|
||||
836F701118BDC2190095E648 /* ps3_cps.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ED518BDC2190095E648 /* ps3_cps.c */; };
|
||||
836F701218BDC2190095E648 /* ps3_ivag.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ED618BDC2190095E648 /* ps3_ivag.c */; };
|
||||
836F701518BDC2190095E648 /* ps3_past.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ED918BDC2190095E648 /* ps3_past.c */; };
|
||||
836F701E18BDC2190095E648 /* redspark.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EE218BDC2190095E648 /* redspark.c */; };
|
||||
836F701F18BDC2190095E648 /* riff.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EE318BDC2190095E648 /* riff.c */; };
|
||||
|
@ -776,6 +777,8 @@
|
|||
832389491D22419B00482226 /* clHCA.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = clHCA.h; sourceTree = "<group>"; };
|
||||
8323894F1D2246C300482226 /* hca.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = hca.c; sourceTree = "<group>"; };
|
||||
832389511D224C0800482226 /* hca_decoder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = hca_decoder.c; sourceTree = "<group>"; };
|
||||
83269DD02399F5DD00F49FE3 /* nus3bank_streamfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nus3bank_streamfile.h; sourceTree = "<group>"; };
|
||||
83269DD12399F5DE00F49FE3 /* ivag.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ivag.c; sourceTree = "<group>"; };
|
||||
83299FCE1E7660C7003A3242 /* bik.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = bik.c; sourceTree = "<group>"; };
|
||||
83299FCF1E7660C7003A3242 /* dsp_adx.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dsp_adx.c; sourceTree = "<group>"; };
|
||||
832BF7FC21E050B6006F50F1 /* circus_decoder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = circus_decoder.c; sourceTree = "<group>"; };
|
||||
|
@ -1065,7 +1068,6 @@
|
|||
836F6ED218BDC2190095E648 /* ps2_xa2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_xa2.c; sourceTree = "<group>"; };
|
||||
836F6ED318BDC2190095E648 /* ps2_xa30.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_xa30.c; sourceTree = "<group>"; };
|
||||
836F6ED518BDC2190095E648 /* ps3_cps.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps3_cps.c; sourceTree = "<group>"; };
|
||||
836F6ED618BDC2190095E648 /* ps3_ivag.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps3_ivag.c; sourceTree = "<group>"; };
|
||||
836F6ED918BDC2190095E648 /* ps3_past.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps3_past.c; sourceTree = "<group>"; };
|
||||
836F6EE218BDC2190095E648 /* redspark.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = redspark.c; sourceTree = "<group>"; };
|
||||
836F6EE318BDC2190095E648 /* riff.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = riff.c; sourceTree = "<group>"; };
|
||||
|
@ -1647,6 +1649,7 @@
|
|||
836F6E5518BDC2180095E648 /* ios_psnd.c */,
|
||||
83AFABBB23795202002F3947 /* isb.c */,
|
||||
836F6E5618BDC2180095E648 /* ish_isd.c */,
|
||||
83269DD12399F5DE00F49FE3 /* ivag.c */,
|
||||
836F6E5718BDC2180095E648 /* ivaud.c */,
|
||||
836F6E5818BDC2180095E648 /* ivb.c */,
|
||||
837CEAE923487F2B00E62A4A /* jstm_streamfile.h */,
|
||||
|
@ -1715,6 +1718,7 @@
|
|||
83C727FC22BC893900678B4A /* nps.c */,
|
||||
837CEAE223487F2A00E62A4A /* nub.c */,
|
||||
832BF81B21E0514B006F50F1 /* nus3audio.c */,
|
||||
83269DD02399F5DD00F49FE3 /* nus3bank_streamfile.h */,
|
||||
834FE0D1215C79E9000A5D3D /* nus3bank.c */,
|
||||
836F6E8118BDC2180095E648 /* nwa.c */,
|
||||
832BF81421E0514A006F50F1 /* nwav.c */,
|
||||
|
@ -1800,7 +1804,6 @@
|
|||
836F6ED218BDC2190095E648 /* ps2_xa2.c */,
|
||||
836F6ED318BDC2190095E648 /* ps2_xa30.c */,
|
||||
836F6ED518BDC2190095E648 /* ps3_cps.c */,
|
||||
836F6ED618BDC2190095E648 /* ps3_ivag.c */,
|
||||
836F6ED918BDC2190095E648 /* ps3_past.c */,
|
||||
837CEAE823487F2B00E62A4A /* psf.c */,
|
||||
83997F5722D9569E00633184 /* rad.c */,
|
||||
|
@ -2033,6 +2036,7 @@
|
|||
837CEAF323487F2C00E62A4A /* mzrt_streamfile.h in Headers */,
|
||||
8349A91B1FE6258200E26435 /* adx_keys.h in Headers */,
|
||||
836F6F4D18BDC2190095E648 /* layout.h in Headers */,
|
||||
83269DD22399F5DE00F49FE3 /* nus3bank_streamfile.h in Headers */,
|
||||
83AA5D251F6E2F9C0020821C /* hca_keys.h in Headers */,
|
||||
836F6F2318BDC2190095E648 /* coding.h in Headers */,
|
||||
);
|
||||
|
@ -2217,6 +2221,7 @@
|
|||
8301659C1F256BD000CA0941 /* nds_strm_ffta2.c in Sources */,
|
||||
837CEA7923487E2500E62A4A /* ubi_adpcm_decoder.c in Sources */,
|
||||
834FE0EB215C79ED000A5D3D /* str_wav.c in Sources */,
|
||||
83269DD32399F5DE00F49FE3 /* ivag.c in Sources */,
|
||||
8349A8DF1FE6251F00E26435 /* vorbis_custom_utils_vid1.c in Sources */,
|
||||
83A21F8D201D8982000F04B9 /* sqex_sead.c in Sources */,
|
||||
83EED5D3203A8BC7008BEB45 /* ea_swvr.c in Sources */,
|
||||
|
@ -2537,7 +2542,6 @@
|
|||
83A21F8A201D8982000F04B9 /* fsb_encrypted.c in Sources */,
|
||||
8306B0B120984552000302D4 /* blocked_halpst.c in Sources */,
|
||||
836F6FD018BDC2190095E648 /* ps2_b1s.c in Sources */,
|
||||
836F701218BDC2190095E648 /* ps3_ivag.c in Sources */,
|
||||
83AA5D181F6E2F600020821C /* mpeg_custom_utils_awc.c in Sources */,
|
||||
834FE0FE215C79ED000A5D3D /* ps_headerless.c in Sources */,
|
||||
8306B0BB20984552000302D4 /* blocked_gsb.c in Sources */,
|
||||
|
|
|
@ -1,56 +1,56 @@
|
|||
#include "coding.h"
|
||||
|
||||
|
||||
/* Decodes Argonaut's ASF ADPCM codec, used in some of their PC games.
|
||||
* Reverse engineered from asfcodec.adl DLL. */
|
||||
void decode_asf(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
|
||||
uint8_t frame[0x11] = {0};
|
||||
off_t frame_offset;
|
||||
int i, frames_in, sample_count = 0;
|
||||
size_t bytes_per_frame, samples_per_frame;
|
||||
int shift, mode;
|
||||
int32_t hist1 = stream->adpcm_history1_32;
|
||||
int32_t hist2 = stream->adpcm_history2_32;
|
||||
|
||||
|
||||
/* external interleave (fixed size), mono */
|
||||
bytes_per_frame = 0x11;
|
||||
samples_per_frame = (bytes_per_frame - 0x01) * 2;
|
||||
frames_in = first_sample / samples_per_frame;
|
||||
//first_sample = first_sample % samples_per_frame; /* for flat layout */
|
||||
|
||||
/* parse frame header */
|
||||
frame_offset = stream->offset + bytes_per_frame*frames_in;
|
||||
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
|
||||
shift = (frame[0x00] >> 4) & 0xf;
|
||||
mode = (frame[0x00] >> 0) & 0xf;
|
||||
|
||||
/* decode nibbles */
|
||||
for (i = first_sample; i < first_sample + samples_to_do; i++) {
|
||||
uint8_t nibbles = frame[0x01 + i/2];
|
||||
int32_t sample;
|
||||
|
||||
sample = i&1 ? /* high nibble first */
|
||||
get_low_nibble_signed(nibbles):
|
||||
get_high_nibble_signed(nibbles);
|
||||
sample = (sample << 4) << (shift + 2); /* move sample to upper nibble, then shift + 2 (IOW: shift + 6) */
|
||||
|
||||
/* mode is checked as a flag, so there are 2 modes only, but lower nibble
|
||||
* may have other values at last frame (ex 0x02/09), could be control flags (loop related?) */
|
||||
if (mode & 0x4) { /* ~filters: 2, -1 */
|
||||
sample = (sample + (hist1 << 7) - (hist2 << 6)) >> 6;
|
||||
}
|
||||
else { /* ~filters: 1, 0 */
|
||||
sample = (sample + (hist1 << 6)) >> 6;
|
||||
}
|
||||
|
||||
outbuf[sample_count] = (int16_t)sample; /* must not clamp */
|
||||
sample_count += channelspacing;
|
||||
|
||||
hist2 = hist1;
|
||||
hist1 = sample;
|
||||
}
|
||||
|
||||
stream->adpcm_history1_32 = hist1;
|
||||
stream->adpcm_history2_32 = hist2;
|
||||
}
|
||||
#include "coding.h"
|
||||
|
||||
|
||||
/* Decodes Argonaut's ASF ADPCM codec, used in some of their PC games.
|
||||
* Reverse engineered from asfcodec.adl DLL. */
|
||||
void decode_asf(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
|
||||
uint8_t frame[0x11] = {0};
|
||||
off_t frame_offset;
|
||||
int i, frames_in, sample_count = 0;
|
||||
size_t bytes_per_frame, samples_per_frame;
|
||||
int shift, mode;
|
||||
int32_t hist1 = stream->adpcm_history1_32;
|
||||
int32_t hist2 = stream->adpcm_history2_32;
|
||||
|
||||
|
||||
/* external interleave (fixed size), mono */
|
||||
bytes_per_frame = 0x11;
|
||||
samples_per_frame = (bytes_per_frame - 0x01) * 2;
|
||||
frames_in = first_sample / samples_per_frame;
|
||||
//first_sample = first_sample % samples_per_frame; /* for flat layout */
|
||||
|
||||
/* parse frame header */
|
||||
frame_offset = stream->offset + bytes_per_frame*frames_in;
|
||||
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
|
||||
shift = (frame[0x00] >> 4) & 0xf;
|
||||
mode = (frame[0x00] >> 0) & 0xf;
|
||||
|
||||
/* decode nibbles */
|
||||
for (i = first_sample; i < first_sample + samples_to_do; i++) {
|
||||
uint8_t nibbles = frame[0x01 + i/2];
|
||||
int32_t sample;
|
||||
|
||||
sample = i&1 ? /* high nibble first */
|
||||
get_low_nibble_signed(nibbles):
|
||||
get_high_nibble_signed(nibbles);
|
||||
sample = (sample << 4) << (shift + 2); /* move sample to upper nibble, then shift + 2 (IOW: shift + 6) */
|
||||
|
||||
/* mode is checked as a flag, so there are 2 modes only, but lower nibble
|
||||
* may have other values at last frame (ex 0x02/09), could be control flags (loop related?) */
|
||||
if (mode & 0x4) { /* ~filters: 2, -1 */
|
||||
sample = (sample + (hist1 << 7) - (hist2 << 6)) >> 6;
|
||||
}
|
||||
else { /* ~filters: 1, 0 */
|
||||
sample = (sample + (hist1 << 6)) >> 6;
|
||||
}
|
||||
|
||||
outbuf[sample_count] = (int16_t)sample; /* must not clamp */
|
||||
sample_count += channelspacing;
|
||||
|
||||
hist2 = hist1;
|
||||
hist1 = sample;
|
||||
}
|
||||
|
||||
stream->adpcm_history1_32 = hist1;
|
||||
stream->adpcm_history2_32 = hist2;
|
||||
}
|
||||
|
|
|
@ -1,281 +1,282 @@
|
|||
#include "coding.h"
|
||||
|
||||
#ifdef VGM_USE_ATRAC9
|
||||
#ifdef __MACOSX__
|
||||
#include <libatrac9/libatrac9.h>
|
||||
#else
|
||||
#include "libatrac9.h"
|
||||
#endif
|
||||
|
||||
/* opaque struct */
|
||||
struct atrac9_codec_data {
|
||||
uint8_t *data_buffer;
|
||||
size_t data_buffer_size;
|
||||
|
||||
sample_t *sample_buffer;
|
||||
size_t samples_filled; /* number of samples in the buffer */
|
||||
size_t samples_used; /* number of samples extracted from the buffer */
|
||||
|
||||
int samples_to_discard;
|
||||
|
||||
atrac9_config config;
|
||||
|
||||
void *handle; /* decoder handle */
|
||||
Atrac9CodecInfo info; /* decoder info */
|
||||
};
|
||||
|
||||
|
||||
atrac9_codec_data *init_atrac9(atrac9_config *cfg) {
|
||||
int status;
|
||||
uint8_t config_data[4];
|
||||
atrac9_codec_data *data = NULL;
|
||||
|
||||
data = calloc(1, sizeof(atrac9_codec_data));
|
||||
if (!data) goto fail;
|
||||
|
||||
data->handle = Atrac9GetHandle();
|
||||
if (!data->handle) goto fail;
|
||||
|
||||
put_32bitBE(config_data, cfg->config_data);
|
||||
status = Atrac9InitDecoder(data->handle, config_data);
|
||||
if (status < 0) goto fail;
|
||||
|
||||
status = Atrac9GetCodecInfo(data->handle, &data->info);
|
||||
if (status < 0) goto fail;
|
||||
//;VGM_LOG("ATRAC9: config=%x, sf-size=%x, sub-frames=%i x %i samples\n", cfg->config_data, data->info.superframeSize, data->info.framesInSuperframe, data->info.frameSamples);
|
||||
|
||||
if (cfg->channels && cfg->channels != data->info.channels) {
|
||||
VGM_LOG("ATRAC9: channels in header %i vs config %i don't match\n", cfg->channels, data->info.channels);
|
||||
goto fail; /* unknown multichannel layout */
|
||||
}
|
||||
|
||||
|
||||
/* must hold at least one superframe and its samples */
|
||||
data->data_buffer_size = data->info.superframeSize;
|
||||
/* extra leeway as Atrac9Decode seems to overread ~2 bytes (doesn't affect decoding though) */
|
||||
data->data_buffer = calloc(sizeof(uint8_t), data->data_buffer_size + 0x10);
|
||||
/* while ATRAC9 uses float internally, Sony's API only return PCM16 */
|
||||
data->sample_buffer = calloc(sizeof(sample_t), data->info.channels * data->info.frameSamples * data->info.framesInSuperframe);
|
||||
|
||||
data->samples_to_discard = cfg->encoder_delay;
|
||||
|
||||
memcpy(&data->config, cfg, sizeof(atrac9_config));
|
||||
|
||||
return data;
|
||||
|
||||
fail:
|
||||
free_atrac9(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void decode_atrac9(VGMSTREAM *vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels) {
|
||||
VGMSTREAMCHANNEL *stream = &vgmstream->ch[0];
|
||||
atrac9_codec_data * data = vgmstream->codec_data;
|
||||
int samples_done = 0;
|
||||
|
||||
|
||||
while (samples_done < samples_to_do) {
|
||||
|
||||
if (data->samples_filled) { /* consume samples */
|
||||
int samples_to_get = data->samples_filled;
|
||||
|
||||
if (data->samples_to_discard) {
|
||||
/* discard samples for looping */
|
||||
if (samples_to_get > data->samples_to_discard)
|
||||
samples_to_get = data->samples_to_discard;
|
||||
data->samples_to_discard -= samples_to_get;
|
||||
}
|
||||
else {
|
||||
/* get max samples and copy */
|
||||
if (samples_to_get > samples_to_do - samples_done)
|
||||
samples_to_get = samples_to_do - samples_done;
|
||||
|
||||
memcpy(outbuf + samples_done*channels,
|
||||
data->sample_buffer + data->samples_used*channels,
|
||||
samples_to_get*channels * sizeof(sample));
|
||||
|
||||
samples_done += samples_to_get;
|
||||
}
|
||||
|
||||
/* mark consumed samples */
|
||||
data->samples_used += samples_to_get;
|
||||
data->samples_filled -= samples_to_get;
|
||||
}
|
||||
else { /* decode data */
|
||||
int iframe, status;
|
||||
int bytes_used = 0;
|
||||
uint8_t *buffer = data->data_buffer;
|
||||
size_t bytes;
|
||||
|
||||
data->samples_used = 0;
|
||||
|
||||
/* ATRAC9 is made of decodable superframes with several sub-frames. AT9 config data gives
|
||||
* superframe size, number of frames and samples (~100-200 bytes and ~256/1024 samples). */
|
||||
|
||||
/* read one raw block (superframe) and advance offsets */
|
||||
bytes = read_streamfile(data->data_buffer,stream->offset, data->info.superframeSize,stream->streamfile);
|
||||
if (bytes != data->data_buffer_size) goto decode_fail;
|
||||
|
||||
stream->offset += bytes;
|
||||
|
||||
/* decode all frames in the superframe block */
|
||||
for (iframe = 0; iframe < data->info.framesInSuperframe; iframe++) {
|
||||
status = Atrac9Decode(data->handle, buffer, data->sample_buffer + data->samples_filled*channels, &bytes_used);
|
||||
if (status < 0) goto decode_fail;
|
||||
|
||||
buffer += bytes_used;
|
||||
data->samples_filled += data->info.frameSamples;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
decode_fail:
|
||||
/* on error just put some 0 samples */
|
||||
VGM_LOG("ATRAC9: decode fail at %x, missing %i samples\n", (uint32_t)stream->offset, (samples_to_do - samples_done));
|
||||
memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * sizeof(sample) * channels);
|
||||
}
|
||||
|
||||
void reset_atrac9(VGMSTREAM *vgmstream) {
|
||||
atrac9_codec_data *data = vgmstream->codec_data;
|
||||
if (!data) return;
|
||||
|
||||
if (!data->handle)
|
||||
goto fail;
|
||||
|
||||
#if 0
|
||||
/* reopen/flush, not needed as superframes decode separatedly and there is no carried state */
|
||||
{
|
||||
int status;
|
||||
uint8_t config_data[4];
|
||||
|
||||
Atrac9ReleaseHandle(data->handle);
|
||||
data->handle = Atrac9GetHandle();
|
||||
if (!data->handle) goto fail;
|
||||
|
||||
put_32bitBE(config_data, data->config.config_data);
|
||||
status = Atrac9InitDecoder(data->handle, config_data);
|
||||
if (status < 0) goto fail;
|
||||
}
|
||||
#endif
|
||||
|
||||
data->samples_used = 0;
|
||||
data->samples_filled = 0;
|
||||
data->samples_to_discard = data->config.encoder_delay;
|
||||
|
||||
return;
|
||||
|
||||
fail:
|
||||
return; /* decode calls should fail... */
|
||||
}
|
||||
|
||||
void seek_atrac9(VGMSTREAM *vgmstream, int32_t num_sample) {
|
||||
atrac9_codec_data *data = vgmstream->codec_data;
|
||||
if (!data) return;
|
||||
|
||||
reset_atrac9(vgmstream);
|
||||
|
||||
/* find closest offset to desired sample, and samples to discard after that offset to reach loop */
|
||||
{
|
||||
int32_t seek_sample = data->config.encoder_delay + num_sample;
|
||||
off_t seek_offset;
|
||||
int32_t seek_discard;
|
||||
int32_t superframe_samples = data->info.frameSamples * data->info.framesInSuperframe;
|
||||
size_t superframe_number, superframe_back;
|
||||
|
||||
superframe_number = (seek_sample / superframe_samples); /* closest */
|
||||
|
||||
/* decoded frames affect each other slightly, so move offset back to make PCM stable
|
||||
* and equivalent to a full discard loop */
|
||||
superframe_back = 1; /* 1 seems enough (even when only 1 subframe in superframe) */
|
||||
if (superframe_back > superframe_number)
|
||||
superframe_back = superframe_number;
|
||||
|
||||
seek_discard = (seek_sample % superframe_samples) + (superframe_back * superframe_samples);
|
||||
seek_offset = (superframe_number - superframe_back) * data->info.superframeSize;
|
||||
|
||||
data->samples_to_discard = seek_discard; /* already includes encoder delay */
|
||||
|
||||
if (vgmstream->loop_ch)
|
||||
vgmstream->loop_ch[0].offset = vgmstream->loop_ch[0].channel_start_offset + seek_offset;
|
||||
}
|
||||
|
||||
#if 0
|
||||
//old full discard loop
|
||||
{
|
||||
data->samples_to_discard = num_sample;
|
||||
data->samples_to_discard += data->config.encoder_delay;
|
||||
|
||||
/* loop offsets are set during decode; force them to stream start so discard works */
|
||||
if (vgmstream->loop_ch)
|
||||
vgmstream->loop_ch[0].offset = vgmstream->loop_ch[0].channel_start_offset;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void free_atrac9(atrac9_codec_data *data) {
|
||||
if (!data) return;
|
||||
|
||||
if (data->handle) Atrac9ReleaseHandle(data->handle);
|
||||
free(data->data_buffer);
|
||||
free(data->sample_buffer);
|
||||
free(data);
|
||||
}
|
||||
|
||||
|
||||
static int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_channels, size_t *out_frame_size, size_t *out_samples_per_frame) {
|
||||
static const int sample_rate_table[16] = {
|
||||
11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000,
|
||||
44100, 48000, 64000, 88200, 96000,128000,176400,192000
|
||||
};
|
||||
static const int samples_power_table[16] = {
|
||||
6, 6, 7, 7, 7, 8, 8, 8,
|
||||
6, 6, 7, 7, 7, 8, 8, 8
|
||||
};
|
||||
static const int channel_table[8] = {
|
||||
1, 2, 2, 6, 8, 4, 0, 0
|
||||
};
|
||||
|
||||
int superframe_size, frames_per_superframe, samples_per_frame, samples_per_superframe;
|
||||
uint32_t sync = (atrac9_config >> 24) & 0xff; /* 8b */
|
||||
uint8_t sample_rate_index = (atrac9_config >> 20) & 0x0f; /* 4b */
|
||||
uint8_t channels_index = (atrac9_config >> 17) & 0x07; /* 3b */
|
||||
/* uint8_t validation bit = (atrac9_config >> 16) & 0x01; */ /* 1b */
|
||||
size_t frame_size = (atrac9_config >> 5) & 0x7FF; /* 11b */
|
||||
size_t superframe_index = (atrac9_config >> 3) & 0x3; /* 2b */
|
||||
/* uint8_t unused = (atrac9_config >> 0) & 0x7);*/ /* 3b */
|
||||
|
||||
superframe_size = ((frame_size+1) << superframe_index);
|
||||
frames_per_superframe = (1 << superframe_index);
|
||||
samples_per_frame = 1 << samples_power_table[sample_rate_index];
|
||||
samples_per_superframe = samples_per_frame * frames_per_superframe;
|
||||
|
||||
if (sync != 0xFE)
|
||||
goto fail;
|
||||
if (out_sample_rate)
|
||||
*out_sample_rate = sample_rate_table[sample_rate_index];
|
||||
if (out_channels)
|
||||
*out_channels = channel_table[channels_index];
|
||||
if (out_frame_size)
|
||||
*out_frame_size = superframe_size;
|
||||
if (out_samples_per_frame)
|
||||
*out_samples_per_frame = samples_per_superframe;
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t atrac9_bytes_to_samples(size_t bytes, atrac9_codec_data *data) {
|
||||
return bytes / data->info.superframeSize * (data->info.frameSamples * data->info.framesInSuperframe);
|
||||
}
|
||||
|
||||
size_t atrac9_bytes_to_samples_cfg(size_t bytes, uint32_t atrac9_config) {
|
||||
size_t frame_size, samples_per_frame;
|
||||
if (!atrac9_parse_config(atrac9_config, NULL, NULL, &frame_size, &samples_per_frame))
|
||||
return 0;
|
||||
return bytes / frame_size * samples_per_frame;
|
||||
}
|
||||
#endif
|
||||
#include "coding.h"
|
||||
|
||||
#ifdef VGM_USE_ATRAC9
|
||||
#ifdef __MACOSX__
|
||||
#include <libatrac9/libatrac9.h>
|
||||
#else
|
||||
#include "libatrac9.h"
|
||||
#endif
|
||||
|
||||
|
||||
/* opaque struct */
|
||||
struct atrac9_codec_data {
|
||||
uint8_t *data_buffer;
|
||||
size_t data_buffer_size;
|
||||
|
||||
sample_t *sample_buffer;
|
||||
size_t samples_filled; /* number of samples in the buffer */
|
||||
size_t samples_used; /* number of samples extracted from the buffer */
|
||||
|
||||
int samples_to_discard;
|
||||
|
||||
atrac9_config config;
|
||||
|
||||
void *handle; /* decoder handle */
|
||||
Atrac9CodecInfo info; /* decoder info */
|
||||
};
|
||||
|
||||
|
||||
atrac9_codec_data *init_atrac9(atrac9_config *cfg) {
|
||||
int status;
|
||||
uint8_t config_data[4];
|
||||
atrac9_codec_data *data = NULL;
|
||||
|
||||
data = calloc(1, sizeof(atrac9_codec_data));
|
||||
if (!data) goto fail;
|
||||
|
||||
data->handle = Atrac9GetHandle();
|
||||
if (!data->handle) goto fail;
|
||||
|
||||
put_32bitBE(config_data, cfg->config_data);
|
||||
status = Atrac9InitDecoder(data->handle, config_data);
|
||||
if (status < 0) goto fail;
|
||||
|
||||
status = Atrac9GetCodecInfo(data->handle, &data->info);
|
||||
if (status < 0) goto fail;
|
||||
//;VGM_LOG("ATRAC9: config=%x, sf-size=%x, sub-frames=%i x %i samples\n", cfg->config_data, data->info.superframeSize, data->info.framesInSuperframe, data->info.frameSamples);
|
||||
|
||||
if (cfg->channels && cfg->channels != data->info.channels) {
|
||||
VGM_LOG("ATRAC9: channels in header %i vs config %i don't match\n", cfg->channels, data->info.channels);
|
||||
goto fail; /* unknown multichannel layout */
|
||||
}
|
||||
|
||||
|
||||
/* must hold at least one superframe and its samples */
|
||||
data->data_buffer_size = data->info.superframeSize;
|
||||
/* extra leeway as Atrac9Decode seems to overread ~2 bytes (doesn't affect decoding though) */
|
||||
data->data_buffer = calloc(sizeof(uint8_t), data->data_buffer_size + 0x10);
|
||||
/* while ATRAC9 uses float internally, Sony's API only return PCM16 */
|
||||
data->sample_buffer = calloc(sizeof(sample_t), data->info.channels * data->info.frameSamples * data->info.framesInSuperframe);
|
||||
|
||||
data->samples_to_discard = cfg->encoder_delay;
|
||||
|
||||
memcpy(&data->config, cfg, sizeof(atrac9_config));
|
||||
|
||||
return data;
|
||||
|
||||
fail:
|
||||
free_atrac9(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void decode_atrac9(VGMSTREAM *vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels) {
|
||||
VGMSTREAMCHANNEL *stream = &vgmstream->ch[0];
|
||||
atrac9_codec_data * data = vgmstream->codec_data;
|
||||
int samples_done = 0;
|
||||
|
||||
|
||||
while (samples_done < samples_to_do) {
|
||||
|
||||
if (data->samples_filled) { /* consume samples */
|
||||
int samples_to_get = data->samples_filled;
|
||||
|
||||
if (data->samples_to_discard) {
|
||||
/* discard samples for looping */
|
||||
if (samples_to_get > data->samples_to_discard)
|
||||
samples_to_get = data->samples_to_discard;
|
||||
data->samples_to_discard -= samples_to_get;
|
||||
}
|
||||
else {
|
||||
/* get max samples and copy */
|
||||
if (samples_to_get > samples_to_do - samples_done)
|
||||
samples_to_get = samples_to_do - samples_done;
|
||||
|
||||
memcpy(outbuf + samples_done*channels,
|
||||
data->sample_buffer + data->samples_used*channels,
|
||||
samples_to_get*channels * sizeof(sample));
|
||||
|
||||
samples_done += samples_to_get;
|
||||
}
|
||||
|
||||
/* mark consumed samples */
|
||||
data->samples_used += samples_to_get;
|
||||
data->samples_filled -= samples_to_get;
|
||||
}
|
||||
else { /* decode data */
|
||||
int iframe, status;
|
||||
int bytes_used = 0;
|
||||
uint8_t *buffer = data->data_buffer;
|
||||
size_t bytes;
|
||||
|
||||
data->samples_used = 0;
|
||||
|
||||
/* ATRAC9 is made of decodable superframes with several sub-frames. AT9 config data gives
|
||||
* superframe size, number of frames and samples (~100-200 bytes and ~256/1024 samples). */
|
||||
|
||||
/* read one raw block (superframe) and advance offsets */
|
||||
bytes = read_streamfile(data->data_buffer,stream->offset, data->info.superframeSize,stream->streamfile);
|
||||
if (bytes != data->data_buffer_size) goto decode_fail;
|
||||
|
||||
stream->offset += bytes;
|
||||
|
||||
/* decode all frames in the superframe block */
|
||||
for (iframe = 0; iframe < data->info.framesInSuperframe; iframe++) {
|
||||
status = Atrac9Decode(data->handle, buffer, data->sample_buffer + data->samples_filled*channels, &bytes_used);
|
||||
if (status < 0) goto decode_fail;
|
||||
|
||||
buffer += bytes_used;
|
||||
data->samples_filled += data->info.frameSamples;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
decode_fail:
|
||||
/* on error just put some 0 samples */
|
||||
VGM_LOG("ATRAC9: decode fail at %x, missing %i samples\n", (uint32_t)stream->offset, (samples_to_do - samples_done));
|
||||
memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * sizeof(sample) * channels);
|
||||
}
|
||||
|
||||
void reset_atrac9(VGMSTREAM *vgmstream) {
|
||||
atrac9_codec_data *data = vgmstream->codec_data;
|
||||
if (!data) return;
|
||||
|
||||
if (!data->handle)
|
||||
goto fail;
|
||||
|
||||
#if 0
|
||||
/* reopen/flush, not needed as superframes decode separatedly and there is no carried state */
|
||||
{
|
||||
int status;
|
||||
uint8_t config_data[4];
|
||||
|
||||
Atrac9ReleaseHandle(data->handle);
|
||||
data->handle = Atrac9GetHandle();
|
||||
if (!data->handle) goto fail;
|
||||
|
||||
put_32bitBE(config_data, data->config.config_data);
|
||||
status = Atrac9InitDecoder(data->handle, config_data);
|
||||
if (status < 0) goto fail;
|
||||
}
|
||||
#endif
|
||||
|
||||
data->samples_used = 0;
|
||||
data->samples_filled = 0;
|
||||
data->samples_to_discard = data->config.encoder_delay;
|
||||
|
||||
return;
|
||||
|
||||
fail:
|
||||
return; /* decode calls should fail... */
|
||||
}
|
||||
|
||||
void seek_atrac9(VGMSTREAM *vgmstream, int32_t num_sample) {
|
||||
atrac9_codec_data *data = vgmstream->codec_data;
|
||||
if (!data) return;
|
||||
|
||||
reset_atrac9(vgmstream);
|
||||
|
||||
/* find closest offset to desired sample, and samples to discard after that offset to reach loop */
|
||||
{
|
||||
int32_t seek_sample = data->config.encoder_delay + num_sample;
|
||||
off_t seek_offset;
|
||||
int32_t seek_discard;
|
||||
int32_t superframe_samples = data->info.frameSamples * data->info.framesInSuperframe;
|
||||
size_t superframe_number, superframe_back;
|
||||
|
||||
superframe_number = (seek_sample / superframe_samples); /* closest */
|
||||
|
||||
/* decoded frames affect each other slightly, so move offset back to make PCM stable
|
||||
* and equivalent to a full discard loop */
|
||||
superframe_back = 1; /* 1 seems enough (even when only 1 subframe in superframe) */
|
||||
if (superframe_back > superframe_number)
|
||||
superframe_back = superframe_number;
|
||||
|
||||
seek_discard = (seek_sample % superframe_samples) + (superframe_back * superframe_samples);
|
||||
seek_offset = (superframe_number - superframe_back) * data->info.superframeSize;
|
||||
|
||||
data->samples_to_discard = seek_discard; /* already includes encoder delay */
|
||||
|
||||
if (vgmstream->loop_ch)
|
||||
vgmstream->loop_ch[0].offset = vgmstream->loop_ch[0].channel_start_offset + seek_offset;
|
||||
}
|
||||
|
||||
#if 0
|
||||
//old full discard loop
|
||||
{
|
||||
data->samples_to_discard = num_sample;
|
||||
data->samples_to_discard += data->config.encoder_delay;
|
||||
|
||||
/* loop offsets are set during decode; force them to stream start so discard works */
|
||||
if (vgmstream->loop_ch)
|
||||
vgmstream->loop_ch[0].offset = vgmstream->loop_ch[0].channel_start_offset;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void free_atrac9(atrac9_codec_data *data) {
|
||||
if (!data) return;
|
||||
|
||||
if (data->handle) Atrac9ReleaseHandle(data->handle);
|
||||
free(data->data_buffer);
|
||||
free(data->sample_buffer);
|
||||
free(data);
|
||||
}
|
||||
|
||||
|
||||
static int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_channels, size_t *out_frame_size, size_t *out_samples_per_frame) {
|
||||
static const int sample_rate_table[16] = {
|
||||
11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000,
|
||||
44100, 48000, 64000, 88200, 96000,128000,176400,192000
|
||||
};
|
||||
static const int samples_power_table[16] = {
|
||||
6, 6, 7, 7, 7, 8, 8, 8,
|
||||
6, 6, 7, 7, 7, 8, 8, 8
|
||||
};
|
||||
static const int channel_table[8] = {
|
||||
1, 2, 2, 6, 8, 4, 0, 0
|
||||
};
|
||||
|
||||
int superframe_size, frames_per_superframe, samples_per_frame, samples_per_superframe;
|
||||
uint32_t sync = (atrac9_config >> 24) & 0xff; /* 8b */
|
||||
uint8_t sample_rate_index = (atrac9_config >> 20) & 0x0f; /* 4b */
|
||||
uint8_t channels_index = (atrac9_config >> 17) & 0x07; /* 3b */
|
||||
/* uint8_t validation bit = (atrac9_config >> 16) & 0x01; */ /* 1b */
|
||||
size_t frame_size = (atrac9_config >> 5) & 0x7FF; /* 11b */
|
||||
size_t superframe_index = (atrac9_config >> 3) & 0x3; /* 2b */
|
||||
/* uint8_t unused = (atrac9_config >> 0) & 0x7);*/ /* 3b */
|
||||
|
||||
superframe_size = ((frame_size+1) << superframe_index);
|
||||
frames_per_superframe = (1 << superframe_index);
|
||||
samples_per_frame = 1 << samples_power_table[sample_rate_index];
|
||||
samples_per_superframe = samples_per_frame * frames_per_superframe;
|
||||
|
||||
if (sync != 0xFE)
|
||||
goto fail;
|
||||
if (out_sample_rate)
|
||||
*out_sample_rate = sample_rate_table[sample_rate_index];
|
||||
if (out_channels)
|
||||
*out_channels = channel_table[channels_index];
|
||||
if (out_frame_size)
|
||||
*out_frame_size = superframe_size;
|
||||
if (out_samples_per_frame)
|
||||
*out_samples_per_frame = samples_per_superframe;
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t atrac9_bytes_to_samples(size_t bytes, atrac9_codec_data *data) {
|
||||
return bytes / data->info.superframeSize * (data->info.frameSamples * data->info.framesInSuperframe);
|
||||
}
|
||||
|
||||
size_t atrac9_bytes_to_samples_cfg(size_t bytes, uint32_t atrac9_config) {
|
||||
size_t frame_size, samples_per_frame;
|
||||
if (!atrac9_parse_config(atrac9_config, NULL, NULL, &frame_size, &samples_per_frame))
|
||||
return 0;
|
||||
return bytes / frame_size * samples_per_frame;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -1,53 +1,53 @@
|
|||
#include "coding.h"
|
||||
|
||||
|
||||
static const int dsa_coefs[16] = {
|
||||
0x0, 0x1999, 0x3333, 0x4CCC,
|
||||
0x6666, 0x8000, 0x9999, 0xB333,
|
||||
0xCCCC, 0xE666, 0x10000, 0x11999,
|
||||
0x13333, 0x18000, 0x1CCCC, 0x21999
|
||||
};
|
||||
|
||||
/* Decodes Ocean DSA ADPCM codec from Last Rites (PC).
|
||||
* Reverse engineered from daemon1's reverse engineering. */
|
||||
void decode_dsa(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
|
||||
uint8_t frame[0x08] = {0};
|
||||
off_t frame_offset;
|
||||
int i, frames_in, sample_count = 0;
|
||||
size_t bytes_per_frame, samples_per_frame;
|
||||
int index, shift, coef;
|
||||
int32_t hist1 = stream->adpcm_history1_32;
|
||||
|
||||
|
||||
/* external interleave (fixed size), mono */
|
||||
bytes_per_frame = 0x08;
|
||||
samples_per_frame = (bytes_per_frame - 0x01) * 2;
|
||||
frames_in = first_sample / samples_per_frame;
|
||||
first_sample = first_sample % samples_per_frame; /* for flat layout */
|
||||
|
||||
/* parse frame header */
|
||||
frame_offset = stream->offset + bytes_per_frame * frames_in;
|
||||
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
|
||||
index = ((frame[0] >> 0) & 0xf);
|
||||
shift = 12 - ((frame[0] >> 4) & 0xf);
|
||||
coef = dsa_coefs[index];
|
||||
|
||||
/* decode nibbles */
|
||||
for (i = first_sample; i < first_sample + samples_to_do; i++) {
|
||||
uint8_t nibbles = frame[0x01 + i/2];
|
||||
int32_t sample;
|
||||
|
||||
sample = i&1 ? /* high nibble first */
|
||||
(nibbles >> 0) & 0xf :
|
||||
(nibbles >> 4) & 0xf;
|
||||
sample = ((int16_t)(sample << 12) >> shift); /* 16b sign extend + scale */
|
||||
sample = sample + ((hist1 * coef) >> 16);
|
||||
|
||||
outbuf[sample_count] = (sample_t)(sample << 2);
|
||||
sample_count += channelspacing;
|
||||
|
||||
hist1 = sample;
|
||||
}
|
||||
|
||||
stream->adpcm_history1_32 = hist1;
|
||||
}
|
||||
#include "coding.h"
|
||||
|
||||
|
||||
static const int dsa_coefs[16] = {
|
||||
0x0, 0x1999, 0x3333, 0x4CCC,
|
||||
0x6666, 0x8000, 0x9999, 0xB333,
|
||||
0xCCCC, 0xE666, 0x10000, 0x11999,
|
||||
0x13333, 0x18000, 0x1CCCC, 0x21999
|
||||
};
|
||||
|
||||
/* Decodes Ocean DSA ADPCM codec from Last Rites (PC).
|
||||
* Reverse engineered from daemon1's reverse engineering. */
|
||||
void decode_dsa(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
|
||||
uint8_t frame[0x08] = {0};
|
||||
off_t frame_offset;
|
||||
int i, frames_in, sample_count = 0;
|
||||
size_t bytes_per_frame, samples_per_frame;
|
||||
int index, shift, coef;
|
||||
int32_t hist1 = stream->adpcm_history1_32;
|
||||
|
||||
|
||||
/* external interleave (fixed size), mono */
|
||||
bytes_per_frame = 0x08;
|
||||
samples_per_frame = (bytes_per_frame - 0x01) * 2;
|
||||
frames_in = first_sample / samples_per_frame;
|
||||
first_sample = first_sample % samples_per_frame; /* for flat layout */
|
||||
|
||||
/* parse frame header */
|
||||
frame_offset = stream->offset + bytes_per_frame * frames_in;
|
||||
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
|
||||
index = ((frame[0] >> 0) & 0xf);
|
||||
shift = 12 - ((frame[0] >> 4) & 0xf);
|
||||
coef = dsa_coefs[index];
|
||||
|
||||
/* decode nibbles */
|
||||
for (i = first_sample; i < first_sample + samples_to_do; i++) {
|
||||
uint8_t nibbles = frame[0x01 + i/2];
|
||||
int32_t sample;
|
||||
|
||||
sample = i&1 ? /* high nibble first */
|
||||
(nibbles >> 0) & 0xf :
|
||||
(nibbles >> 4) & 0xf;
|
||||
sample = ((int16_t)(sample << 12) >> shift); /* 16b sign extend + scale */
|
||||
sample = sample + ((hist1 * coef) >> 16);
|
||||
|
||||
outbuf[sample_count] = (sample_t)(sample << 2);
|
||||
sample_count += channelspacing;
|
||||
|
||||
hist1 = sample;
|
||||
}
|
||||
|
||||
stream->adpcm_history1_32 = hist1;
|
||||
}
|
||||
|
|
|
@ -1,78 +1,78 @@
|
|||
#include "coding.h"
|
||||
|
||||
|
||||
/* tweaked XA/PSX coefs << 6 */
|
||||
static const int8_t fadpcm_coefs[8][2] = {
|
||||
{ 0, 0 },
|
||||
{ 60, 0 },
|
||||
{ 122, 60 },
|
||||
{ 115, 52 },
|
||||
{ 98, 55 },
|
||||
/* rest is 0s */
|
||||
};
|
||||
|
||||
/* FMOD's FADPCM, basically XA/PSX ADPCM with a fancy header layout.
|
||||
* Code/layout could be simplified but tries to emulate FMOD's code.
|
||||
* Algorithm and tables debugged from their PC DLLs (byte-accurate). */
|
||||
void decode_fadpcm(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
|
||||
uint8_t frame[0x8c] = {0};
|
||||
off_t frame_offset;
|
||||
int i, j, k, frames_in, sample_count = 0, samples_done = 0;
|
||||
size_t bytes_per_frame, samples_per_frame;
|
||||
uint32_t coefs, shifts;
|
||||
int32_t hist1; //= stream->adpcm_history1_32;
|
||||
int32_t hist2; //= stream->adpcm_history2_32;
|
||||
|
||||
/* external interleave (fixed size), mono */
|
||||
bytes_per_frame = 0x8c;
|
||||
samples_per_frame = (bytes_per_frame - 0xc) * 2;
|
||||
frames_in = first_sample / samples_per_frame;
|
||||
first_sample = first_sample % samples_per_frame;
|
||||
|
||||
/* parse 0xc header (header samples are not written to outbuf) */
|
||||
frame_offset = stream->offset + bytes_per_frame * frames_in;
|
||||
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
|
||||
coefs = get_u32le(frame + 0x00);
|
||||
shifts = get_u32le(frame + 0x04);
|
||||
hist1 = get_s16le(frame + 0x08);
|
||||
hist2 = get_s16le(frame + 0x0a);
|
||||
|
||||
|
||||
/* decode nibbles, grouped in 8 sets of 0x10 * 0x04 * 2 */
|
||||
for (i = 0; i < 8; i++) {
|
||||
int index, shift, coef1, coef2;
|
||||
|
||||
/* each set has its own coefs/shifts (indexes > 7 are repeat, ex. 0x9 is 0x2) */
|
||||
index = ((coefs >> i*4) & 0x0f) % 0x07;
|
||||
shift = (shifts >> i*4) & 0x0f;
|
||||
|
||||
coef1 = fadpcm_coefs[index][0];
|
||||
coef2 = fadpcm_coefs[index][1];
|
||||
shift = 22 - shift; /* pre-adjust for 32b sign extend */
|
||||
|
||||
for (j = 0; j < 4; j++) {
|
||||
uint32_t nibbles = get_u32le(frame + 0x0c + 0x10*i + 0x04*j);
|
||||
|
||||
for (k = 0; k < 8; k++) {
|
||||
int32_t sample;
|
||||
|
||||
sample = (nibbles >> k*4) & 0x0f;
|
||||
sample = (sample << 28) >> shift; /* 32b sign extend + scale */
|
||||
sample = (sample - hist2*coef2 + hist1*coef1) >> 6;
|
||||
sample = clamp16(sample);
|
||||
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = sample;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
hist2 = hist1;
|
||||
hist1 = sample;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//stream->adpcm_history1_32 = hist1;
|
||||
//stream->adpcm_history2_32 = hist2;
|
||||
}
|
||||
#include "coding.h"
|
||||
|
||||
|
||||
/* tweaked XA/PSX coefs << 6 */
|
||||
static const int8_t fadpcm_coefs[8][2] = {
|
||||
{ 0, 0 },
|
||||
{ 60, 0 },
|
||||
{ 122, 60 },
|
||||
{ 115, 52 },
|
||||
{ 98, 55 },
|
||||
/* rest is 0s */
|
||||
};
|
||||
|
||||
/* FMOD's FADPCM, basically XA/PSX ADPCM with a fancy header layout.
|
||||
* Code/layout could be simplified but tries to emulate FMOD's code.
|
||||
* Algorithm and tables debugged from their PC DLLs (byte-accurate). */
|
||||
void decode_fadpcm(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
|
||||
uint8_t frame[0x8c] = {0};
|
||||
off_t frame_offset;
|
||||
int i, j, k, frames_in, sample_count = 0, samples_done = 0;
|
||||
size_t bytes_per_frame, samples_per_frame;
|
||||
uint32_t coefs, shifts;
|
||||
int32_t hist1; //= stream->adpcm_history1_32;
|
||||
int32_t hist2; //= stream->adpcm_history2_32;
|
||||
|
||||
/* external interleave (fixed size), mono */
|
||||
bytes_per_frame = 0x8c;
|
||||
samples_per_frame = (bytes_per_frame - 0xc) * 2;
|
||||
frames_in = first_sample / samples_per_frame;
|
||||
first_sample = first_sample % samples_per_frame;
|
||||
|
||||
/* parse 0xc header (header samples are not written to outbuf) */
|
||||
frame_offset = stream->offset + bytes_per_frame * frames_in;
|
||||
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
|
||||
coefs = get_u32le(frame + 0x00);
|
||||
shifts = get_u32le(frame + 0x04);
|
||||
hist1 = get_s16le(frame + 0x08);
|
||||
hist2 = get_s16le(frame + 0x0a);
|
||||
|
||||
|
||||
/* decode nibbles, grouped in 8 sets of 0x10 * 0x04 * 2 */
|
||||
for (i = 0; i < 8; i++) {
|
||||
int index, shift, coef1, coef2;
|
||||
|
||||
/* each set has its own coefs/shifts (indexes > 7 are repeat, ex. 0x9 is 0x2) */
|
||||
index = ((coefs >> i*4) & 0x0f) % 0x07;
|
||||
shift = (shifts >> i*4) & 0x0f;
|
||||
|
||||
coef1 = fadpcm_coefs[index][0];
|
||||
coef2 = fadpcm_coefs[index][1];
|
||||
shift = 22 - shift; /* pre-adjust for 32b sign extend */
|
||||
|
||||
for (j = 0; j < 4; j++) {
|
||||
uint32_t nibbles = get_u32le(frame + 0x0c + 0x10*i + 0x04*j);
|
||||
|
||||
for (k = 0; k < 8; k++) {
|
||||
int32_t sample;
|
||||
|
||||
sample = (nibbles >> k*4) & 0x0f;
|
||||
sample = (sample << 28) >> shift; /* 32b sign extend + scale */
|
||||
sample = (sample - hist2*coef2 + hist1*coef1) >> 6;
|
||||
sample = clamp16(sample);
|
||||
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = sample;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
hist2 = hist1;
|
||||
hist1 = sample;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//stream->adpcm_history1_32 = hist1;
|
||||
//stream->adpcm_history2_32 = hist2;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,190 +1,190 @@
|
|||
#include "coding.h"
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
|
||||
static int ffmpeg_make_riff_atrac3(uint8_t * buf, size_t buf_size, size_t sample_count, size_t data_size, int channels, int sample_rate, int block_align, int joint_stereo, int encoder_delay) {
|
||||
uint16_t codec_ATRAC3 = 0x0270;
|
||||
size_t riff_size = 4+4+ 4 + 0x28 + 0x10 + 4+4;
|
||||
|
||||
if (buf_size < riff_size)
|
||||
return -1;
|
||||
|
||||
memcpy(buf+0x00, "RIFF", 4);
|
||||
put_32bitLE(buf+0x04, (int32_t)(riff_size-4-4 + data_size)); /* riff size */
|
||||
memcpy(buf+0x08, "WAVE", 4);
|
||||
|
||||
memcpy(buf+0x0c, "fmt ", 4);
|
||||
put_32bitLE(buf+0x10, 0x20);/*fmt size*/
|
||||
put_16bitLE(buf+0x14, codec_ATRAC3);
|
||||
put_16bitLE(buf+0x16, channels);
|
||||
put_32bitLE(buf+0x18, sample_rate);
|
||||
put_32bitLE(buf+0x1c, sample_rate*channels / sizeof(sample)); /* average bytes per second (wrong) */
|
||||
put_32bitLE(buf+0x20, (int16_t)(block_align)); /* block align */
|
||||
|
||||
put_16bitLE(buf+0x24, 0x0e); /* extra data size */
|
||||
put_16bitLE(buf+0x26, 1); /* unknown, always 1 */
|
||||
put_16bitLE(buf+0x28, 0x0800 * channels); /* unknown (some size? 0x1000=2ch, 0x0800=1ch) */
|
||||
put_16bitLE(buf+0x2a, 0); /* unknown, always 0 */
|
||||
put_16bitLE(buf+0x2c, joint_stereo ? 0x0001 : 0x0000);
|
||||
put_16bitLE(buf+0x2e, joint_stereo ? 0x0001 : 0x0000); /* repeated? */
|
||||
put_16bitLE(buf+0x30, 1); /* unknown, always 1 (frame_factor?) */
|
||||
put_16bitLE(buf+0x32, 0); /* unknown, always 0 */
|
||||
|
||||
memcpy(buf+0x34, "fact", 4);
|
||||
put_32bitLE(buf+0x38, 0x8); /* fact size */
|
||||
put_32bitLE(buf+0x3c, sample_count);
|
||||
put_32bitLE(buf+0x40, encoder_delay);
|
||||
|
||||
memcpy(buf+0x44, "data", 4);
|
||||
put_32bitLE(buf+0x48, data_size); /* data size */
|
||||
|
||||
return riff_size;
|
||||
}
|
||||
|
||||
ffmpeg_codec_data * init_ffmpeg_atrac3_raw(STREAMFILE *sf, off_t offset, size_t data_size, int sample_count, int channels, int sample_rate, int block_align, int encoder_delay) {
|
||||
ffmpeg_codec_data *ffmpeg_data = NULL;
|
||||
uint8_t buf[0x100];
|
||||
int bytes;
|
||||
int joint_stereo = (block_align == 0x60*channels) && channels > 1; /* only lowest block size does joint stereo */
|
||||
int is_at3 = 1; /* could detect using block size */
|
||||
|
||||
/* create fake header + init ffmpeg + apply fixes to FFmpeg decoding */
|
||||
bytes = ffmpeg_make_riff_atrac3(buf,sizeof(buf), sample_count, data_size, channels, sample_rate, block_align, joint_stereo, encoder_delay);
|
||||
ffmpeg_data = init_ffmpeg_header_offset(sf, buf,bytes, offset,data_size);
|
||||
if (!ffmpeg_data) goto fail;
|
||||
|
||||
/* unlike with RIFF ATRAC3 we don't set implicit delay, as raw ATRAC3 headers often give loop/samples
|
||||
* in offsets, so calcs are expected to be handled externally (presumably the game would call raw decoding API
|
||||
* and any skips would be handled manually) */
|
||||
|
||||
/* FFmpeg reads this but just in case they fiddle with it in the future */
|
||||
ffmpeg_data->totalSamples = sample_count;
|
||||
|
||||
/* encoder delay: encoder introduces some garbage (not always silent) samples to skip at the beginning (at least 1 frame)
|
||||
* FFmpeg doesn't set this, and even if it ever does it's probably better to force it for the implicit skip. */
|
||||
ffmpeg_set_skip_samples(ffmpeg_data, encoder_delay);
|
||||
|
||||
/* invert ATRAC3: waveform is inverted vs official tools (not noticeable but for accuracy) */
|
||||
if (is_at3) {
|
||||
ffmpeg_data->invert_floats_set = 1;
|
||||
}
|
||||
|
||||
return ffmpeg_data;
|
||||
fail:
|
||||
free_ffmpeg(ffmpeg_data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* init ATRAC3/plus while adding some fixes */
|
||||
ffmpeg_codec_data * init_ffmpeg_atrac3_riff(STREAMFILE *sf, off_t offset, int* out_samples) {
|
||||
ffmpeg_codec_data *ffmpeg_data = NULL;
|
||||
int is_at3 = 0, is_at3p = 0, codec;
|
||||
size_t riff_size;
|
||||
int fact_samples, skip_samples, implicit_skip;
|
||||
off_t fact_offset = 0;
|
||||
size_t fact_size = 0;
|
||||
|
||||
|
||||
/* some simplified checks just in case */
|
||||
if (read_32bitBE(offset + 0x00,sf) != 0x52494646) /* "RIFF" */
|
||||
goto fail;
|
||||
|
||||
riff_size = read_32bitLE(offset + 0x04,sf) + 0x08;
|
||||
codec = (uint16_t)read_16bitLE(offset + 0x14, sf);
|
||||
switch(codec) {
|
||||
case 0x0270: is_at3 = 1; break;
|
||||
case 0xFFFE: is_at3p = 1; break;
|
||||
default: goto fail;
|
||||
}
|
||||
|
||||
|
||||
/* init ffmpeg + apply fixes to FFmpeg decoding (with these fixes should be
|
||||
* sample-accurate vs official tools, except usual +-1 float-to-pcm conversion) */
|
||||
ffmpeg_data = init_ffmpeg_offset(sf, offset, riff_size);
|
||||
if (!ffmpeg_data) goto fail;
|
||||
|
||||
|
||||
/* well behaved .at3 define "fact" but official tools accept files without it */
|
||||
if (find_chunk_le(sf,0x66616374,offset + 0x0c,0, &fact_offset, &fact_size)) { /* "fact" */
|
||||
if (fact_size == 0x08) { /* early AT3 (mainly PSP games) */
|
||||
fact_samples = read_32bitLE(fact_offset + 0x00, sf);
|
||||
skip_samples = read_32bitLE(fact_offset + 0x04, sf); /* base skip samples */
|
||||
}
|
||||
else if (fact_size == 0x0c) { /* late AT3 (mainly PS3 games and few PSP games) */
|
||||
fact_samples = read_32bitLE(fact_offset + 0x00, sf);
|
||||
/* 0x04: base skip samples, ignored by decoder */
|
||||
skip_samples = read_32bitLE(fact_offset + 0x08, sf); /* skip samples with implicit skip of 184 added */
|
||||
}
|
||||
else {
|
||||
VGM_LOG("ATRAC3: unknown fact size\n");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else {
|
||||
fact_samples = 0; /* tools output 0 samples in this case unless loop end is defined */
|
||||
if (is_at3)
|
||||
skip_samples = 1024; /* 1 frame */
|
||||
else if (is_at3p)
|
||||
skip_samples = 2048; /* 1 frame */
|
||||
else
|
||||
skip_samples = 0;
|
||||
}
|
||||
|
||||
/* implicit skip: official tools skip this even with encoder delay forced to 0. Maybe FFmpeg decodes late,
|
||||
* but when forcing tools to decode all frame samples it always ends a bit before last frame, so maybe it's
|
||||
* really an internal skip, since encoder adds extra frames so fact num_samples + encoder delay + implicit skip
|
||||
* never goes past file. Same for all bitrate/channels, not added to loops. This is probably "decoder delay"
|
||||
* also seen in codecs like MP3 */
|
||||
if (is_at3) {
|
||||
implicit_skip = 69;
|
||||
}
|
||||
else if (is_at3p && fact_size == 0x08) {
|
||||
implicit_skip = 184*2;
|
||||
}
|
||||
else if (is_at3p && fact_size == 0x0c) {
|
||||
implicit_skip = 184; /* first 184 is already added to delay vs field at 0x08 */
|
||||
}
|
||||
else if (is_at3p) {
|
||||
implicit_skip = 184; /* default for unknown sizes */
|
||||
}
|
||||
else {
|
||||
implicit_skip = 0;
|
||||
}
|
||||
|
||||
/* FFmpeg reads this but just in case they fiddle with it in the future */
|
||||
ffmpeg_data->totalSamples = fact_samples;
|
||||
|
||||
/* encoder delay: encoder introduces some garbage (not always silent) samples to skip at the beginning (at least 1 frame)
|
||||
* FFmpeg doesn't set this, and even if it ever does it's probably better to force it for the implicit skip. */
|
||||
ffmpeg_set_skip_samples(ffmpeg_data, skip_samples + implicit_skip);
|
||||
|
||||
/* invert ATRAC3: waveform is inverted vs official tools (not noticeable but for accuracy) */
|
||||
if (is_at3) {
|
||||
ffmpeg_data->invert_floats_set = 1;
|
||||
}
|
||||
|
||||
/* multichannel fix: LFE channel should be reordered on decode (ATRAC3Plus only, only 1/2/6/8ch exist):
|
||||
* - 6ch: FL FR FC BL BR LFE > FL FR FC LFE BL BR
|
||||
* - 8ch: FL FR FC BL BR SL SR LFE > FL FR FC LFE BL BR SL SR */
|
||||
if (is_at3p && ffmpeg_data->channels == 6) {
|
||||
/* LFE BR BL > LFE BL BR > same */
|
||||
int channel_remap[] = { 0, 1, 2, 5, 5, 5, };
|
||||
ffmpeg_set_channel_remapping(ffmpeg_data, channel_remap);
|
||||
}
|
||||
else if (is_at3p && ffmpeg_data->channels == 8) {
|
||||
/* LFE BR SL SR BL > LFE BL SL SR BR > LFE BL BR SR SL > LFE BL BR SL SR > same */
|
||||
int channel_remap[] = { 0, 1, 2, 7, 7, 7, 7, 7};
|
||||
ffmpeg_set_channel_remapping(ffmpeg_data, channel_remap);
|
||||
}
|
||||
|
||||
|
||||
if (out_samples)
|
||||
*out_samples = fact_samples;
|
||||
|
||||
return ffmpeg_data;
|
||||
fail:
|
||||
free_ffmpeg(ffmpeg_data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
#include "coding.h"
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
|
||||
static int ffmpeg_make_riff_atrac3(uint8_t * buf, size_t buf_size, size_t sample_count, size_t data_size, int channels, int sample_rate, int block_align, int joint_stereo, int encoder_delay) {
|
||||
uint16_t codec_ATRAC3 = 0x0270;
|
||||
size_t riff_size = 4+4+ 4 + 0x28 + 0x10 + 4+4;
|
||||
|
||||
if (buf_size < riff_size)
|
||||
return -1;
|
||||
|
||||
memcpy(buf+0x00, "RIFF", 4);
|
||||
put_32bitLE(buf+0x04, (int32_t)(riff_size-4-4 + data_size)); /* riff size */
|
||||
memcpy(buf+0x08, "WAVE", 4);
|
||||
|
||||
memcpy(buf+0x0c, "fmt ", 4);
|
||||
put_32bitLE(buf+0x10, 0x20);/*fmt size*/
|
||||
put_16bitLE(buf+0x14, codec_ATRAC3);
|
||||
put_16bitLE(buf+0x16, channels);
|
||||
put_32bitLE(buf+0x18, sample_rate);
|
||||
put_32bitLE(buf+0x1c, sample_rate*channels / sizeof(sample)); /* average bytes per second (wrong) */
|
||||
put_32bitLE(buf+0x20, (int16_t)(block_align)); /* block align */
|
||||
|
||||
put_16bitLE(buf+0x24, 0x0e); /* extra data size */
|
||||
put_16bitLE(buf+0x26, 1); /* unknown, always 1 */
|
||||
put_16bitLE(buf+0x28, 0x0800 * channels); /* unknown (some size? 0x1000=2ch, 0x0800=1ch) */
|
||||
put_16bitLE(buf+0x2a, 0); /* unknown, always 0 */
|
||||
put_16bitLE(buf+0x2c, joint_stereo ? 0x0001 : 0x0000);
|
||||
put_16bitLE(buf+0x2e, joint_stereo ? 0x0001 : 0x0000); /* repeated? */
|
||||
put_16bitLE(buf+0x30, 1); /* unknown, always 1 (frame_factor?) */
|
||||
put_16bitLE(buf+0x32, 0); /* unknown, always 0 */
|
||||
|
||||
memcpy(buf+0x34, "fact", 4);
|
||||
put_32bitLE(buf+0x38, 0x8); /* fact size */
|
||||
put_32bitLE(buf+0x3c, sample_count);
|
||||
put_32bitLE(buf+0x40, encoder_delay);
|
||||
|
||||
memcpy(buf+0x44, "data", 4);
|
||||
put_32bitLE(buf+0x48, data_size); /* data size */
|
||||
|
||||
return riff_size;
|
||||
}
|
||||
|
||||
ffmpeg_codec_data * init_ffmpeg_atrac3_raw(STREAMFILE *sf, off_t offset, size_t data_size, int sample_count, int channels, int sample_rate, int block_align, int encoder_delay) {
|
||||
ffmpeg_codec_data *ffmpeg_data = NULL;
|
||||
uint8_t buf[0x100];
|
||||
int bytes;
|
||||
int joint_stereo = (block_align == 0x60*channels) && channels > 1; /* only lowest block size does joint stereo */
|
||||
int is_at3 = 1; /* could detect using block size */
|
||||
|
||||
/* create fake header + init ffmpeg + apply fixes to FFmpeg decoding */
|
||||
bytes = ffmpeg_make_riff_atrac3(buf,sizeof(buf), sample_count, data_size, channels, sample_rate, block_align, joint_stereo, encoder_delay);
|
||||
ffmpeg_data = init_ffmpeg_header_offset(sf, buf,bytes, offset,data_size);
|
||||
if (!ffmpeg_data) goto fail;
|
||||
|
||||
/* unlike with RIFF ATRAC3 we don't set implicit delay, as raw ATRAC3 headers often give loop/samples
|
||||
* in offsets, so calcs are expected to be handled externally (presumably the game would call raw decoding API
|
||||
* and any skips would be handled manually) */
|
||||
|
||||
/* FFmpeg reads this but just in case they fiddle with it in the future */
|
||||
ffmpeg_data->totalSamples = sample_count;
|
||||
|
||||
/* encoder delay: encoder introduces some garbage (not always silent) samples to skip at the beginning (at least 1 frame)
|
||||
* FFmpeg doesn't set this, and even if it ever does it's probably better to force it for the implicit skip. */
|
||||
ffmpeg_set_skip_samples(ffmpeg_data, encoder_delay);
|
||||
|
||||
/* invert ATRAC3: waveform is inverted vs official tools (not noticeable but for accuracy) */
|
||||
if (is_at3) {
|
||||
ffmpeg_data->invert_floats_set = 1;
|
||||
}
|
||||
|
||||
return ffmpeg_data;
|
||||
fail:
|
||||
free_ffmpeg(ffmpeg_data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* init ATRAC3/plus while adding some fixes */
|
||||
ffmpeg_codec_data * init_ffmpeg_atrac3_riff(STREAMFILE *sf, off_t offset, int* out_samples) {
|
||||
ffmpeg_codec_data *ffmpeg_data = NULL;
|
||||
int is_at3 = 0, is_at3p = 0, codec;
|
||||
size_t riff_size;
|
||||
int fact_samples, skip_samples, implicit_skip;
|
||||
off_t fact_offset = 0;
|
||||
size_t fact_size = 0;
|
||||
|
||||
|
||||
/* some simplified checks just in case */
|
||||
if (read_32bitBE(offset + 0x00,sf) != 0x52494646) /* "RIFF" */
|
||||
goto fail;
|
||||
|
||||
riff_size = read_32bitLE(offset + 0x04,sf) + 0x08;
|
||||
codec = (uint16_t)read_16bitLE(offset + 0x14, sf);
|
||||
switch(codec) {
|
||||
case 0x0270: is_at3 = 1; break;
|
||||
case 0xFFFE: is_at3p = 1; break;
|
||||
default: goto fail;
|
||||
}
|
||||
|
||||
|
||||
/* init ffmpeg + apply fixes to FFmpeg decoding (with these fixes should be
|
||||
* sample-accurate vs official tools, except usual +-1 float-to-pcm conversion) */
|
||||
ffmpeg_data = init_ffmpeg_offset(sf, offset, riff_size);
|
||||
if (!ffmpeg_data) goto fail;
|
||||
|
||||
|
||||
/* well behaved .at3 define "fact" but official tools accept files without it */
|
||||
if (find_chunk_le(sf,0x66616374,offset + 0x0c,0, &fact_offset, &fact_size)) { /* "fact" */
|
||||
if (fact_size == 0x08) { /* early AT3 (mainly PSP games) */
|
||||
fact_samples = read_32bitLE(fact_offset + 0x00, sf);
|
||||
skip_samples = read_32bitLE(fact_offset + 0x04, sf); /* base skip samples */
|
||||
}
|
||||
else if (fact_size == 0x0c) { /* late AT3 (mainly PS3 games and few PSP games) */
|
||||
fact_samples = read_32bitLE(fact_offset + 0x00, sf);
|
||||
/* 0x04: base skip samples, ignored by decoder */
|
||||
skip_samples = read_32bitLE(fact_offset + 0x08, sf); /* skip samples with implicit skip of 184 added */
|
||||
}
|
||||
else {
|
||||
VGM_LOG("ATRAC3: unknown fact size\n");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else {
|
||||
fact_samples = 0; /* tools output 0 samples in this case unless loop end is defined */
|
||||
if (is_at3)
|
||||
skip_samples = 1024; /* 1 frame */
|
||||
else if (is_at3p)
|
||||
skip_samples = 2048; /* 1 frame */
|
||||
else
|
||||
skip_samples = 0;
|
||||
}
|
||||
|
||||
/* implicit skip: official tools skip this even with encoder delay forced to 0. Maybe FFmpeg decodes late,
|
||||
* but when forcing tools to decode all frame samples it always ends a bit before last frame, so maybe it's
|
||||
* really an internal skip, since encoder adds extra frames so fact num_samples + encoder delay + implicit skip
|
||||
* never goes past file. Same for all bitrate/channels, not added to loops. This is probably "decoder delay"
|
||||
* also seen in codecs like MP3 */
|
||||
if (is_at3) {
|
||||
implicit_skip = 69;
|
||||
}
|
||||
else if (is_at3p && fact_size == 0x08) {
|
||||
implicit_skip = 184*2;
|
||||
}
|
||||
else if (is_at3p && fact_size == 0x0c) {
|
||||
implicit_skip = 184; /* first 184 is already added to delay vs field at 0x08 */
|
||||
}
|
||||
else if (is_at3p) {
|
||||
implicit_skip = 184; /* default for unknown sizes */
|
||||
}
|
||||
else {
|
||||
implicit_skip = 0;
|
||||
}
|
||||
|
||||
/* FFmpeg reads this but just in case they fiddle with it in the future */
|
||||
ffmpeg_data->totalSamples = fact_samples;
|
||||
|
||||
/* encoder delay: encoder introduces some garbage (not always silent) samples to skip at the beginning (at least 1 frame)
|
||||
* FFmpeg doesn't set this, and even if it ever does it's probably better to force it for the implicit skip. */
|
||||
ffmpeg_set_skip_samples(ffmpeg_data, skip_samples + implicit_skip);
|
||||
|
||||
/* invert ATRAC3: waveform is inverted vs official tools (not noticeable but for accuracy) */
|
||||
if (is_at3) {
|
||||
ffmpeg_data->invert_floats_set = 1;
|
||||
}
|
||||
|
||||
/* multichannel fix: LFE channel should be reordered on decode (ATRAC3Plus only, only 1/2/6/8ch exist):
|
||||
* - 6ch: FL FR FC BL BR LFE > FL FR FC LFE BL BR
|
||||
* - 8ch: FL FR FC BL BR SL SR LFE > FL FR FC LFE BL BR SL SR */
|
||||
if (is_at3p && ffmpeg_data->channels == 6) {
|
||||
/* LFE BR BL > LFE BL BR > same */
|
||||
int channel_remap[] = { 0, 1, 2, 5, 5, 5, };
|
||||
ffmpeg_set_channel_remapping(ffmpeg_data, channel_remap);
|
||||
}
|
||||
else if (is_at3p && ffmpeg_data->channels == 8) {
|
||||
/* LFE BR SL SR BL > LFE BL SL SR BR > LFE BL BR SR SL > LFE BL BR SL SR > same */
|
||||
int channel_remap[] = { 0, 1, 2, 7, 7, 7, 7, 7};
|
||||
ffmpeg_set_channel_remapping(ffmpeg_data, channel_remap);
|
||||
}
|
||||
|
||||
|
||||
if (out_samples)
|
||||
*out_samples = fact_samples;
|
||||
|
||||
return ffmpeg_data;
|
||||
fail:
|
||||
free_ffmpeg(ffmpeg_data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,422 +1,422 @@
|
|||
#include "mpeg_decoder.h"
|
||||
|
||||
#ifdef VGM_USE_MPEG
|
||||
|
||||
/* init config and validate per type */
|
||||
int mpeg_custom_setup_init_default(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) {
|
||||
mpeg_frame_info info;
|
||||
|
||||
|
||||
/* get frame info at offset */
|
||||
if ( !mpeg_get_frame_info(streamFile, start_offset, &info))
|
||||
goto fail;
|
||||
switch(info.layer) {
|
||||
case 1: *coding_type = coding_MPEG_layer1; break;
|
||||
case 2: *coding_type = coding_MPEG_layer2; break;
|
||||
case 3: *coding_type = coding_MPEG_layer3; break;
|
||||
default: goto fail;
|
||||
}
|
||||
data->channels_per_frame = info.channels;
|
||||
data->samples_per_frame = info.frame_samples;
|
||||
data->bitrate_per_frame = info.bit_rate;
|
||||
data->sample_rate_per_frame = info.sample_rate;
|
||||
|
||||
|
||||
/* extra checks per type */
|
||||
switch(data->type) {
|
||||
case MPEG_XVAG:
|
||||
if (data->config.chunk_size <= 0 || data->config.interleave <= 0)
|
||||
goto fail; /* needs external fixed size */
|
||||
break;
|
||||
|
||||
case MPEG_FSB:
|
||||
if (data->config.fsb_padding != 0
|
||||
&& data->config.fsb_padding != 2
|
||||
&& data->config.fsb_padding != 4
|
||||
&& data->config.fsb_padding != 16)
|
||||
goto fail; /* aligned to closest 0/2/4/16 bytes */
|
||||
|
||||
/* get find interleave to stream offsets are set up externally */
|
||||
{
|
||||
int current_data_size = info.frame_size;
|
||||
int current_padding = 0;
|
||||
/* FSB padding for Layer III or multichannel Layer II */
|
||||
if ((info.layer == 3 && data->config.fsb_padding) || data->config.fsb_padding == 16) {
|
||||
current_padding = (current_data_size % data->config.fsb_padding)
|
||||
? data->config.fsb_padding - (current_data_size % data->config.fsb_padding)
|
||||
: 0;
|
||||
}
|
||||
|
||||
data->config.interleave = current_data_size + current_padding; /* should be constant for all stream */
|
||||
}
|
||||
break;
|
||||
|
||||
case MPEG_P3D:
|
||||
case MPEG_SCD:
|
||||
if (data->config.interleave <= 0)
|
||||
goto fail; /* needs external fixed size */
|
||||
break;
|
||||
|
||||
case MPEG_LYN:
|
||||
if (data->config.interleave <= 0)
|
||||
goto fail; /* needs external fixed size */
|
||||
data->default_buffer_size = data->config.interleave;
|
||||
//todo simplify/unify XVAG/P3D/SCD/LYN and just feed arbitrary chunks to the decoder
|
||||
break;
|
||||
|
||||
case MPEG_STANDARD:
|
||||
case MPEG_AHX:
|
||||
case MPEG_EA:
|
||||
if (info.channels != data->config.channels)
|
||||
goto fail; /* no multichannel expected */
|
||||
break;
|
||||
|
||||
default:
|
||||
break; /* nothing special needed */
|
||||
}
|
||||
|
||||
|
||||
//todo: test more: this improves the output, but seems formats aren't usually prepared
|
||||
// (and/or the num_samples includes all possible samples in file, so by discarding some it'll reach EOF)
|
||||
|
||||
/* set encoder delay (samples to skip at the beginning of a stream) if needed, which varies with encoder used */
|
||||
switch(data->type) {
|
||||
//case MPEG_AHX: data->skip_samples = 480; break; /* observed default */
|
||||
//case MPEG_P3D: data->skip_samples = info.frame_samples; break; /* matches Radical ADPCM (PC) output */
|
||||
|
||||
/* FSBs (with FMOD DLLs) don't seem to need it. Particularly a few games (all from Wayforward?)
|
||||
* contain audible garbage at the beginning, but it's actually there in-game too */
|
||||
//case MPEG_FSB: data->skip_samples = 0; break;
|
||||
|
||||
case MPEG_XVAG: /* set in header and needed for gapless looping */
|
||||
data->skip_samples = data->config.skip_samples; break;
|
||||
case MPEG_STANDARD:
|
||||
data->skip_samples = data->config.skip_samples; break;
|
||||
case MPEG_EA:
|
||||
/* typical MP2 decoder delay, verified vs sx.exe, also SCHl blocks header takes discard
|
||||
* samples into account (so block_samples+240*2+1 = total frame samples) */
|
||||
if (info.layer == 2) {
|
||||
data->skip_samples = 240*2 + 1;
|
||||
}
|
||||
/* MP3 probably uses 576 + 528+1 but no known games use it */
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
data->samples_to_discard = data->skip_samples;
|
||||
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* writes data to the buffer and moves offsets */
|
||||
int mpeg_custom_parse_frame_default(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) {
|
||||
mpeg_custom_stream *ms = data->streams[num_stream];
|
||||
mpeg_frame_info info;
|
||||
size_t current_data_size = 0;
|
||||
size_t current_padding = 0;
|
||||
size_t current_interleave_pre = 0; /* interleaved data size before current stream */
|
||||
size_t current_interleave_post = 0; /* interleaved data size after current stream */
|
||||
size_t current_interleave = 0; /* interleave in this block (usually this + pre + post = interleave*streams = block) */
|
||||
|
||||
|
||||
/* Get data size to give to the decoder, per stream. Usually 1 frame at a time,
|
||||
* but doesn't really need to be a full frame (decoder would request more data). */
|
||||
switch(data->type) {
|
||||
|
||||
case MPEG_XVAG: /* frames of fixed size (though we could read frame info too) */
|
||||
current_interleave = data->config.interleave; /* big interleave */
|
||||
current_interleave_pre = current_interleave*num_stream;
|
||||
current_interleave_post = current_interleave*(data->streams_size-1) - current_interleave_pre;
|
||||
|
||||
current_data_size = data->config.chunk_size;
|
||||
break;
|
||||
|
||||
case MPEG_FSB: /* frames with padding + interleave */
|
||||
current_interleave = data->config.interleave; /* constant for multi-stream FSbs (1 frame + padding) */
|
||||
current_interleave_pre = current_interleave*num_stream;
|
||||
current_interleave_post = current_interleave*(data->streams_size-1) - current_interleave_pre;
|
||||
|
||||
if (!mpeg_get_frame_info(stream->streamfile, stream->offset + current_interleave_pre, &info))
|
||||
goto fail;
|
||||
current_data_size = info.frame_size;
|
||||
|
||||
/* get FSB padding for Layer III or multichannel Layer II (Layer I isn't supported by FMOD).
|
||||
* Padding sometimes contains garbage like the next frame header so we can't feed it to mpg123 or it gets confused. */
|
||||
if ((info.layer == 3 && data->config.fsb_padding) || data->config.fsb_padding == 16) {
|
||||
current_padding = (current_data_size % data->config.fsb_padding)
|
||||
? data->config.fsb_padding - (current_data_size % data->config.fsb_padding)
|
||||
: 0;
|
||||
|
||||
/* Rare Mafia II (PS3) bug (GP_0701_music multilang only): some frame paddings "4" are incorrect,
|
||||
* calcs give 0xD0+0x00 but need 0xD0+0x04 (unlike all other fsbs, which never do that).
|
||||
* FMOD tools decode fine, so they may be doing special detection too, since even
|
||||
* re-encoding the same file and using the same FSB flags/modes won't trigger the bug. */
|
||||
if (info.layer == 3 && data->config.fsb_padding == 4 && current_data_size == 0xD0) {
|
||||
uint32_t next_header;
|
||||
off_t next_offset;
|
||||
|
||||
next_offset = stream->offset + current_data_size + current_padding;
|
||||
if (current_interleave && ((next_offset - stream->channel_start_offset + current_interleave_pre + current_interleave_post) % current_interleave == 0)) {
|
||||
next_offset += current_interleave_pre + current_interleave_post;
|
||||
}
|
||||
|
||||
next_header = read_32bitBE(next_offset, stream->streamfile);
|
||||
if ((next_header & 0xFFE00000) != 0xFFE00000) { /* doesn't land in a proper frame, fix sizes and hope */
|
||||
VGM_LOG_ONCE("MPEG FSB: stream with wrong padding found\n");
|
||||
current_padding = 0x04;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
VGM_ASSERT(data->streams_size > 1 && current_interleave != current_data_size+current_padding,
|
||||
"MPEG FSB: %i streams with non-constant interleave found @ 0x%08x\n", data->streams_size, (uint32_t)stream->offset);
|
||||
break;
|
||||
|
||||
case MPEG_P3D: /* fixed interleave, not frame-aligned (ie. blocks may end/start in part of a frame) */
|
||||
case MPEG_SCD:
|
||||
case MPEG_LYN:
|
||||
current_interleave = data->config.interleave;
|
||||
|
||||
/* check if current interleave block is short */
|
||||
{
|
||||
off_t block_offset = stream->offset - stream->channel_start_offset;
|
||||
size_t next_block = data->streams_size*data->config.interleave;
|
||||
|
||||
if (data->config.data_size && block_offset + next_block >= data->config.data_size)
|
||||
current_interleave = (data->config.data_size % next_block) / data->streams_size; /* short_interleave*/
|
||||
}
|
||||
|
||||
current_interleave_pre = current_interleave*num_stream;
|
||||
current_interleave_post = current_interleave*(data->streams_size-1) - current_interleave_pre;
|
||||
|
||||
current_data_size = current_interleave;
|
||||
break;
|
||||
|
||||
default: /* standard frames (CBR or VBR) */
|
||||
if ( !mpeg_get_frame_info(stream->streamfile, stream->offset, &info) )
|
||||
goto fail;
|
||||
current_data_size = info.frame_size;
|
||||
break;
|
||||
}
|
||||
if (!current_data_size || current_data_size > ms->buffer_size) {
|
||||
VGM_LOG("MPEG: incorrect data_size 0x%x vs buffer 0x%x\n", current_data_size, ms->buffer_size);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* This assumes all streams' offsets start in the first stream, and advances
|
||||
* the 'full interleaved block' at once, ex:
|
||||
* start at s0=0x00, s1=0x00, interleave=0x40 (block = 0x40*2=0x80)
|
||||
* @0x00 read 0x40 of s0, skip 0x40 of s1 (block of 0x80 done) > new offset = 0x80
|
||||
* @0x00 skip 0x40 of s0, read 0x40 of s1 (block of 0x80 done) > new offset = 0x800
|
||||
*/
|
||||
|
||||
/* read chunk (skipping other interleaves if needed) */
|
||||
ms->bytes_in_buffer = read_streamfile(ms->buffer,stream->offset + current_interleave_pre, current_data_size, stream->streamfile);
|
||||
|
||||
|
||||
/* update offsets and skip other streams */
|
||||
stream->offset += current_data_size + current_padding;
|
||||
|
||||
/* skip rest of block (interleave per stream) once this stream's interleaved data is done, if defined */
|
||||
if (current_interleave && ((stream->offset - stream->channel_start_offset + current_interleave_pre + current_interleave_post) % current_interleave == 0)) {
|
||||
stream->offset += current_interleave_pre + current_interleave_post;
|
||||
}
|
||||
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*****************/
|
||||
/* FRAME HELPERS */
|
||||
/*****************/
|
||||
|
||||
/**
|
||||
* Gets info from a MPEG frame header at offset. Normally you would use mpg123_info but somehow
|
||||
* it's wrong at times (maybe because we use an ancient version) so here we do our thing.
|
||||
*/
|
||||
static int mpeg_get_frame_info_h(uint32_t header, mpeg_frame_info *info) {
|
||||
/* index tables */
|
||||
static const int versions[4] = { /* MPEG 2.5 */ 3, /* reserved */ -1, /* MPEG 2 */ 2, /* MPEG 1 */ 1 };
|
||||
static const int layers[4] = { -1,3,2,1 };
|
||||
static const int bit_rates[5][16] = { /* [version index ][bit rate index] (0=free, -1=bad) */
|
||||
{ 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1 }, /* MPEG1 Layer I */
|
||||
{ 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1 }, /* MPEG1 Layer II */
|
||||
{ 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1 }, /* MPEG1 Layer III */
|
||||
{ 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1 }, /* MPEG2/2.5 Layer I */
|
||||
{ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 }, /* MPEG2/2.5 Layer II/III */
|
||||
};
|
||||
static const int sample_rates[4][4] = { /* [version][sample rate index] */
|
||||
{ 44100, 48000, 32000, -1}, /* MPEG1 */
|
||||
{ 22050, 24000, 16000, -1}, /* MPEG2 */
|
||||
{ 11025, 12000, 8000, -1}, /* MPEG2.5 */
|
||||
};
|
||||
static const int channels[4] = { 2,2,2, 1 }; /* [channel] */
|
||||
static const int frame_samples[3][3] = { /* [version][layer] */
|
||||
{ 384, 1152, 1152 }, /* MPEG1 */
|
||||
{ 384, 1152, 576 }, /* MPEG2 */
|
||||
{ 384, 1152, 576 } /* MPEG2.5 */
|
||||
};
|
||||
|
||||
int idx, padding;
|
||||
|
||||
|
||||
memset(info, 0, sizeof(*info));
|
||||
|
||||
if ((header >> 21) != 0x7FF) /* 31-21: sync */
|
||||
goto fail;
|
||||
|
||||
info->version = versions[(header >> 19) & 0x3]; /* 20,19: version */
|
||||
if (info->version <= 0) goto fail;
|
||||
|
||||
info->layer = layers[(header >> 17) & 0x3]; /* 18,17: layer */
|
||||
if (info->layer <= 0 || info->layer > 3) goto fail;
|
||||
|
||||
//crc = (header >> 16) & 0x1; /* 16: protected by crc? */
|
||||
|
||||
idx = (info->version==1 ? info->layer-1 : (3 + (info->layer==1 ? 0 : 1)));
|
||||
info->bit_rate = bit_rates[idx][(header >> 12) & 0xf]; /* 15-12: bit rate */
|
||||
if (info->bit_rate <= 0) goto fail;
|
||||
|
||||
info->sample_rate = sample_rates[info->version-1][(header >> 10) & 0x3]; /* 11-10: sampling rate */
|
||||
if (info->sample_rate <= 0) goto fail;
|
||||
|
||||
padding = (header >> 9) & 0x1; /* 9: padding? */
|
||||
//private = (header >> 8) & 0x1; /* 8: private bit */
|
||||
|
||||
info->channels = channels[(header >> 6) & 0x3]; /* 7,6: channel mode */
|
||||
|
||||
//js_mode = (header >> 4) & 0x3; /* 5,4: mode extension for joint stereo */
|
||||
//copyright = (header >> 3) & 0x1; /* 3: copyrighted */
|
||||
//original = (header >> 2) & 0x1; /* 2: original */
|
||||
//emphasis = (header >> 0) & 0x3; /* 1,0: emphasis */
|
||||
|
||||
info->frame_samples = frame_samples[info->version-1][info->layer-1];
|
||||
|
||||
/* calculate frame length (from hcs's fsb_mpeg) */
|
||||
switch (info->frame_samples) {
|
||||
case 384: info->frame_size = (12l * info->bit_rate * 1000l / info->sample_rate + padding) * 4; break; /* 384/32 = 12 */
|
||||
case 576: info->frame_size = (72l * info->bit_rate * 1000l / info->sample_rate + padding); break; /* 576/8 = 72 */
|
||||
case 1152: info->frame_size = (144l * info->bit_rate * 1000l / info->sample_rate + padding); break; /* 1152/8 = 144 */
|
||||
default: goto fail;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
int mpeg_get_frame_info(STREAMFILE *sf, off_t offset, mpeg_frame_info *info) {
|
||||
uint32_t header = read_u32be(offset, sf);
|
||||
return mpeg_get_frame_info_h(header, info);
|
||||
}
|
||||
|
||||
size_t mpeg_get_samples(STREAMFILE *sf, off_t start_offset, size_t bytes) {
|
||||
off_t offset = start_offset;
|
||||
off_t max_offset = start_offset + bytes;
|
||||
int frames = 0, samples = 0, encoder_delay = 0, encoder_padding = 0;
|
||||
mpeg_frame_info info;
|
||||
|
||||
if (!sf)
|
||||
return 0;
|
||||
|
||||
if (max_offset > get_streamfile_size(sf))
|
||||
max_offset = get_streamfile_size(sf);
|
||||
|
||||
/* MPEG may use VBR so must read all frames */
|
||||
while (offset < max_offset) {
|
||||
uint32_t header = read_u32be(offset+0x00, sf);
|
||||
|
||||
/* skip ID3v2 */
|
||||
if ((header & 0xFFFFFF00) == 0x49443300) { /* "ID3\0" */
|
||||
size_t frame_size = 0;
|
||||
uint8_t flags = read_u8(offset+0x05, sf);
|
||||
/* this is how it's officially read :/ */
|
||||
frame_size += read_u8(offset+0x06, sf) << 21;
|
||||
frame_size += read_u8(offset+0x07, sf) << 14;
|
||||
frame_size += read_u8(offset+0x08, sf) << 7;
|
||||
frame_size += read_u8(offset+0x09, sf) << 0;
|
||||
frame_size += 0x0a;
|
||||
if (flags & 0x10) /* footer? */
|
||||
frame_size += 0x0a;
|
||||
|
||||
offset += frame_size;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* skip ID3v1 */
|
||||
if ((header & 0xFFFFFF00) == 0x54414700) { /* "TAG\0" */
|
||||
;VGM_LOG("MPEG: ID3v1 at %lx\n", offset);
|
||||
offset += 0x80;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* regular frame */
|
||||
if (!mpeg_get_frame_info_h(header, &info)) {
|
||||
VGM_LOG("MPEG: unknown frame at %lx\n", offset);
|
||||
break;
|
||||
}
|
||||
|
||||
/* detect Xing header (disguised as a normal frame) */
|
||||
if (frames < 3 && /* should be first after tags */
|
||||
info.frame_size >= 0x24 + 0x78 &&
|
||||
read_u32be(offset + 0x04, sf) == 0 &&
|
||||
(read_u32be(offset + 0x24, sf) == 0x58696E67 || /* "Xing" (mainly for VBR) */
|
||||
read_u32be(offset + 0x24, sf) == 0x496E666F)) { /* "Info" (mainly for CBR) */
|
||||
uint32_t flags = read_u32be(offset + 0x28, sf);
|
||||
|
||||
if (flags & 1) { /* other flags indicate seek table and stuff */
|
||||
uint32_t frame_count = read_u32be(offset + 0x2c, sf);
|
||||
samples = frame_count * info.frame_samples;
|
||||
}
|
||||
|
||||
/* vendor specific */
|
||||
if (info.frame_size > 0x24 + 0x78 + 0x24 &&
|
||||
read_u32be(offset + 0x9c, sf) == 0x4C414D45) { /* "LAME" */
|
||||
if (info.layer == 3) {
|
||||
uint32_t delays = read_u32be(offset + 0xb0, sf);
|
||||
encoder_delay = ((delays >> 12) & 0xFFF);
|
||||
encoder_padding = ((delays >> 0) & 0xFFF);
|
||||
|
||||
encoder_delay += (528 + 1); /* implicit MDCT decoder delay (seen in LAME source) */
|
||||
if (encoder_padding > 528 + 1)
|
||||
encoder_padding -= (528 + 1);
|
||||
}
|
||||
else {
|
||||
encoder_delay = 240 + 1;
|
||||
}
|
||||
|
||||
/* replay gain and stuff */
|
||||
}
|
||||
|
||||
/* there is also "iTunes" vendor with no apparent extra info, iTunes delays are in "iTunSMPB" ID3 tag */
|
||||
|
||||
;VGM_LOG("MPEG: found Xing header\n");
|
||||
break; /* we got samples */
|
||||
}
|
||||
|
||||
//TODO: detect "VBRI" header (Fraunhofer encoder)
|
||||
// https://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header#VBRIHeader
|
||||
|
||||
/* could detect VBR/CBR but read frames to remove ID3 end tags */
|
||||
|
||||
frames++;
|
||||
offset += info.frame_size;
|
||||
samples += info.frame_samples;
|
||||
}
|
||||
|
||||
;VGM_LOG("MPEG: samples=%i, ed=%i, ep=%i, end=%i\n", samples,encoder_delay,encoder_padding, samples - encoder_delay - encoder_padding);
|
||||
|
||||
//todo return encoder delay
|
||||
samples = samples - encoder_delay - encoder_padding;
|
||||
return samples;
|
||||
}
|
||||
|
||||
#endif
|
||||
#include "mpeg_decoder.h"
|
||||
|
||||
#ifdef VGM_USE_MPEG
|
||||
|
||||
/* init config and validate per type */
|
||||
int mpeg_custom_setup_init_default(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) {
|
||||
mpeg_frame_info info;
|
||||
|
||||
|
||||
/* get frame info at offset */
|
||||
if ( !mpeg_get_frame_info(streamFile, start_offset, &info))
|
||||
goto fail;
|
||||
switch(info.layer) {
|
||||
case 1: *coding_type = coding_MPEG_layer1; break;
|
||||
case 2: *coding_type = coding_MPEG_layer2; break;
|
||||
case 3: *coding_type = coding_MPEG_layer3; break;
|
||||
default: goto fail;
|
||||
}
|
||||
data->channels_per_frame = info.channels;
|
||||
data->samples_per_frame = info.frame_samples;
|
||||
data->bitrate_per_frame = info.bit_rate;
|
||||
data->sample_rate_per_frame = info.sample_rate;
|
||||
|
||||
|
||||
/* extra checks per type */
|
||||
switch(data->type) {
|
||||
case MPEG_XVAG:
|
||||
if (data->config.chunk_size <= 0 || data->config.interleave <= 0)
|
||||
goto fail; /* needs external fixed size */
|
||||
break;
|
||||
|
||||
case MPEG_FSB:
|
||||
if (data->config.fsb_padding != 0
|
||||
&& data->config.fsb_padding != 2
|
||||
&& data->config.fsb_padding != 4
|
||||
&& data->config.fsb_padding != 16)
|
||||
goto fail; /* aligned to closest 0/2/4/16 bytes */
|
||||
|
||||
/* get find interleave to stream offsets are set up externally */
|
||||
{
|
||||
int current_data_size = info.frame_size;
|
||||
int current_padding = 0;
|
||||
/* FSB padding for Layer III or multichannel Layer II */
|
||||
if ((info.layer == 3 && data->config.fsb_padding) || data->config.fsb_padding == 16) {
|
||||
current_padding = (current_data_size % data->config.fsb_padding)
|
||||
? data->config.fsb_padding - (current_data_size % data->config.fsb_padding)
|
||||
: 0;
|
||||
}
|
||||
|
||||
data->config.interleave = current_data_size + current_padding; /* should be constant for all stream */
|
||||
}
|
||||
break;
|
||||
|
||||
case MPEG_P3D:
|
||||
case MPEG_SCD:
|
||||
if (data->config.interleave <= 0)
|
||||
goto fail; /* needs external fixed size */
|
||||
break;
|
||||
|
||||
case MPEG_LYN:
|
||||
if (data->config.interleave <= 0)
|
||||
goto fail; /* needs external fixed size */
|
||||
data->default_buffer_size = data->config.interleave;
|
||||
//todo simplify/unify XVAG/P3D/SCD/LYN and just feed arbitrary chunks to the decoder
|
||||
break;
|
||||
|
||||
case MPEG_STANDARD:
|
||||
case MPEG_AHX:
|
||||
case MPEG_EA:
|
||||
if (info.channels != data->config.channels)
|
||||
goto fail; /* no multichannel expected */
|
||||
break;
|
||||
|
||||
default:
|
||||
break; /* nothing special needed */
|
||||
}
|
||||
|
||||
|
||||
//todo: test more: this improves the output, but seems formats aren't usually prepared
|
||||
// (and/or the num_samples includes all possible samples in file, so by discarding some it'll reach EOF)
|
||||
|
||||
/* set encoder delay (samples to skip at the beginning of a stream) if needed, which varies with encoder used */
|
||||
switch(data->type) {
|
||||
//case MPEG_AHX: data->skip_samples = 480; break; /* observed default */
|
||||
//case MPEG_P3D: data->skip_samples = info.frame_samples; break; /* matches Radical ADPCM (PC) output */
|
||||
|
||||
/* FSBs (with FMOD DLLs) don't seem to need it. Particularly a few games (all from Wayforward?)
|
||||
* contain audible garbage at the beginning, but it's actually there in-game too */
|
||||
//case MPEG_FSB: data->skip_samples = 0; break;
|
||||
|
||||
case MPEG_XVAG: /* set in header and needed for gapless looping */
|
||||
data->skip_samples = data->config.skip_samples; break;
|
||||
case MPEG_STANDARD:
|
||||
data->skip_samples = data->config.skip_samples; break;
|
||||
case MPEG_EA:
|
||||
/* typical MP2 decoder delay, verified vs sx.exe, also SCHl blocks header takes discard
|
||||
* samples into account (so block_samples+240*2+1 = total frame samples) */
|
||||
if (info.layer == 2) {
|
||||
data->skip_samples = 240*2 + 1;
|
||||
}
|
||||
/* MP3 probably uses 576 + 528+1 but no known games use it */
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
data->samples_to_discard = data->skip_samples;
|
||||
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* writes data to the buffer and moves offsets */
|
||||
int mpeg_custom_parse_frame_default(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) {
|
||||
mpeg_custom_stream *ms = data->streams[num_stream];
|
||||
mpeg_frame_info info;
|
||||
size_t current_data_size = 0;
|
||||
size_t current_padding = 0;
|
||||
size_t current_interleave_pre = 0; /* interleaved data size before current stream */
|
||||
size_t current_interleave_post = 0; /* interleaved data size after current stream */
|
||||
size_t current_interleave = 0; /* interleave in this block (usually this + pre + post = interleave*streams = block) */
|
||||
|
||||
|
||||
/* Get data size to give to the decoder, per stream. Usually 1 frame at a time,
|
||||
* but doesn't really need to be a full frame (decoder would request more data). */
|
||||
switch(data->type) {
|
||||
|
||||
case MPEG_XVAG: /* frames of fixed size (though we could read frame info too) */
|
||||
current_interleave = data->config.interleave; /* big interleave */
|
||||
current_interleave_pre = current_interleave*num_stream;
|
||||
current_interleave_post = current_interleave*(data->streams_size-1) - current_interleave_pre;
|
||||
|
||||
current_data_size = data->config.chunk_size;
|
||||
break;
|
||||
|
||||
case MPEG_FSB: /* frames with padding + interleave */
|
||||
current_interleave = data->config.interleave; /* constant for multi-stream FSbs (1 frame + padding) */
|
||||
current_interleave_pre = current_interleave*num_stream;
|
||||
current_interleave_post = current_interleave*(data->streams_size-1) - current_interleave_pre;
|
||||
|
||||
if (!mpeg_get_frame_info(stream->streamfile, stream->offset + current_interleave_pre, &info))
|
||||
goto fail;
|
||||
current_data_size = info.frame_size;
|
||||
|
||||
/* get FSB padding for Layer III or multichannel Layer II (Layer I isn't supported by FMOD).
|
||||
* Padding sometimes contains garbage like the next frame header so we can't feed it to mpg123 or it gets confused. */
|
||||
if ((info.layer == 3 && data->config.fsb_padding) || data->config.fsb_padding == 16) {
|
||||
current_padding = (current_data_size % data->config.fsb_padding)
|
||||
? data->config.fsb_padding - (current_data_size % data->config.fsb_padding)
|
||||
: 0;
|
||||
|
||||
/* Rare Mafia II (PS3) bug (GP_0701_music multilang only): some frame paddings "4" are incorrect,
|
||||
* calcs give 0xD0+0x00 but need 0xD0+0x04 (unlike all other fsbs, which never do that).
|
||||
* FMOD tools decode fine, so they may be doing special detection too, since even
|
||||
* re-encoding the same file and using the same FSB flags/modes won't trigger the bug. */
|
||||
if (info.layer == 3 && data->config.fsb_padding == 4 && current_data_size == 0xD0) {
|
||||
uint32_t next_header;
|
||||
off_t next_offset;
|
||||
|
||||
next_offset = stream->offset + current_data_size + current_padding;
|
||||
if (current_interleave && ((next_offset - stream->channel_start_offset + current_interleave_pre + current_interleave_post) % current_interleave == 0)) {
|
||||
next_offset += current_interleave_pre + current_interleave_post;
|
||||
}
|
||||
|
||||
next_header = read_32bitBE(next_offset, stream->streamfile);
|
||||
if ((next_header & 0xFFE00000) != 0xFFE00000) { /* doesn't land in a proper frame, fix sizes and hope */
|
||||
VGM_LOG_ONCE("MPEG FSB: stream with wrong padding found\n");
|
||||
current_padding = 0x04;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
VGM_ASSERT(data->streams_size > 1 && current_interleave != current_data_size+current_padding,
|
||||
"MPEG FSB: %i streams with non-constant interleave found @ 0x%08x\n", data->streams_size, (uint32_t)stream->offset);
|
||||
break;
|
||||
|
||||
case MPEG_P3D: /* fixed interleave, not frame-aligned (ie. blocks may end/start in part of a frame) */
|
||||
case MPEG_SCD:
|
||||
case MPEG_LYN:
|
||||
current_interleave = data->config.interleave;
|
||||
|
||||
/* check if current interleave block is short */
|
||||
{
|
||||
off_t block_offset = stream->offset - stream->channel_start_offset;
|
||||
size_t next_block = data->streams_size*data->config.interleave;
|
||||
|
||||
if (data->config.data_size && block_offset + next_block >= data->config.data_size)
|
||||
current_interleave = (data->config.data_size % next_block) / data->streams_size; /* short_interleave*/
|
||||
}
|
||||
|
||||
current_interleave_pre = current_interleave*num_stream;
|
||||
current_interleave_post = current_interleave*(data->streams_size-1) - current_interleave_pre;
|
||||
|
||||
current_data_size = current_interleave;
|
||||
break;
|
||||
|
||||
default: /* standard frames (CBR or VBR) */
|
||||
if ( !mpeg_get_frame_info(stream->streamfile, stream->offset, &info) )
|
||||
goto fail;
|
||||
current_data_size = info.frame_size;
|
||||
break;
|
||||
}
|
||||
if (!current_data_size || current_data_size > ms->buffer_size) {
|
||||
VGM_LOG("MPEG: incorrect data_size 0x%x vs buffer 0x%x\n", current_data_size, ms->buffer_size);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* This assumes all streams' offsets start in the first stream, and advances
|
||||
* the 'full interleaved block' at once, ex:
|
||||
* start at s0=0x00, s1=0x00, interleave=0x40 (block = 0x40*2=0x80)
|
||||
* @0x00 read 0x40 of s0, skip 0x40 of s1 (block of 0x80 done) > new offset = 0x80
|
||||
* @0x00 skip 0x40 of s0, read 0x40 of s1 (block of 0x80 done) > new offset = 0x800
|
||||
*/
|
||||
|
||||
/* read chunk (skipping other interleaves if needed) */
|
||||
ms->bytes_in_buffer = read_streamfile(ms->buffer,stream->offset + current_interleave_pre, current_data_size, stream->streamfile);
|
||||
|
||||
|
||||
/* update offsets and skip other streams */
|
||||
stream->offset += current_data_size + current_padding;
|
||||
|
||||
/* skip rest of block (interleave per stream) once this stream's interleaved data is done, if defined */
|
||||
if (current_interleave && ((stream->offset - stream->channel_start_offset + current_interleave_pre + current_interleave_post) % current_interleave == 0)) {
|
||||
stream->offset += current_interleave_pre + current_interleave_post;
|
||||
}
|
||||
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*****************/
|
||||
/* FRAME HELPERS */
|
||||
/*****************/
|
||||
|
||||
/**
|
||||
* Gets info from a MPEG frame header at offset. Normally you would use mpg123_info but somehow
|
||||
* it's wrong at times (maybe because we use an ancient version) so here we do our thing.
|
||||
*/
|
||||
static int mpeg_get_frame_info_h(uint32_t header, mpeg_frame_info *info) {
|
||||
/* index tables */
|
||||
static const int versions[4] = { /* MPEG 2.5 */ 3, /* reserved */ -1, /* MPEG 2 */ 2, /* MPEG 1 */ 1 };
|
||||
static const int layers[4] = { -1,3,2,1 };
|
||||
static const int bit_rates[5][16] = { /* [version index ][bit rate index] (0=free, -1=bad) */
|
||||
{ 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1 }, /* MPEG1 Layer I */
|
||||
{ 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1 }, /* MPEG1 Layer II */
|
||||
{ 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1 }, /* MPEG1 Layer III */
|
||||
{ 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1 }, /* MPEG2/2.5 Layer I */
|
||||
{ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 }, /* MPEG2/2.5 Layer II/III */
|
||||
};
|
||||
static const int sample_rates[4][4] = { /* [version][sample rate index] */
|
||||
{ 44100, 48000, 32000, -1}, /* MPEG1 */
|
||||
{ 22050, 24000, 16000, -1}, /* MPEG2 */
|
||||
{ 11025, 12000, 8000, -1}, /* MPEG2.5 */
|
||||
};
|
||||
static const int channels[4] = { 2,2,2, 1 }; /* [channel] */
|
||||
static const int frame_samples[3][3] = { /* [version][layer] */
|
||||
{ 384, 1152, 1152 }, /* MPEG1 */
|
||||
{ 384, 1152, 576 }, /* MPEG2 */
|
||||
{ 384, 1152, 576 } /* MPEG2.5 */
|
||||
};
|
||||
|
||||
int idx, padding;
|
||||
|
||||
|
||||
memset(info, 0, sizeof(*info));
|
||||
|
||||
if ((header >> 21) != 0x7FF) /* 31-21: sync */
|
||||
goto fail;
|
||||
|
||||
info->version = versions[(header >> 19) & 0x3]; /* 20,19: version */
|
||||
if (info->version <= 0) goto fail;
|
||||
|
||||
info->layer = layers[(header >> 17) & 0x3]; /* 18,17: layer */
|
||||
if (info->layer <= 0 || info->layer > 3) goto fail;
|
||||
|
||||
//crc = (header >> 16) & 0x1; /* 16: protected by crc? */
|
||||
|
||||
idx = (info->version==1 ? info->layer-1 : (3 + (info->layer==1 ? 0 : 1)));
|
||||
info->bit_rate = bit_rates[idx][(header >> 12) & 0xf]; /* 15-12: bit rate */
|
||||
if (info->bit_rate <= 0) goto fail;
|
||||
|
||||
info->sample_rate = sample_rates[info->version-1][(header >> 10) & 0x3]; /* 11-10: sampling rate */
|
||||
if (info->sample_rate <= 0) goto fail;
|
||||
|
||||
padding = (header >> 9) & 0x1; /* 9: padding? */
|
||||
//private = (header >> 8) & 0x1; /* 8: private bit */
|
||||
|
||||
info->channels = channels[(header >> 6) & 0x3]; /* 7,6: channel mode */
|
||||
|
||||
//js_mode = (header >> 4) & 0x3; /* 5,4: mode extension for joint stereo */
|
||||
//copyright = (header >> 3) & 0x1; /* 3: copyrighted */
|
||||
//original = (header >> 2) & 0x1; /* 2: original */
|
||||
//emphasis = (header >> 0) & 0x3; /* 1,0: emphasis */
|
||||
|
||||
info->frame_samples = frame_samples[info->version-1][info->layer-1];
|
||||
|
||||
/* calculate frame length (from hcs's fsb_mpeg) */
|
||||
switch (info->frame_samples) {
|
||||
case 384: info->frame_size = (12l * info->bit_rate * 1000l / info->sample_rate + padding) * 4; break; /* 384/32 = 12 */
|
||||
case 576: info->frame_size = (72l * info->bit_rate * 1000l / info->sample_rate + padding); break; /* 576/8 = 72 */
|
||||
case 1152: info->frame_size = (144l * info->bit_rate * 1000l / info->sample_rate + padding); break; /* 1152/8 = 144 */
|
||||
default: goto fail;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
int mpeg_get_frame_info(STREAMFILE *sf, off_t offset, mpeg_frame_info *info) {
|
||||
uint32_t header = read_u32be(offset, sf);
|
||||
return mpeg_get_frame_info_h(header, info);
|
||||
}
|
||||
|
||||
size_t mpeg_get_samples(STREAMFILE *sf, off_t start_offset, size_t bytes) {
|
||||
off_t offset = start_offset;
|
||||
off_t max_offset = start_offset + bytes;
|
||||
int frames = 0, samples = 0, encoder_delay = 0, encoder_padding = 0;
|
||||
mpeg_frame_info info;
|
||||
|
||||
if (!sf)
|
||||
return 0;
|
||||
|
||||
if (max_offset > get_streamfile_size(sf))
|
||||
max_offset = get_streamfile_size(sf);
|
||||
|
||||
/* MPEG may use VBR so must read all frames */
|
||||
while (offset < max_offset) {
|
||||
uint32_t header = read_u32be(offset+0x00, sf);
|
||||
|
||||
/* skip ID3v2 */
|
||||
if ((header & 0xFFFFFF00) == 0x49443300) { /* "ID3\0" */
|
||||
size_t frame_size = 0;
|
||||
uint8_t flags = read_u8(offset+0x05, sf);
|
||||
/* this is how it's officially read :/ */
|
||||
frame_size += read_u8(offset+0x06, sf) << 21;
|
||||
frame_size += read_u8(offset+0x07, sf) << 14;
|
||||
frame_size += read_u8(offset+0x08, sf) << 7;
|
||||
frame_size += read_u8(offset+0x09, sf) << 0;
|
||||
frame_size += 0x0a;
|
||||
if (flags & 0x10) /* footer? */
|
||||
frame_size += 0x0a;
|
||||
|
||||
offset += frame_size;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* skip ID3v1 */
|
||||
if ((header & 0xFFFFFF00) == 0x54414700) { /* "TAG\0" */
|
||||
;VGM_LOG("MPEG: ID3v1 at %lx\n", offset);
|
||||
offset += 0x80;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* regular frame */
|
||||
if (!mpeg_get_frame_info_h(header, &info)) {
|
||||
VGM_LOG("MPEG: unknown frame at %lx\n", offset);
|
||||
break;
|
||||
}
|
||||
|
||||
/* detect Xing header (disguised as a normal frame) */
|
||||
if (frames < 3 && /* should be first after tags */
|
||||
info.frame_size >= 0x24 + 0x78 &&
|
||||
read_u32be(offset + 0x04, sf) == 0 &&
|
||||
(read_u32be(offset + 0x24, sf) == 0x58696E67 || /* "Xing" (mainly for VBR) */
|
||||
read_u32be(offset + 0x24, sf) == 0x496E666F)) { /* "Info" (mainly for CBR) */
|
||||
uint32_t flags = read_u32be(offset + 0x28, sf);
|
||||
|
||||
if (flags & 1) { /* other flags indicate seek table and stuff */
|
||||
uint32_t frame_count = read_u32be(offset + 0x2c, sf);
|
||||
samples = frame_count * info.frame_samples;
|
||||
}
|
||||
|
||||
/* vendor specific */
|
||||
if (info.frame_size > 0x24 + 0x78 + 0x24 &&
|
||||
read_u32be(offset + 0x9c, sf) == 0x4C414D45) { /* "LAME" */
|
||||
if (info.layer == 3) {
|
||||
uint32_t delays = read_u32be(offset + 0xb0, sf);
|
||||
encoder_delay = ((delays >> 12) & 0xFFF);
|
||||
encoder_padding = ((delays >> 0) & 0xFFF);
|
||||
|
||||
encoder_delay += (528 + 1); /* implicit MDCT decoder delay (seen in LAME source) */
|
||||
if (encoder_padding > 528 + 1)
|
||||
encoder_padding -= (528 + 1);
|
||||
}
|
||||
else {
|
||||
encoder_delay = 240 + 1;
|
||||
}
|
||||
|
||||
/* replay gain and stuff */
|
||||
}
|
||||
|
||||
/* there is also "iTunes" vendor with no apparent extra info, iTunes delays are in "iTunSMPB" ID3 tag */
|
||||
|
||||
;VGM_LOG("MPEG: found Xing header\n");
|
||||
break; /* we got samples */
|
||||
}
|
||||
|
||||
//TODO: detect "VBRI" header (Fraunhofer encoder)
|
||||
// https://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header#VBRIHeader
|
||||
|
||||
/* could detect VBR/CBR but read frames to remove ID3 end tags */
|
||||
|
||||
frames++;
|
||||
offset += info.frame_size;
|
||||
samples += info.frame_samples;
|
||||
}
|
||||
|
||||
;VGM_LOG("MPEG: samples=%i, ed=%i, ep=%i, end=%i\n", samples,encoder_delay,encoder_padding, samples - encoder_delay - encoder_padding);
|
||||
|
||||
//todo return encoder delay
|
||||
samples = samples - encoder_delay - encoder_padding;
|
||||
return samples;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,114 +1,114 @@
|
|||
#include "mpeg_decoder.h"
|
||||
|
||||
#ifdef VGM_USE_MPEG
|
||||
#define MPEG_AHX_EXPECTED_FRAME_SIZE 0x414
|
||||
|
||||
static int ahx_decrypt_type08(uint8_t * buffer, mpeg_custom_config *config);
|
||||
|
||||
/* writes data to the buffer and moves offsets, transforming AHX frames as needed */
|
||||
int mpeg_custom_parse_frame_ahx(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) {
|
||||
mpeg_custom_stream *ms = data->streams[num_stream];
|
||||
size_t current_data_size = 0;
|
||||
size_t file_size = get_streamfile_size(stream->streamfile);
|
||||
|
||||
/* AHX has a 0xFFF5E0C0 header with frame size 0x414 (160kbps, 22050Hz) but they actually are much shorter */
|
||||
|
||||
/* read supposed frame size first (to minimize reads) */
|
||||
ms->bytes_in_buffer = read_streamfile(ms->buffer, stream->offset, MPEG_AHX_EXPECTED_FRAME_SIZE, stream->streamfile);
|
||||
|
||||
/* find actual frame size by looking for the next frame header */
|
||||
{
|
||||
uint32_t current_header = get_u32be(ms->buffer);
|
||||
int next_pos = 0x04;
|
||||
|
||||
while (next_pos <= MPEG_AHX_EXPECTED_FRAME_SIZE) {
|
||||
uint32_t next_header = get_u32be(ms->buffer + next_pos);
|
||||
|
||||
if (current_header == next_header) {
|
||||
current_data_size = next_pos;
|
||||
break;
|
||||
}
|
||||
|
||||
/* AHXs end in a 0x0c footer (0x41485845 28632943 52490000 / "AHXE(c)CRI\0\0") */
|
||||
if (stream->offset + next_pos + 0x0c >= file_size) {
|
||||
current_data_size = next_pos;
|
||||
break;
|
||||
}
|
||||
|
||||
next_pos++;
|
||||
}
|
||||
}
|
||||
|
||||
if (current_data_size == 0 || current_data_size > ms->buffer_size || current_data_size > MPEG_AHX_EXPECTED_FRAME_SIZE) {
|
||||
VGM_LOG("MPEG AHX: incorrect data_size 0x%x\n", current_data_size);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* 0-fill up to expected size to keep mpg123 happy */
|
||||
memset(ms->buffer + current_data_size, 0, MPEG_AHX_EXPECTED_FRAME_SIZE - current_data_size);
|
||||
ms->bytes_in_buffer = MPEG_AHX_EXPECTED_FRAME_SIZE;
|
||||
|
||||
|
||||
/* decrypt if needed */
|
||||
switch(data->config.encryption) {
|
||||
case 0x00: break;
|
||||
case 0x08: ahx_decrypt_type08(ms->buffer, &data->config); break;
|
||||
default:
|
||||
VGM_LOG("MPEG AHX: unknown encryption 0x%x\n", data->config.encryption);
|
||||
break; /* garbled frame */
|
||||
}
|
||||
|
||||
/* update offsets */
|
||||
stream->offset += current_data_size;
|
||||
if (stream->offset + 0x0c >= file_size)
|
||||
stream->offset = file_size; /* skip 0x0c footer to reach EOF (shouldn't happen normally) */
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Decrypts an AHX type 0x08 (keystring) encrypted frame. Algorithm by Thealexbarney */
|
||||
static int ahx_decrypt_type08(uint8_t * buffer, mpeg_custom_config *config) {
|
||||
int i, index, encrypted_bits;
|
||||
uint32_t value;
|
||||
uint16_t current_key;
|
||||
|
||||
/* encryption 0x08 modifies a few bits every frame, here we decrypt and write to data buffer */
|
||||
|
||||
/* derive keystring to 3 primes, using the type 0x08 method, and assign each an index of 1/2/3 (0=no key) */
|
||||
/* (externally done for now, see: https://github.com/Thealexbarney/VGAudio/blob/2.0/src/VGAudio/Codecs/CriAdx/CriAdxKey.cs) */
|
||||
|
||||
/* read 2b from a bitstream offset to decrypt, and use it as an index to get the key.
|
||||
* AHX encrypted bitstream starts at 107b (0x0d*8+3), every frame, and seem to always use index 2 */
|
||||
value = get_u32be(buffer + 0x0d);
|
||||
index = (value >> (32-3-2)) & 0x03;
|
||||
switch(index) {
|
||||
case 0: current_key = 0; break;
|
||||
case 1: current_key = config->cri_key1; break;
|
||||
case 2: current_key = config->cri_key2; break;
|
||||
case 3: current_key = config->cri_key3; break;
|
||||
default: goto fail;
|
||||
}
|
||||
|
||||
/* AHX for DC: 16b, normal: 6b (no idea, probably some Layer II field) */
|
||||
encrypted_bits = config->cri_type == 0x10 ? 16 : 6;
|
||||
|
||||
/* decrypt next bitstream 2b pairs, up to 16b (max key size):
|
||||
* - read 2b from bitstream (from higher to lower)
|
||||
* - read 2b from key (from lower to higher)
|
||||
* - XOR them to decrypt */
|
||||
for (i = 0; i < encrypted_bits; i+=2) {
|
||||
uint32_t xor_2b = (current_key >> i) & 0x03;
|
||||
value ^= ((xor_2b << (32-3-2-2)) >> i);
|
||||
}
|
||||
|
||||
/* write output */
|
||||
put_32bitBE(buffer + 0x0d, value);
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
#include "mpeg_decoder.h"
|
||||
|
||||
#ifdef VGM_USE_MPEG
|
||||
#define MPEG_AHX_EXPECTED_FRAME_SIZE 0x414
|
||||
|
||||
static int ahx_decrypt_type08(uint8_t * buffer, mpeg_custom_config *config);
|
||||
|
||||
/* writes data to the buffer and moves offsets, transforming AHX frames as needed */
|
||||
int mpeg_custom_parse_frame_ahx(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) {
|
||||
mpeg_custom_stream *ms = data->streams[num_stream];
|
||||
size_t current_data_size = 0;
|
||||
size_t file_size = get_streamfile_size(stream->streamfile);
|
||||
|
||||
/* AHX has a 0xFFF5E0C0 header with frame size 0x414 (160kbps, 22050Hz) but they actually are much shorter */
|
||||
|
||||
/* read supposed frame size first (to minimize reads) */
|
||||
ms->bytes_in_buffer = read_streamfile(ms->buffer, stream->offset, MPEG_AHX_EXPECTED_FRAME_SIZE, stream->streamfile);
|
||||
|
||||
/* find actual frame size by looking for the next frame header */
|
||||
{
|
||||
uint32_t current_header = get_u32be(ms->buffer);
|
||||
int next_pos = 0x04;
|
||||
|
||||
while (next_pos <= MPEG_AHX_EXPECTED_FRAME_SIZE) {
|
||||
uint32_t next_header = get_u32be(ms->buffer + next_pos);
|
||||
|
||||
if (current_header == next_header) {
|
||||
current_data_size = next_pos;
|
||||
break;
|
||||
}
|
||||
|
||||
/* AHXs end in a 0x0c footer (0x41485845 28632943 52490000 / "AHXE(c)CRI\0\0") */
|
||||
if (stream->offset + next_pos + 0x0c >= file_size) {
|
||||
current_data_size = next_pos;
|
||||
break;
|
||||
}
|
||||
|
||||
next_pos++;
|
||||
}
|
||||
}
|
||||
|
||||
if (current_data_size == 0 || current_data_size > ms->buffer_size || current_data_size > MPEG_AHX_EXPECTED_FRAME_SIZE) {
|
||||
VGM_LOG("MPEG AHX: incorrect data_size 0x%x\n", current_data_size);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* 0-fill up to expected size to keep mpg123 happy */
|
||||
memset(ms->buffer + current_data_size, 0, MPEG_AHX_EXPECTED_FRAME_SIZE - current_data_size);
|
||||
ms->bytes_in_buffer = MPEG_AHX_EXPECTED_FRAME_SIZE;
|
||||
|
||||
|
||||
/* decrypt if needed */
|
||||
switch(data->config.encryption) {
|
||||
case 0x00: break;
|
||||
case 0x08: ahx_decrypt_type08(ms->buffer, &data->config); break;
|
||||
default:
|
||||
VGM_LOG("MPEG AHX: unknown encryption 0x%x\n", data->config.encryption);
|
||||
break; /* garbled frame */
|
||||
}
|
||||
|
||||
/* update offsets */
|
||||
stream->offset += current_data_size;
|
||||
if (stream->offset + 0x0c >= file_size)
|
||||
stream->offset = file_size; /* skip 0x0c footer to reach EOF (shouldn't happen normally) */
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Decrypts an AHX type 0x08 (keystring) encrypted frame. Algorithm by Thealexbarney */
|
||||
static int ahx_decrypt_type08(uint8_t * buffer, mpeg_custom_config *config) {
|
||||
int i, index, encrypted_bits;
|
||||
uint32_t value;
|
||||
uint16_t current_key;
|
||||
|
||||
/* encryption 0x08 modifies a few bits every frame, here we decrypt and write to data buffer */
|
||||
|
||||
/* derive keystring to 3 primes, using the type 0x08 method, and assign each an index of 1/2/3 (0=no key) */
|
||||
/* (externally done for now, see: https://github.com/Thealexbarney/VGAudio/blob/2.0/src/VGAudio/Codecs/CriAdx/CriAdxKey.cs) */
|
||||
|
||||
/* read 2b from a bitstream offset to decrypt, and use it as an index to get the key.
|
||||
* AHX encrypted bitstream starts at 107b (0x0d*8+3), every frame, and seem to always use index 2 */
|
||||
value = get_u32be(buffer + 0x0d);
|
||||
index = (value >> (32-3-2)) & 0x03;
|
||||
switch(index) {
|
||||
case 0: current_key = 0; break;
|
||||
case 1: current_key = config->cri_key1; break;
|
||||
case 2: current_key = config->cri_key2; break;
|
||||
case 3: current_key = config->cri_key3; break;
|
||||
default: goto fail;
|
||||
}
|
||||
|
||||
/* AHX for DC: 16b, normal: 6b (no idea, probably some Layer II field) */
|
||||
encrypted_bits = config->cri_type == 0x10 ? 16 : 6;
|
||||
|
||||
/* decrypt next bitstream 2b pairs, up to 16b (max key size):
|
||||
* - read 2b from bitstream (from higher to lower)
|
||||
* - read 2b from key (from lower to higher)
|
||||
* - XOR them to decrypt */
|
||||
for (i = 0; i < encrypted_bits; i+=2) {
|
||||
uint32_t xor_2b = (current_key >> i) & 0x03;
|
||||
value ^= ((xor_2b << (32-3-2-2)) >> i);
|
||||
}
|
||||
|
||||
/* write output */
|
||||
put_32bitBE(buffer + 0x0d, value);
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,176 +1,176 @@
|
|||
#include "mpeg_decoder.h"
|
||||
|
||||
#ifdef VGM_USE_MPEG
|
||||
|
||||
/* parsed info from a single EAMP3 frame */
|
||||
typedef struct {
|
||||
uint32_t extended_flag;
|
||||
uint32_t stereo_flag; /* assumed */
|
||||
uint32_t unknown_flag; /* unused? */
|
||||
uint32_t frame_size; /* full size including headers and pcm block */
|
||||
uint32_t pcm_number; /* samples in the PCM block (typically 1 MPEG frame, 1152) */
|
||||
|
||||
uint32_t pre_size; /* size of the header part */
|
||||
uint32_t mpeg_size; /* size of the MPEG part */
|
||||
uint32_t pcm_size; /* size of the PCM block */
|
||||
} eamp3_frame_info;
|
||||
|
||||
static int eamp3_parse_frame(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, eamp3_frame_info * eaf);
|
||||
static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, eamp3_frame_info * eaf);
|
||||
static int eamp3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start);
|
||||
|
||||
/* init config and validate */
|
||||
int mpeg_custom_setup_init_eamp3(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) {
|
||||
mpeg_frame_info info;
|
||||
uint16_t frame_header;
|
||||
size_t header_size;
|
||||
|
||||
|
||||
/* test unknown stuff */
|
||||
frame_header = (uint16_t)read_16bitLE(start_offset+0x00, streamFile);
|
||||
if (frame_header & 0x2000) {
|
||||
VGM_LOG("EAMP3: found unknown bit 13\n");
|
||||
goto fail;
|
||||
}
|
||||
if ((frame_header & 0x8000) && (uint32_t)read_32bitLE(start_offset+0x02, streamFile) > 0xFFFF) {
|
||||
VGM_LOG("EAMP3: found big PCM block\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* get frame info at offset */
|
||||
header_size = (frame_header & 0x8000) ? 0x06 : 0x02;
|
||||
if (!mpeg_get_frame_info(streamFile, start_offset+header_size, &info))
|
||||
goto fail;
|
||||
switch(info.layer) {
|
||||
case 1: *coding_type = coding_MPEG_layer1; break;
|
||||
case 2: *coding_type = coding_MPEG_layer2; break;
|
||||
case 3: *coding_type = coding_MPEG_layer3; break;
|
||||
default: goto fail;
|
||||
}
|
||||
data->channels_per_frame = info.channels;
|
||||
data->samples_per_frame = info.frame_samples;
|
||||
data->bitrate_per_frame = info.bit_rate;
|
||||
data->sample_rate_per_frame = info.sample_rate;
|
||||
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* reads custom frame header + MPEG data + (optional) PCM block */
|
||||
int mpeg_custom_parse_frame_eamp3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) {
|
||||
mpeg_custom_stream *ms = data->streams[num_stream];
|
||||
eamp3_frame_info eaf;
|
||||
int ok;
|
||||
|
||||
|
||||
if (!eamp3_skip_data(stream, data, num_stream, 1))
|
||||
goto fail;
|
||||
|
||||
ok = eamp3_parse_frame(stream, data, &eaf);
|
||||
if (!ok) goto fail;
|
||||
|
||||
ms->bytes_in_buffer = read_streamfile(ms->buffer, stream->offset + eaf.pre_size, eaf.mpeg_size, stream->streamfile);
|
||||
|
||||
ok = eamp3_write_pcm_block(stream, data, num_stream, &eaf);
|
||||
if (!ok) goto fail;
|
||||
|
||||
stream->offset += eaf.frame_size;
|
||||
|
||||
if (!eamp3_skip_data(stream, data, num_stream, 0))
|
||||
goto fail;
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int eamp3_parse_frame(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, eamp3_frame_info * eaf) {
|
||||
uint16_t current_header = (uint16_t)read_16bitLE(stream->offset+0x00, stream->streamfile);
|
||||
|
||||
eaf->extended_flag = (current_header & 0x8000);
|
||||
eaf->stereo_flag = (current_header & 0x4000);
|
||||
eaf->unknown_flag = (current_header & 0x2000);
|
||||
eaf->frame_size = (current_header & 0x1FFF); /* full size including PCM block */
|
||||
eaf->pcm_number = 0;
|
||||
if (eaf->extended_flag > 0) {
|
||||
eaf->pcm_number = (uint32_t)read_32bitLE(stream->offset+0x02, stream->streamfile);
|
||||
eaf->pcm_size = sizeof(sample) * eaf->pcm_number * data->channels_per_frame;
|
||||
eaf->pre_size = 0x06;
|
||||
eaf->mpeg_size = eaf->frame_size - eaf->pre_size - eaf->pcm_size;
|
||||
if (eaf->frame_size < eaf->pre_size + eaf->pcm_size) {
|
||||
VGM_LOG("EAMP3: bad pcm size at %x\n", (uint32_t)stream->offset);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else {
|
||||
eaf->pcm_size = 0;
|
||||
eaf->pre_size = 0x02;
|
||||
eaf->mpeg_size = eaf->frame_size - eaf->pre_size;
|
||||
}
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* write PCM block directly to sample buffer and setup decode discard (see EALayer3). */
|
||||
static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, eamp3_frame_info * eaf) {
|
||||
mpeg_custom_stream *ms = data->streams[num_stream];
|
||||
size_t bytes_filled;
|
||||
int i;
|
||||
|
||||
|
||||
bytes_filled = sizeof(sample) * ms->samples_filled * data->channels_per_frame;
|
||||
if (bytes_filled + eaf->pcm_size > ms->output_buffer_size) {
|
||||
VGM_LOG("EAMP3: can't fill the sample buffer with 0x%x\n", eaf->pcm_size);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
if (eaf->pcm_number) {
|
||||
|
||||
/* read + write PCM block samples (always LE) */
|
||||
for (i = 0; i < eaf->pcm_number * data->channels_per_frame; i++) {
|
||||
off_t pcm_offset = stream->offset + eaf->pre_size + eaf->mpeg_size + sizeof(sample)*i;
|
||||
int16_t pcm_sample = read_16bitLE(pcm_offset,stream->streamfile);
|
||||
put_16bitLE(ms->output_buffer + bytes_filled + sizeof(sample)*i, pcm_sample);
|
||||
}
|
||||
ms->samples_filled += eaf->pcm_number;
|
||||
|
||||
/* modify decoded samples */
|
||||
{
|
||||
size_t decode_to_discard = eaf->pcm_number; //todo guessed
|
||||
ms->decode_to_discard += decode_to_discard;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Skip EA-frames from other streams for .sns/sps multichannel (see EALayer3). */
|
||||
static int eamp3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start) {
|
||||
int ok, i;
|
||||
eamp3_frame_info eaf;
|
||||
int skips = at_start ? num_stream : data->streams_size - 1 - num_stream;
|
||||
|
||||
|
||||
for (i = 0; i < skips; i++) {
|
||||
ok = eamp3_parse_frame(stream, data, &eaf);
|
||||
if (!ok) goto fail;
|
||||
|
||||
//;VGM_LOG("s%i: skipping %x, now at %lx\n", num_stream,eaf.frame_size,stream->offset);
|
||||
stream->offset += eaf.frame_size;
|
||||
}
|
||||
//;VGM_LOG("s%i: skipped %i frames, now at %lx\n", num_stream,skips,stream->offset);
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
#include "mpeg_decoder.h"
|
||||
|
||||
#ifdef VGM_USE_MPEG
|
||||
|
||||
/* parsed info from a single EAMP3 frame */
|
||||
typedef struct {
|
||||
uint32_t extended_flag;
|
||||
uint32_t stereo_flag; /* assumed */
|
||||
uint32_t unknown_flag; /* unused? */
|
||||
uint32_t frame_size; /* full size including headers and pcm block */
|
||||
uint32_t pcm_number; /* samples in the PCM block (typically 1 MPEG frame, 1152) */
|
||||
|
||||
uint32_t pre_size; /* size of the header part */
|
||||
uint32_t mpeg_size; /* size of the MPEG part */
|
||||
uint32_t pcm_size; /* size of the PCM block */
|
||||
} eamp3_frame_info;
|
||||
|
||||
static int eamp3_parse_frame(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, eamp3_frame_info * eaf);
|
||||
static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, eamp3_frame_info * eaf);
|
||||
static int eamp3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start);
|
||||
|
||||
/* init config and validate */
|
||||
int mpeg_custom_setup_init_eamp3(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) {
|
||||
mpeg_frame_info info;
|
||||
uint16_t frame_header;
|
||||
size_t header_size;
|
||||
|
||||
|
||||
/* test unknown stuff */
|
||||
frame_header = (uint16_t)read_16bitLE(start_offset+0x00, streamFile);
|
||||
if (frame_header & 0x2000) {
|
||||
VGM_LOG("EAMP3: found unknown bit 13\n");
|
||||
goto fail;
|
||||
}
|
||||
if ((frame_header & 0x8000) && (uint32_t)read_32bitLE(start_offset+0x02, streamFile) > 0xFFFF) {
|
||||
VGM_LOG("EAMP3: found big PCM block\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* get frame info at offset */
|
||||
header_size = (frame_header & 0x8000) ? 0x06 : 0x02;
|
||||
if (!mpeg_get_frame_info(streamFile, start_offset+header_size, &info))
|
||||
goto fail;
|
||||
switch(info.layer) {
|
||||
case 1: *coding_type = coding_MPEG_layer1; break;
|
||||
case 2: *coding_type = coding_MPEG_layer2; break;
|
||||
case 3: *coding_type = coding_MPEG_layer3; break;
|
||||
default: goto fail;
|
||||
}
|
||||
data->channels_per_frame = info.channels;
|
||||
data->samples_per_frame = info.frame_samples;
|
||||
data->bitrate_per_frame = info.bit_rate;
|
||||
data->sample_rate_per_frame = info.sample_rate;
|
||||
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* reads custom frame header + MPEG data + (optional) PCM block */
|
||||
int mpeg_custom_parse_frame_eamp3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) {
|
||||
mpeg_custom_stream *ms = data->streams[num_stream];
|
||||
eamp3_frame_info eaf;
|
||||
int ok;
|
||||
|
||||
|
||||
if (!eamp3_skip_data(stream, data, num_stream, 1))
|
||||
goto fail;
|
||||
|
||||
ok = eamp3_parse_frame(stream, data, &eaf);
|
||||
if (!ok) goto fail;
|
||||
|
||||
ms->bytes_in_buffer = read_streamfile(ms->buffer, stream->offset + eaf.pre_size, eaf.mpeg_size, stream->streamfile);
|
||||
|
||||
ok = eamp3_write_pcm_block(stream, data, num_stream, &eaf);
|
||||
if (!ok) goto fail;
|
||||
|
||||
stream->offset += eaf.frame_size;
|
||||
|
||||
if (!eamp3_skip_data(stream, data, num_stream, 0))
|
||||
goto fail;
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int eamp3_parse_frame(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, eamp3_frame_info * eaf) {
|
||||
uint16_t current_header = (uint16_t)read_16bitLE(stream->offset+0x00, stream->streamfile);
|
||||
|
||||
eaf->extended_flag = (current_header & 0x8000);
|
||||
eaf->stereo_flag = (current_header & 0x4000);
|
||||
eaf->unknown_flag = (current_header & 0x2000);
|
||||
eaf->frame_size = (current_header & 0x1FFF); /* full size including PCM block */
|
||||
eaf->pcm_number = 0;
|
||||
if (eaf->extended_flag > 0) {
|
||||
eaf->pcm_number = (uint32_t)read_32bitLE(stream->offset+0x02, stream->streamfile);
|
||||
eaf->pcm_size = sizeof(sample) * eaf->pcm_number * data->channels_per_frame;
|
||||
eaf->pre_size = 0x06;
|
||||
eaf->mpeg_size = eaf->frame_size - eaf->pre_size - eaf->pcm_size;
|
||||
if (eaf->frame_size < eaf->pre_size + eaf->pcm_size) {
|
||||
VGM_LOG("EAMP3: bad pcm size at %x\n", (uint32_t)stream->offset);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else {
|
||||
eaf->pcm_size = 0;
|
||||
eaf->pre_size = 0x02;
|
||||
eaf->mpeg_size = eaf->frame_size - eaf->pre_size;
|
||||
}
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* write PCM block directly to sample buffer and setup decode discard (see EALayer3). */
|
||||
static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, eamp3_frame_info * eaf) {
|
||||
mpeg_custom_stream *ms = data->streams[num_stream];
|
||||
size_t bytes_filled;
|
||||
int i;
|
||||
|
||||
|
||||
bytes_filled = sizeof(sample) * ms->samples_filled * data->channels_per_frame;
|
||||
if (bytes_filled + eaf->pcm_size > ms->output_buffer_size) {
|
||||
VGM_LOG("EAMP3: can't fill the sample buffer with 0x%x\n", eaf->pcm_size);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
if (eaf->pcm_number) {
|
||||
|
||||
/* read + write PCM block samples (always LE) */
|
||||
for (i = 0; i < eaf->pcm_number * data->channels_per_frame; i++) {
|
||||
off_t pcm_offset = stream->offset + eaf->pre_size + eaf->mpeg_size + sizeof(sample)*i;
|
||||
int16_t pcm_sample = read_16bitLE(pcm_offset,stream->streamfile);
|
||||
put_16bitLE(ms->output_buffer + bytes_filled + sizeof(sample)*i, pcm_sample);
|
||||
}
|
||||
ms->samples_filled += eaf->pcm_number;
|
||||
|
||||
/* modify decoded samples */
|
||||
{
|
||||
size_t decode_to_discard = eaf->pcm_number; //todo guessed
|
||||
ms->decode_to_discard += decode_to_discard;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Skip EA-frames from other streams for .sns/sps multichannel (see EALayer3). */
|
||||
static int eamp3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start) {
|
||||
int ok, i;
|
||||
eamp3_frame_info eaf;
|
||||
int skips = at_start ? num_stream : data->streams_size - 1 - num_stream;
|
||||
|
||||
|
||||
for (i = 0; i < skips; i++) {
|
||||
ok = eamp3_parse_frame(stream, data, &eaf);
|
||||
if (!ok) goto fail;
|
||||
|
||||
//;VGM_LOG("s%i: skipping %x, now at %lx\n", num_stream,eaf.frame_size,stream->offset);
|
||||
stream->offset += eaf.frame_size;
|
||||
}
|
||||
//;VGM_LOG("s%i: skipped %i frames, now at %lx\n", num_stream,skips,stream->offset);
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,153 +1,153 @@
|
|||
#include "coding.h"
|
||||
#include "../util.h"
|
||||
|
||||
/* MTA2 decoder based on:
|
||||
* - MGS Developer Wiki: https://www.mgsdevwiki.com/wiki/index.php/MTA2_(Codec) [codec by daemon1]
|
||||
* - Solid4 tools: https://github.com/GHzGangster/Drebin
|
||||
* (PS3 probably uses floats, so this may not be 100% accurate)
|
||||
*
|
||||
* MTA2 layout:
|
||||
* - data is divided into N tracks of 0x10 header + 0x90 frame per track channel, forming N streams
|
||||
* ex: 8ch: track0 4ch + track1 4ch + track0 4ch + track1 4ch ...; or 2ch = 1ch track0 + 1ch track1
|
||||
* * up to 16 possible tracks, but max seen is 3 (ex. track0=sneaking, track1=action, track2=ambience)
|
||||
* - each ch frame is divided into 4 headers + 4 vertical groups with nibbles (0x4*4 + 0x20*4)
|
||||
* ex. group1 is 0x04(4) + 0x14(4) + 0x24(4) + 0x34(4) ... (vertically maybe for paralelism?)
|
||||
*
|
||||
* Due to this vertical layout and multiple hist/indexes, it decodes everything in a block between calls
|
||||
* but discards unwanted data, instead of trying to skip to the target nibble. Meaning no need to save hist, and
|
||||
* expects samples_to_do to be block_samples at most (could be simplified, I guess).
|
||||
*/
|
||||
|
||||
/* tweaked XA/PSX coefs << 8 */
|
||||
static const int16_t mta2_coefs[8][2] = {
|
||||
{ 0, 0 },
|
||||
{ 240, 0 },
|
||||
{ 460, -208 },
|
||||
{ 392, -220 },
|
||||
{ 488, -240 },
|
||||
{ 460, -240 },
|
||||
{ 460, -220 },
|
||||
{ 240, -104 }
|
||||
};
|
||||
|
||||
static const int mta2_scales[32] = {
|
||||
256, 335, 438, 573, 749, 979, 1281, 1675,
|
||||
2190, 2864, 3746, 4898, 6406, 8377, 10955, 14327,
|
||||
18736, 24503, 32043, 41905, 54802, 71668, 93724, 122568,
|
||||
160290, 209620, 274133, 358500, 468831, 613119, 801811, 1048576
|
||||
};
|
||||
|
||||
/* decodes a block for a channel */
|
||||
void decode_mta2(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
|
||||
uint8_t frame[0x10 + 0x90*8] = {0};
|
||||
int samples_done = 0, sample_count = 0, channel_block_samples, channel_first_sample, frame_size = 0;
|
||||
int i, group, row, col;
|
||||
int track_channels = 0, track_channel;
|
||||
|
||||
|
||||
/* track skip */
|
||||
do {
|
||||
int num_track = 0, channel_layout;
|
||||
|
||||
/* parse track header (0x10) and skip tracks that our current channel doesn't belong to */
|
||||
read_streamfile(frame, stream->offset, 0x10, stream->streamfile); /* ignore EOF errors */
|
||||
num_track = get_u8 (frame + 0x00); /* 0=first */
|
||||
/* 0x01(3): num_frame (0=first) */
|
||||
/* 0x04(1): 0? */
|
||||
channel_layout = get_u8 (frame + 0x05); /* bitmask, see mta2.c */
|
||||
frame_size = get_u16be(frame + 0x06); /* not including this header */
|
||||
/* 0x08(8): null */
|
||||
|
||||
VGM_ASSERT(frame_size == 0, "MTA2: empty frame at %x\n", (uint32_t)stream->offset);
|
||||
/* frame_size 0 means silent/empty frame (rarely found near EOF for one track but not others)
|
||||
* negative track only happens for truncated files (EOF) */
|
||||
if (frame_size == 0 || num_track < 0) {
|
||||
for (i = 0; i < samples_to_do; i++)
|
||||
outbuf[i * channelspacing] = 0;
|
||||
stream->offset += 0x10;
|
||||
return;
|
||||
}
|
||||
|
||||
track_channels = 0;
|
||||
for (i = 0; i < 8; i++) { /* max 8ch */
|
||||
if ((channel_layout >> i) & 0x01)
|
||||
track_channels++;
|
||||
}
|
||||
|
||||
if (track_channels == 0) { /* bad data, avoid div by 0 */
|
||||
VGM_LOG("MTA2: track_channels 0 at %x\n", (uint32_t)stream->offset);
|
||||
return;
|
||||
}
|
||||
|
||||
/* assumes tracks channels are divided evenly in all tracks (ex. not 2ch + 1ch + 1ch) */
|
||||
if (channel / track_channels == num_track)
|
||||
break; /* channel belongs to this track */
|
||||
|
||||
/* keep looping for our track */
|
||||
stream->offset += 0x10 + frame_size;
|
||||
}
|
||||
while (1);
|
||||
|
||||
/* parse stuff */
|
||||
read_streamfile(frame + 0x10, stream->offset + 0x10, frame_size, stream->streamfile); /* ignore EOF errors */
|
||||
track_channel = channel % track_channels;
|
||||
channel_block_samples = (0x80*2);
|
||||
channel_first_sample = first_sample % (0x80*2);
|
||||
|
||||
/* parse channel frame (header 0x04*4 + data 0x20*4) */
|
||||
for (group = 0; group < 4; group++) {
|
||||
short hist2, hist1, coefs, scale;
|
||||
uint32_t group_header = get_u32be(frame + 0x10 + track_channel*0x90 + group*0x4);
|
||||
hist2 = (short) ((group_header >> 16) & 0xfff0); /* upper 16b discarding 4b */
|
||||
hist1 = (short) ((group_header >> 4) & 0xfff0); /* lower 16b discarding 4b */
|
||||
coefs = (group_header >> 5) & 0x7; /* mid 3b */
|
||||
scale = group_header & 0x1f; /* lower 5b */
|
||||
|
||||
/* write header samples (skips the last 2 group nibbles), like Drebin's decoder
|
||||
* last 2 nibbles and next 2 header hist should match though */
|
||||
if (sample_count >= channel_first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist2;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
if (sample_count >= channel_first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist1;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
/* decode nibbles */
|
||||
for (row = 0; row < 8; row++) {
|
||||
int pos = 0x10 + track_channel*0x90 + 0x10 + group*0x4 + row*0x10;
|
||||
for (col = 0; col < 4*2; col++) {
|
||||
uint8_t nibbles = frame[pos + col/2];
|
||||
int32_t sample;
|
||||
|
||||
sample = col&1 ? /* high nibble first */
|
||||
get_low_nibble_signed(nibbles) :
|
||||
get_high_nibble_signed(nibbles);
|
||||
sample = sample * mta2_scales[scale];
|
||||
sample = (sample + hist1 * mta2_coefs[coefs][0] + hist2 * mta2_coefs[coefs][1] + 128) >> 8;
|
||||
sample = clamp16(sample);
|
||||
|
||||
/* ignore last 2 nibbles (uses first 2 header samples) */
|
||||
if (row < 7 || col < 3*2) {
|
||||
if (sample_count >= channel_first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = sample;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
}
|
||||
|
||||
hist2 = hist1;
|
||||
hist1 = sample;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* block fully done */
|
||||
if (channel_first_sample + samples_done == channel_block_samples) {
|
||||
stream->offset += 0x10 + frame_size;
|
||||
}
|
||||
}
|
||||
#include "coding.h"
|
||||
#include "../util.h"
|
||||
|
||||
/* MTA2 decoder based on:
|
||||
* - MGS Developer Wiki: https://www.mgsdevwiki.com/wiki/index.php/MTA2_(Codec) [codec by daemon1]
|
||||
* - Solid4 tools: https://github.com/GHzGangster/Drebin
|
||||
* (PS3 probably uses floats, so this may not be 100% accurate)
|
||||
*
|
||||
* MTA2 layout:
|
||||
* - data is divided into N tracks of 0x10 header + 0x90 frame per track channel, forming N streams
|
||||
* ex: 8ch: track0 4ch + track1 4ch + track0 4ch + track1 4ch ...; or 2ch = 1ch track0 + 1ch track1
|
||||
* * up to 16 possible tracks, but max seen is 3 (ex. track0=sneaking, track1=action, track2=ambience)
|
||||
* - each ch frame is divided into 4 headers + 4 vertical groups with nibbles (0x4*4 + 0x20*4)
|
||||
* ex. group1 is 0x04(4) + 0x14(4) + 0x24(4) + 0x34(4) ... (vertically maybe for paralelism?)
|
||||
*
|
||||
* Due to this vertical layout and multiple hist/indexes, it decodes everything in a block between calls
|
||||
* but discards unwanted data, instead of trying to skip to the target nibble. Meaning no need to save hist, and
|
||||
* expects samples_to_do to be block_samples at most (could be simplified, I guess).
|
||||
*/
|
||||
|
||||
/* tweaked XA/PSX coefs << 8 */
|
||||
static const int16_t mta2_coefs[8][2] = {
|
||||
{ 0, 0 },
|
||||
{ 240, 0 },
|
||||
{ 460, -208 },
|
||||
{ 392, -220 },
|
||||
{ 488, -240 },
|
||||
{ 460, -240 },
|
||||
{ 460, -220 },
|
||||
{ 240, -104 }
|
||||
};
|
||||
|
||||
static const int mta2_scales[32] = {
|
||||
256, 335, 438, 573, 749, 979, 1281, 1675,
|
||||
2190, 2864, 3746, 4898, 6406, 8377, 10955, 14327,
|
||||
18736, 24503, 32043, 41905, 54802, 71668, 93724, 122568,
|
||||
160290, 209620, 274133, 358500, 468831, 613119, 801811, 1048576
|
||||
};
|
||||
|
||||
/* decodes a block for a channel */
|
||||
void decode_mta2(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
|
||||
uint8_t frame[0x10 + 0x90*8] = {0};
|
||||
int samples_done = 0, sample_count = 0, channel_block_samples, channel_first_sample, frame_size = 0;
|
||||
int i, group, row, col;
|
||||
int track_channels = 0, track_channel;
|
||||
|
||||
|
||||
/* track skip */
|
||||
do {
|
||||
int num_track = 0, channel_layout;
|
||||
|
||||
/* parse track header (0x10) and skip tracks that our current channel doesn't belong to */
|
||||
read_streamfile(frame, stream->offset, 0x10, stream->streamfile); /* ignore EOF errors */
|
||||
num_track = get_u8 (frame + 0x00); /* 0=first */
|
||||
/* 0x01(3): num_frame (0=first) */
|
||||
/* 0x04(1): 0? */
|
||||
channel_layout = get_u8 (frame + 0x05); /* bitmask, see mta2.c */
|
||||
frame_size = get_u16be(frame + 0x06); /* not including this header */
|
||||
/* 0x08(8): null */
|
||||
|
||||
VGM_ASSERT(frame_size == 0, "MTA2: empty frame at %x\n", (uint32_t)stream->offset);
|
||||
/* frame_size 0 means silent/empty frame (rarely found near EOF for one track but not others)
|
||||
* negative track only happens for truncated files (EOF) */
|
||||
if (frame_size == 0 || num_track < 0) {
|
||||
for (i = 0; i < samples_to_do; i++)
|
||||
outbuf[i * channelspacing] = 0;
|
||||
stream->offset += 0x10;
|
||||
return;
|
||||
}
|
||||
|
||||
track_channels = 0;
|
||||
for (i = 0; i < 8; i++) { /* max 8ch */
|
||||
if ((channel_layout >> i) & 0x01)
|
||||
track_channels++;
|
||||
}
|
||||
|
||||
if (track_channels == 0) { /* bad data, avoid div by 0 */
|
||||
VGM_LOG("MTA2: track_channels 0 at %x\n", (uint32_t)stream->offset);
|
||||
return;
|
||||
}
|
||||
|
||||
/* assumes tracks channels are divided evenly in all tracks (ex. not 2ch + 1ch + 1ch) */
|
||||
if (channel / track_channels == num_track)
|
||||
break; /* channel belongs to this track */
|
||||
|
||||
/* keep looping for our track */
|
||||
stream->offset += 0x10 + frame_size;
|
||||
}
|
||||
while (1);
|
||||
|
||||
/* parse stuff */
|
||||
read_streamfile(frame + 0x10, stream->offset + 0x10, frame_size, stream->streamfile); /* ignore EOF errors */
|
||||
track_channel = channel % track_channels;
|
||||
channel_block_samples = (0x80*2);
|
||||
channel_first_sample = first_sample % (0x80*2);
|
||||
|
||||
/* parse channel frame (header 0x04*4 + data 0x20*4) */
|
||||
for (group = 0; group < 4; group++) {
|
||||
short hist2, hist1, coefs, scale;
|
||||
uint32_t group_header = get_u32be(frame + 0x10 + track_channel*0x90 + group*0x4);
|
||||
hist2 = (short) ((group_header >> 16) & 0xfff0); /* upper 16b discarding 4b */
|
||||
hist1 = (short) ((group_header >> 4) & 0xfff0); /* lower 16b discarding 4b */
|
||||
coefs = (group_header >> 5) & 0x7; /* mid 3b */
|
||||
scale = group_header & 0x1f; /* lower 5b */
|
||||
|
||||
/* write header samples (skips the last 2 group nibbles), like Drebin's decoder
|
||||
* last 2 nibbles and next 2 header hist should match though */
|
||||
if (sample_count >= channel_first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist2;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
if (sample_count >= channel_first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist1;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
/* decode nibbles */
|
||||
for (row = 0; row < 8; row++) {
|
||||
int pos = 0x10 + track_channel*0x90 + 0x10 + group*0x4 + row*0x10;
|
||||
for (col = 0; col < 4*2; col++) {
|
||||
uint8_t nibbles = frame[pos + col/2];
|
||||
int32_t sample;
|
||||
|
||||
sample = col&1 ? /* high nibble first */
|
||||
get_low_nibble_signed(nibbles) :
|
||||
get_high_nibble_signed(nibbles);
|
||||
sample = sample * mta2_scales[scale];
|
||||
sample = (sample + hist1 * mta2_coefs[coefs][0] + hist2 * mta2_coefs[coefs][1] + 128) >> 8;
|
||||
sample = clamp16(sample);
|
||||
|
||||
/* ignore last 2 nibbles (uses first 2 header samples) */
|
||||
if (row < 7 || col < 3*2) {
|
||||
if (sample_count >= channel_first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = sample;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
}
|
||||
|
||||
hist2 = hist1;
|
||||
hist1 = sample;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* block fully done */
|
||||
if (channel_first_sample + samples_done == channel_block_samples) {
|
||||
stream->offset += 0x10 + frame_size;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -239,7 +239,7 @@ static int ps_find_loop_offsets_internal(STREAMFILE *streamFile, off_t start_off
|
|||
int detect_full_loops = config & 1;
|
||||
|
||||
|
||||
if (data_size == 0 || channels == 0 || (channels > 0 && interleave == 0))
|
||||
if (data_size == 0 || channels == 0 || (channels > 1 && interleave == 0))
|
||||
return 0;
|
||||
|
||||
while (offset < max_offset) {
|
||||
|
|
|
@ -1,112 +1,112 @@
|
|||
#include "coding.h"
|
||||
|
||||
|
||||
/* a somewhat IMA-like mix of pre-calculated [index][nibble][step,index] all in one */
|
||||
static const int32_t ptadpcm_table[16][16][2] = {
|
||||
{
|
||||
{ -14, 2}, { -10, 2}, { -7, 1}, { -5, 1}, { -3, 0}, { -2, 0}, { -1, 0}, { 0, 0},
|
||||
{ 0, 0}, { 1, 0}, { 2, 0}, { 3, 0}, { 5, 1}, { 7, 1}, { 10, 2}, { 14, 2},
|
||||
}, {
|
||||
{ -28, 3}, { -20, 3}, { -14, 2}, { -10, 2}, { -7, 1}, { -5, 1}, { -3, 1}, { -1, 0},
|
||||
{ 1, 0}, { 3, 1}, { 5, 1}, { 7, 1}, { 10, 2}, { 14, 2}, { 20, 3}, { 28, 3},
|
||||
}, {
|
||||
{ -56, 4}, { -40, 4}, { -28, 3}, { -20, 3}, { -14, 2}, { -10, 2}, { -6, 2}, { -2, 1},
|
||||
{ 2, 1}, { 6, 2}, { 10, 2}, { 14, 2}, { 20, 3}, { 28, 3}, { 40, 4}, { 56, 4},
|
||||
}, {
|
||||
{ -112, 5}, { -80, 5}, { -56, 4}, { -40, 4}, { -28, 3}, { -20, 3}, { -12, 3}, { -4, 2},
|
||||
{ 4, 2}, { 12, 3}, { 20, 3}, { 28, 3}, { 40, 4}, { 56, 4}, { 80, 5}, { 112, 5},
|
||||
}, {
|
||||
{ -224, 6}, { -160, 6}, { -112, 5}, { -80, 5}, { -56, 4}, { -40, 4}, { -24, 4}, { -8, 3},
|
||||
{ 8, 3}, { 24, 4}, { 40, 4}, { 56, 4}, { 80, 5}, { 112, 5}, { 160, 6}, { 224, 6},
|
||||
}, {
|
||||
{ -448, 7}, { -320, 7}, { -224, 6}, { -160, 6}, { -112, 5}, { -80, 5}, { -48, 5}, { -16, 4},
|
||||
{ 16, 4}, { 48, 5}, { 80, 5}, { 112, 5}, { 160, 6}, { 224, 6}, { 320, 7}, { 448, 7},
|
||||
}, {
|
||||
{ -896, 8}, { -640, 8}, { -448, 7}, { -320, 7}, { -224, 6}, { -160, 6}, { -96, 6}, { -32, 5},
|
||||
{ 32, 5}, { 96, 6}, { 160, 6}, { 224, 6}, { 320, 7}, { 448, 7}, { 640, 8}, { 896, 8},
|
||||
}, {
|
||||
{ -1792, 9}, { -1280, 9}, { -896, 8}, { -640, 8}, { -448, 7}, { -320, 7}, { -192, 7}, { -64, 6},
|
||||
{ 64, 6}, { 192, 7}, { 320, 7}, { 448, 7}, { 640, 8}, { 896, 8}, { 1280, 9}, { 1792, 9},
|
||||
}, {
|
||||
{ -3584, 10}, { -2560, 10}, { -1792, 9}, { -1280, 9}, { -896, 8}, { -640, 8}, { -384, 8}, { -128, 7},
|
||||
{ 128, 7}, { 384, 8}, { 640, 8}, { 896, 8}, { 1280, 9}, { 1792, 9}, { 2560, 10}, { 3584, 10},
|
||||
}, {
|
||||
{ -7168, 11}, { -5120, 11}, { -3584, 10}, { -2560, 10}, {-1792, 9}, {-1280, 9}, { -768, 9}, { -256, 8},
|
||||
{ 256, 8}, { 768, 9}, { 1280, 9}, { 1792, 9}, { 2560, 10}, { 3584, 10}, { 5120, 11}, { 7168, 11},
|
||||
}, {
|
||||
{-14336, 11}, {-10240, 11}, { -7168, 11}, { -5120, 11}, {-3584, 10}, {-2560, 10}, {-1536, 10}, { -512, 9},
|
||||
{ 512, 9}, { 1536, 10}, { 2560, 10}, { 3584, 10}, { 5120, 11}, { 7168, 11}, {10240, 11}, {14336, 11},
|
||||
}, {
|
||||
{-28672, 11}, {-20480, 11}, {-14336, 11}, {-10240, 11}, {-7168, 11}, {-5120, 11}, {-3072, 11}, {-1024, 10},
|
||||
{ 1024, 10}, { 3072, 11}, { 5120, 11}, { 7168, 11}, {10240, 11}, {14336, 11}, {20480, 11}, {28672, 11},
|
||||
},
|
||||
/* rest is 0s (uses up to index 12) */
|
||||
};
|
||||
|
||||
/* Platinum "PtADPCM" custom ADPCM for Wwise (reverse engineered from .exes). */
|
||||
void decode_ptadpcm(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size) {
|
||||
uint8_t frame[0x104] = {0};
|
||||
off_t frame_offset;
|
||||
int i, frames_in, sample_count = 0, samples_done = 0;
|
||||
size_t bytes_per_frame, samples_per_frame;
|
||||
int16_t hist1, hist2;
|
||||
int index, nibble, step;
|
||||
|
||||
/* external interleave (variable size), mono */
|
||||
bytes_per_frame = frame_size;
|
||||
samples_per_frame = 2 + (frame_size - 0x05) * 2;
|
||||
frames_in = first_sample / samples_per_frame;
|
||||
//first_sample = first_sample % samples_per_frame;
|
||||
|
||||
/* parse frame header */
|
||||
frame_offset = stream->offset + bytes_per_frame*frames_in;
|
||||
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
|
||||
hist2 = get_s16le(frame + 0x00);
|
||||
hist1 = get_s16le(frame + 0x02);
|
||||
index = frame[0x04];
|
||||
|
||||
VGM_ASSERT_ONCE(index > 12, "PTADPCM: incorrect index at %x\n", (uint32_t)frame_offset);
|
||||
|
||||
/* write header samples (needed) */
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist2;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist1;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
/* decode nibbles */
|
||||
for (i = first_sample; i < first_sample + samples_to_do; i++) {
|
||||
uint8_t nibbles = frame[0x05 + i/2];
|
||||
int32_t sample;
|
||||
|
||||
nibble = !(i&1) ? /* low nibble first */
|
||||
(nibbles >> 0) & 0xF :
|
||||
(nibbles >> 4) & 0xF;
|
||||
|
||||
step = ptadpcm_table[index][nibble][0];
|
||||
index = ptadpcm_table[index][nibble][1];
|
||||
sample = clamp16(step + 2*hist1 - hist2);
|
||||
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = sample;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
hist2 = hist1;
|
||||
hist1 = sample;
|
||||
}
|
||||
|
||||
//stream->adpcm_history1_32 = hist1;
|
||||
//stream->adpcm_history2_32 = hist2;
|
||||
}
|
||||
|
||||
size_t ptadpcm_bytes_to_samples(size_t bytes, int channels, size_t frame_size) {
|
||||
if (channels <= 0 || frame_size < 0x06) return 0;
|
||||
return (bytes / channels / frame_size) * ((frame_size-0x05) * 2);
|
||||
}
|
||||
#include "coding.h"
|
||||
|
||||
|
||||
/* a somewhat IMA-like mix of pre-calculated [index][nibble][step,index] all in one */
|
||||
static const int32_t ptadpcm_table[16][16][2] = {
|
||||
{
|
||||
{ -14, 2}, { -10, 2}, { -7, 1}, { -5, 1}, { -3, 0}, { -2, 0}, { -1, 0}, { 0, 0},
|
||||
{ 0, 0}, { 1, 0}, { 2, 0}, { 3, 0}, { 5, 1}, { 7, 1}, { 10, 2}, { 14, 2},
|
||||
}, {
|
||||
{ -28, 3}, { -20, 3}, { -14, 2}, { -10, 2}, { -7, 1}, { -5, 1}, { -3, 1}, { -1, 0},
|
||||
{ 1, 0}, { 3, 1}, { 5, 1}, { 7, 1}, { 10, 2}, { 14, 2}, { 20, 3}, { 28, 3},
|
||||
}, {
|
||||
{ -56, 4}, { -40, 4}, { -28, 3}, { -20, 3}, { -14, 2}, { -10, 2}, { -6, 2}, { -2, 1},
|
||||
{ 2, 1}, { 6, 2}, { 10, 2}, { 14, 2}, { 20, 3}, { 28, 3}, { 40, 4}, { 56, 4},
|
||||
}, {
|
||||
{ -112, 5}, { -80, 5}, { -56, 4}, { -40, 4}, { -28, 3}, { -20, 3}, { -12, 3}, { -4, 2},
|
||||
{ 4, 2}, { 12, 3}, { 20, 3}, { 28, 3}, { 40, 4}, { 56, 4}, { 80, 5}, { 112, 5},
|
||||
}, {
|
||||
{ -224, 6}, { -160, 6}, { -112, 5}, { -80, 5}, { -56, 4}, { -40, 4}, { -24, 4}, { -8, 3},
|
||||
{ 8, 3}, { 24, 4}, { 40, 4}, { 56, 4}, { 80, 5}, { 112, 5}, { 160, 6}, { 224, 6},
|
||||
}, {
|
||||
{ -448, 7}, { -320, 7}, { -224, 6}, { -160, 6}, { -112, 5}, { -80, 5}, { -48, 5}, { -16, 4},
|
||||
{ 16, 4}, { 48, 5}, { 80, 5}, { 112, 5}, { 160, 6}, { 224, 6}, { 320, 7}, { 448, 7},
|
||||
}, {
|
||||
{ -896, 8}, { -640, 8}, { -448, 7}, { -320, 7}, { -224, 6}, { -160, 6}, { -96, 6}, { -32, 5},
|
||||
{ 32, 5}, { 96, 6}, { 160, 6}, { 224, 6}, { 320, 7}, { 448, 7}, { 640, 8}, { 896, 8},
|
||||
}, {
|
||||
{ -1792, 9}, { -1280, 9}, { -896, 8}, { -640, 8}, { -448, 7}, { -320, 7}, { -192, 7}, { -64, 6},
|
||||
{ 64, 6}, { 192, 7}, { 320, 7}, { 448, 7}, { 640, 8}, { 896, 8}, { 1280, 9}, { 1792, 9},
|
||||
}, {
|
||||
{ -3584, 10}, { -2560, 10}, { -1792, 9}, { -1280, 9}, { -896, 8}, { -640, 8}, { -384, 8}, { -128, 7},
|
||||
{ 128, 7}, { 384, 8}, { 640, 8}, { 896, 8}, { 1280, 9}, { 1792, 9}, { 2560, 10}, { 3584, 10},
|
||||
}, {
|
||||
{ -7168, 11}, { -5120, 11}, { -3584, 10}, { -2560, 10}, {-1792, 9}, {-1280, 9}, { -768, 9}, { -256, 8},
|
||||
{ 256, 8}, { 768, 9}, { 1280, 9}, { 1792, 9}, { 2560, 10}, { 3584, 10}, { 5120, 11}, { 7168, 11},
|
||||
}, {
|
||||
{-14336, 11}, {-10240, 11}, { -7168, 11}, { -5120, 11}, {-3584, 10}, {-2560, 10}, {-1536, 10}, { -512, 9},
|
||||
{ 512, 9}, { 1536, 10}, { 2560, 10}, { 3584, 10}, { 5120, 11}, { 7168, 11}, {10240, 11}, {14336, 11},
|
||||
}, {
|
||||
{-28672, 11}, {-20480, 11}, {-14336, 11}, {-10240, 11}, {-7168, 11}, {-5120, 11}, {-3072, 11}, {-1024, 10},
|
||||
{ 1024, 10}, { 3072, 11}, { 5120, 11}, { 7168, 11}, {10240, 11}, {14336, 11}, {20480, 11}, {28672, 11},
|
||||
},
|
||||
/* rest is 0s (uses up to index 12) */
|
||||
};
|
||||
|
||||
/* Platinum "PtADPCM" custom ADPCM for Wwise (reverse engineered from .exes). */
|
||||
void decode_ptadpcm(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size) {
|
||||
uint8_t frame[0x104] = {0};
|
||||
off_t frame_offset;
|
||||
int i, frames_in, sample_count = 0, samples_done = 0;
|
||||
size_t bytes_per_frame, samples_per_frame;
|
||||
int16_t hist1, hist2;
|
||||
int index, nibble, step;
|
||||
|
||||
/* external interleave (variable size), mono */
|
||||
bytes_per_frame = frame_size;
|
||||
samples_per_frame = 2 + (frame_size - 0x05) * 2;
|
||||
frames_in = first_sample / samples_per_frame;
|
||||
//first_sample = first_sample % samples_per_frame;
|
||||
|
||||
/* parse frame header */
|
||||
frame_offset = stream->offset + bytes_per_frame*frames_in;
|
||||
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
|
||||
hist2 = get_s16le(frame + 0x00);
|
||||
hist1 = get_s16le(frame + 0x02);
|
||||
index = frame[0x04];
|
||||
|
||||
VGM_ASSERT_ONCE(index > 12, "PTADPCM: incorrect index at %x\n", (uint32_t)frame_offset);
|
||||
|
||||
/* write header samples (needed) */
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist2;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist1;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
/* decode nibbles */
|
||||
for (i = first_sample; i < first_sample + samples_to_do; i++) {
|
||||
uint8_t nibbles = frame[0x05 + i/2];
|
||||
int32_t sample;
|
||||
|
||||
nibble = !(i&1) ? /* low nibble first */
|
||||
(nibbles >> 0) & 0xF :
|
||||
(nibbles >> 4) & 0xF;
|
||||
|
||||
step = ptadpcm_table[index][nibble][0];
|
||||
index = ptadpcm_table[index][nibble][1];
|
||||
sample = clamp16(step + 2*hist1 - hist2);
|
||||
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = sample;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
hist2 = hist1;
|
||||
hist1 = sample;
|
||||
}
|
||||
|
||||
//stream->adpcm_history1_32 = hist1;
|
||||
//stream->adpcm_history2_32 = hist2;
|
||||
}
|
||||
|
||||
size_t ptadpcm_bytes_to_samples(size_t bytes, int channels, size_t frame_size) {
|
||||
if (channels <= 0 || frame_size < 0x06) return 0;
|
||||
return (bytes / channels / frame_size) * ((frame_size-0x05) * 2);
|
||||
}
|
||||
|
|
|
@ -1,65 +1,65 @@
|
|||
#include "coding.h"
|
||||
|
||||
|
||||
/* Decodes Konami XMD from Xbox games.
|
||||
* Algorithm reverse engineered from SH4/CV:CoD's xbe (byte-accurate). */
|
||||
void decode_xmd(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size) {
|
||||
uint8_t frame[0x15] = {0};
|
||||
off_t frame_offset;
|
||||
int i, frames_in, sample_count = 0, samples_done = 0;
|
||||
size_t bytes_per_frame, samples_per_frame;
|
||||
int16_t hist1, hist2;
|
||||
uint16_t scale;
|
||||
|
||||
|
||||
/* external interleave (variable size), mono */
|
||||
bytes_per_frame = frame_size;
|
||||
samples_per_frame = 2 + (frame_size - 0x06) * 2;
|
||||
frames_in = first_sample / samples_per_frame;
|
||||
//first_sample = first_sample % samples_per_frame; /* for flat layout */
|
||||
|
||||
/* parse frame header */
|
||||
frame_offset = stream->offset + bytes_per_frame * frames_in;
|
||||
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
|
||||
hist2 = get_s16le(frame + 0x00);
|
||||
hist1 = get_s16le(frame + 0x02);
|
||||
scale = get_u16le(frame + 0x04); /* scale doesn't go too high though */
|
||||
|
||||
/* write header samples (needed) */
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist2;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist1;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
/* decode nibbles */
|
||||
for (i = first_sample; i < first_sample + samples_to_do; i++) {
|
||||
uint8_t nibbles = frame[0x06 + i/2];
|
||||
int32_t sample;
|
||||
|
||||
sample = i&1 ? /* low nibble first */
|
||||
get_high_nibble_signed(nibbles):
|
||||
get_low_nibble_signed(nibbles);
|
||||
/* Coefs are based on XA's filter 2 (using those creates hissing in some songs though)
|
||||
* ex. 1.796875 * (1 << 14) = 0x7300, -0.8125 * (1 << 14) = -0x3400 */
|
||||
sample = (sample*(scale<<14) + (hist1*0x7298) - (hist2*0x3350)) >> 14;
|
||||
|
||||
//new_sample = clamp16(new_sample); /* not needed */
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = (int16_t)sample;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
hist2 = hist1;
|
||||
hist1 = sample;
|
||||
}
|
||||
|
||||
//stream->adpcm_history1_32 = hist1;
|
||||
//stream->adpcm_history2_32 = hist2;
|
||||
}
|
||||
#include "coding.h"
|
||||
|
||||
|
||||
/* Decodes Konami XMD from Xbox games.
|
||||
* Algorithm reverse engineered from SH4/CV:CoD's xbe (byte-accurate). */
|
||||
void decode_xmd(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size) {
|
||||
uint8_t frame[0x15] = {0};
|
||||
off_t frame_offset;
|
||||
int i, frames_in, sample_count = 0, samples_done = 0;
|
||||
size_t bytes_per_frame, samples_per_frame;
|
||||
int16_t hist1, hist2;
|
||||
uint16_t scale;
|
||||
|
||||
|
||||
/* external interleave (variable size), mono */
|
||||
bytes_per_frame = frame_size;
|
||||
samples_per_frame = 2 + (frame_size - 0x06) * 2;
|
||||
frames_in = first_sample / samples_per_frame;
|
||||
//first_sample = first_sample % samples_per_frame; /* for flat layout */
|
||||
|
||||
/* parse frame header */
|
||||
frame_offset = stream->offset + bytes_per_frame * frames_in;
|
||||
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
|
||||
hist2 = get_s16le(frame + 0x00);
|
||||
hist1 = get_s16le(frame + 0x02);
|
||||
scale = get_u16le(frame + 0x04); /* scale doesn't go too high though */
|
||||
|
||||
/* write header samples (needed) */
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist2;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist1;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
/* decode nibbles */
|
||||
for (i = first_sample; i < first_sample + samples_to_do; i++) {
|
||||
uint8_t nibbles = frame[0x06 + i/2];
|
||||
int32_t sample;
|
||||
|
||||
sample = i&1 ? /* low nibble first */
|
||||
get_high_nibble_signed(nibbles):
|
||||
get_low_nibble_signed(nibbles);
|
||||
/* Coefs are based on XA's filter 2 (using those creates hissing in some songs though)
|
||||
* ex. 1.796875 * (1 << 14) = 0x7300, -0.8125 * (1 << 14) = -0x3400 */
|
||||
sample = (sample*(scale<<14) + (hist1*0x7298) - (hist2*0x3350)) >> 14;
|
||||
|
||||
//new_sample = clamp16(new_sample); /* not needed */
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = (int16_t)sample;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
hist2 = hist1;
|
||||
hist1 = sample;
|
||||
}
|
||||
|
||||
//stream->adpcm_history1_32 = hist1;
|
||||
//stream->adpcm_history2_32 = hist2;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -24,7 +24,7 @@ void block_update_ea_sns(off_t block_offset, VGMSTREAM * vgmstream) {
|
|||
/* At 0x00(1): block flag
|
||||
* - in SNS: 0x00=normal block, 0x80=last block (not mandatory)
|
||||
* - in SPS: 0x48=header, 0x44=normal block, 0x45=last block (empty) */
|
||||
block_id = (block_size & 0x00FFFFFF) >> 24;
|
||||
block_id = (block_size & 0xFF000000) >> 24;
|
||||
block_size &= 0x00FFFFFF;
|
||||
|
||||
if (block_id == 0x00 || block_id == 0x80 || block_id == 0x44) {
|
||||
|
|
|
@ -2,30 +2,40 @@
|
|||
#include "../vgmstream.h"
|
||||
|
||||
/* set up for the block at the given offset */
|
||||
void block_update_thp(off_t block_offset, VGMSTREAM * vgmstream) {
|
||||
int i,j;
|
||||
STREAMFILE *streamFile=vgmstream->ch[0].streamfile;
|
||||
off_t start_offset;
|
||||
int32_t nextFrameSize;
|
||||
void block_update_thp(off_t block_offset, VGMSTREAM *vgmstream) {
|
||||
int i, j;
|
||||
STREAMFILE *streamFile = vgmstream->ch[0].streamfile;
|
||||
off_t audio_offset;
|
||||
size_t next_block_size, video_size;
|
||||
|
||||
vgmstream->current_block_offset = block_offset;
|
||||
nextFrameSize=read_32bitBE(vgmstream->current_block_offset,streamFile);
|
||||
next_block_size = read_32bitBE(block_offset + 0x00, streamFile);
|
||||
/* 0x04: frame size previous */
|
||||
video_size = read_32bitBE(block_offset + 0x08,streamFile);
|
||||
/* 0x0c: audio size */
|
||||
|
||||
vgmstream->next_block_offset = vgmstream->current_block_offset
|
||||
+ vgmstream->full_block_size;
|
||||
vgmstream->full_block_size = nextFrameSize;
|
||||
audio_offset = block_offset + 0x10 + video_size;
|
||||
|
||||
start_offset=vgmstream->current_block_offset
|
||||
+ read_32bitBE(vgmstream->current_block_offset+0x08,streamFile)+0x10;
|
||||
vgmstream->current_block_size=read_32bitBE(start_offset,streamFile);
|
||||
start_offset+=8;
|
||||
vgmstream->current_block_offset = block_offset;
|
||||
vgmstream->next_block_offset = block_offset + vgmstream->full_block_size;
|
||||
vgmstream->full_block_size = next_block_size;
|
||||
|
||||
for(i=0;i<vgmstream->channels;i++) {
|
||||
for(j=0;j<16;j++) {
|
||||
vgmstream->ch[i].adpcm_coef[j]=read_16bitBE(start_offset+(i*0x20)+(j*2),streamFile);
|
||||
}
|
||||
vgmstream->ch[i].adpcm_history1_16=read_16bitBE(start_offset + (0x20*vgmstream->channels) + (i*4),streamFile);
|
||||
vgmstream->ch[i].adpcm_history2_16=read_16bitBE(start_offset + (0x20*vgmstream->channels) + (i*4) + 2,streamFile);
|
||||
vgmstream->ch[i].offset = start_offset + (0x24*vgmstream->channels)+(i*vgmstream->current_block_size);
|
||||
}
|
||||
/* block samples can be smaller than block size, normally in the last block,
|
||||
* but num_samples already takes that into account, so there is no real difference */
|
||||
vgmstream->current_block_size = read_32bitBE(audio_offset + 0x00, streamFile);
|
||||
vgmstream->current_block_samples = read_32bitBE(audio_offset + 0x04, streamFile);
|
||||
|
||||
audio_offset += 0x08;
|
||||
|
||||
for (i = 0; i < vgmstream->channels; i++) {
|
||||
off_t coef_offset = audio_offset + i*0x20;
|
||||
off_t hist_offset = audio_offset + vgmstream->channels*0x20 + i*0x04;
|
||||
off_t data_offset = audio_offset + vgmstream->channels*0x24 + i*vgmstream->current_block_size;
|
||||
|
||||
for (j = 0; j < 16; j++) {
|
||||
vgmstream->ch[i].adpcm_coef[j] = read_16bitBE(coef_offset + (j*0x02),streamFile);
|
||||
}
|
||||
vgmstream->ch[i].adpcm_history1_16 = read_16bitBE(hist_offset + 0x00,streamFile);
|
||||
vgmstream->ch[i].adpcm_history2_16 = read_16bitBE(hist_offset + 0x02,streamFile);
|
||||
vgmstream->ch[i].offset = data_offset;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,346 +1,352 @@
|
|||
#ifndef _ADX_KEYS_H_
|
||||
#define _ADX_KEYS_H_
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint16_t start,mult,add; /* XOR values derived from the actual key */
|
||||
char* key8; /* keystring used by type 8 encryption */
|
||||
uint64_t key9; /* keycode used by type 9 encryption */
|
||||
} adxkey_info;
|
||||
|
||||
/**
|
||||
* List of known keys, cracked from the sound files.
|
||||
* Keystrings (type 8) and keycodes (type 9) from executables / VGAudio / game's executables / 2ch.net.
|
||||
* Multiple keys may work for a game due to how they are derived.
|
||||
* start/mult/add are optional (0,0,0) if key8/9 are provided, but take priority if given.
|
||||
*/
|
||||
static const adxkey_info adxkey8_list[] = {
|
||||
|
||||
/* GOD HAND (PS2), Okami (PS2) [Clover Studio] */
|
||||
{0x49e1,0x4a57,0x553d, "karaage",0},
|
||||
|
||||
/* Blood+ (PS2) [Grasshopper Manufacture] */
|
||||
{0x5f5d,0x58bd,0x55ed, NULL,0}, // keystring not in ELF?
|
||||
|
||||
/* Killer7 (PS2) [Grasshopper Manufacture] */
|
||||
{0x50fb,0x5803,0x5701, "GHM",0},
|
||||
|
||||
/* Samurai Champloo (PS2) [Grasshopper Manufacture] */
|
||||
{0x4f3f,0x472f,0x562f, "GHMSC",0},
|
||||
|
||||
/* Raiden III (PS2) [Moss] */
|
||||
{0x66f5,0x58bd,0x4459, "(C)2005 MOSS LTD. BMW Z4",0},
|
||||
|
||||
/* Phantasy Star Universe (PC), Phantasy Star Universe: Ambition of the Illuminus (PS2) [Sonic Team] */
|
||||
{0x5deb,0x5f27,0x673f, "3x5k62bg9ptbwy",0},
|
||||
|
||||
/* Senko no Ronde [G.rev] */
|
||||
{0x46d3,0x5ced,0x474d, "ranatus",0},
|
||||
|
||||
/* NiGHTS: Journey of Dreams (Wii) [Sonic Team] */
|
||||
{0x440b,0x6539,0x5723, "sakakit4649",0},
|
||||
|
||||
/* unknown source */
|
||||
{0x586d,0x5d65,0x63eb, NULL,0}, // from guessadx (unique?)
|
||||
|
||||
/* Shuffle! On the Stage (PS2) [Navel] */
|
||||
{0x4969,0x5deb,0x467f, "SHUF",0},
|
||||
|
||||
/* Aoishiro (PS2) [Success] */
|
||||
{0x4d65,0x5eb7,0x5dfd, "wakasugi",0},
|
||||
|
||||
/* Sonic and the Black Knight (Wii) [Sonic Team] */
|
||||
{0x55b7,0x6191,0x5a77, "morio",0},
|
||||
|
||||
/* Amagami (PS2) [Enterbrain] */
|
||||
{0x5a17,0x509f,0x5bfd, "mituba",0}, /* also AHX key */
|
||||
|
||||
/* Yamasa Digi Portable: Matsuri no Tatsujin (PSP) [Yamasa] */
|
||||
{0x4c01,0x549d,0x676f, "7fa0xB9tw3",0},
|
||||
|
||||
/* Fragments Blue (PS2) [Kadokawa Shoten] */
|
||||
{0x5803,0x4555,0x47bf, "PIETA",0},
|
||||
|
||||
/* Soulcalibur IV (PS3) [Namco] */
|
||||
{0x59ed,0x4679,0x46c9, "SC4Test",0},
|
||||
|
||||
/* Senko no Ronde DUO (X360) [G.rev] */
|
||||
{0x6157,0x6809,0x4045, NULL,0}, // from guessadx
|
||||
|
||||
/* Nogizaka Haruka no Himitsu: Cosplay Hajimemashita (PS2) [Vridge] */
|
||||
{0x45af,0x5f27,0x52b1, "SKFHSIA",0},
|
||||
|
||||
/* Little Anchor (PS2) [D3 Publisher] */
|
||||
{0x5f65,0x5b3d,0x5f65, NULL,0}, // confirmed unique with guessadx
|
||||
|
||||
/* Hanayoi Romanesque: Ai to Kanashimi (PS2) [Marvelous] */
|
||||
{0x5563,0x5047,0x43ed, NULL,0}, // 2nd from guessadx, other was {0x5562,0x5047,0x1433}
|
||||
|
||||
/* Mobile Suit Gundam: Gundam vs. Gundam NEXT PLUS (PSP) [Capcom] */
|
||||
{0x4f7b,0x4fdb,0x5cbf, "CS-GGNX+",0},
|
||||
|
||||
/* Shoukan Shoujo: Elemental Girl Calling (PS2) [Bridge NetShop] */
|
||||
{0x4f7b,0x5071,0x4c61, "ELEMENGAL",0},
|
||||
|
||||
/* Rakushou! Pachi-Slot Sengen 6: Rio 2 Cruising Vanadis (PS2) [Net Corporation] */
|
||||
{0x53e9,0x586d,0x4eaf, NULL,0}, // confirmed unique with guessadx
|
||||
|
||||
/* Tears to Tiara Gaiden Avalon no Nazo (PS3) [Aquaplus] */
|
||||
{0x47e1,0x60e9,0x51c1, NULL,0}, // confirmed unique with guessadx
|
||||
|
||||
/* Neon Genesis Evangelion: Koutetsu no Girlfriend 2nd (PS2) [Broccoli] */
|
||||
{0x481d,0x4f25,0x5243, "eva2",0},
|
||||
|
||||
/* Futakoi Alternative (PS2) [Marvelous] */
|
||||
{0x413b,0x543b,0x57d1, "LOVLOV",0},
|
||||
|
||||
/* Gakuen Utopia: Manabi Straight! KiraKira Happy Festa! (PS2) [Marvelous] */
|
||||
{0x440b,0x4327,0x564b, "MANABIST",0},
|
||||
|
||||
/* Soshite Kono Uchuu ni Kirameku Kimi no Shi XXX (PS2) [Datam Polystar] */
|
||||
{0x5f5d,0x552b,0x5507, "DATAM-KK2",0},
|
||||
|
||||
/* Sakura Taisen: Atsuki Chishio Ni (PS2) [Sega] */
|
||||
{0x645d,0x6011,0x5c29, NULL,0}, // confirmed unique with guessadx
|
||||
|
||||
/* Sakura Taisen 3 ~Paris wa Moeteiru ka~ (PS2) [Sega] */
|
||||
{0x62ad,0x4b13,0x5957, NULL,0}, // confirmed unique with guessadx
|
||||
|
||||
/* Sotsugyou 2nd Generation (PS2) [Jinx] */
|
||||
{0x6305,0x509f,0x4c01, NULL,0}, // First guess from guessadx, other was {0x6307,0x509f,0x2ac5}
|
||||
|
||||
/* La Corda d'Oro (PSP) [Koei] */
|
||||
{0x55b7,0x67e5,0x5387, NULL,0}, // keystring not in ELF?
|
||||
|
||||
/* Nanatsuiro * Drops Pure!! (PS2) [Media Works] */
|
||||
{0x6731,0x645d,0x566b, NULL,0}, // confirmed unique with guessadx
|
||||
|
||||
/* Shakugan no Shana (PS2) [Vridge] */
|
||||
{0x5fc5,0x63d9,0x599f, "FUZETSU",0},
|
||||
|
||||
/* Uragiri wa Boku no Namae o Shitteiru (PS2) [Kadokawa Shoten] */
|
||||
{0x4c73,0x4d8d,0x5827, NULL,0}, // confirmed unique with guessadx
|
||||
|
||||
/* StormLover!! (PSP), StormLover Kai!! (PSP) [Vridge] */
|
||||
{0x5a11,0x67e5,0x6751, "HEXDPFMDKPQW",0}, /* unknown AHX key */
|
||||
|
||||
/* Sora no Otoshimono: DokiDoki Summer Vacation (PSP) [Kadokawa Shoten] */
|
||||
{0x5e75,0x4a89,0x4c61, "funen-gomi",0},
|
||||
|
||||
/* Boku wa Koukuu Kanseikan: Airport Hero Naha (PSP) [Sonic Powered] */
|
||||
{0x64ab,0x5297,0x632f, "sonic",0},
|
||||
|
||||
/* Lucky Star: Net Idol Meister (PSP) [Vridge, Kadokawa Shoten] */
|
||||
{0x4d81,0x5243,0x58c7, "JJOLIFJLE",0}, /* unknown AHX key */
|
||||
|
||||
/* Ishin Renka: Ryouma Gaiden (PSP) [Vridge] */
|
||||
{0x54d1,0x526d,0x5e8b, "LQAFJOIEJ",0}, /* unknown AHX key */
|
||||
|
||||
/* Lucky Star: Ryouou Gakuen Outousai Portable (PSP) [Vridge] */
|
||||
{0x4d05,0x663b,0x6343, "IUNOIRU",0}, /* unknown AHX key */
|
||||
|
||||
/* Marriage Royale: Prism Story (PSP) [Vridge] */
|
||||
{0x40a9,0x46b1,0x62ad, "ROYMAR",0}, /* unknown AHX key */
|
||||
|
||||
/* Nogizaka Haruka no Himitsu: Doujinshi Hajimemashita (PSP) [Vridge] */
|
||||
{0x4609,0x671f,0x4b65, "CLKMEOUHFLIE",0}, /* unknown AHX key */
|
||||
|
||||
/* Slotter Mania P: Mach Go Go Go III (PSP) [Dorart] */
|
||||
{0x41ef,0x463d,0x5507, "SGGK",0},
|
||||
|
||||
/* Nichijou: Uchuujin (PSP) [Vridge] */
|
||||
{0x4369,0x486d,0x5461, "LJLOUHIU787",0}, /* unknown AHX key */
|
||||
|
||||
/* R-15 Portable (PSP) [Kadokawa Shoten] */
|
||||
{0x6809,0x5fd5,0x5bb1, "R-15(Heart)Love",0},
|
||||
|
||||
/* Suzumiya Haruhi-chan no Mahjong (PSP) [Kadokawa Shoten] */
|
||||
{0x5c33,0x4133,0x4ce7, "bi88a#fas",0},
|
||||
|
||||
/* StormLover Natsu Koi!! (PSP) [Vridge] */
|
||||
{0x4133,0x5a01,0x5723, "LIKDFJUIDJOQ",0}, /* unknown AHX key */
|
||||
|
||||
/* Shounen Onmyouji: Tsubasa yo Ima, Sora e Kaere (PS2) [Kadokawa Shoten] */
|
||||
{0x55d9,0x46d3,0x5b01, "SONMYOJI",0},
|
||||
|
||||
/* Girls Bravo: Romance 15's (PS2) [Kadokawa Shoten] */
|
||||
{0x658f,0x4a89,0x5213, "GBRAVO",0},
|
||||
|
||||
/* Kashimashi! Girl Meets Girl: Hajimete no Natsu Monogatari (PS2) [Vridge] */
|
||||
{0x6109,0x5135,0x673f, "KASHIM",0},
|
||||
|
||||
/* Bakumatsu Renka: Karyuu Kenshiden (PS2) [Vridge] */
|
||||
{0x4919,0x612d,0x4919, "RENRENKA22",0},
|
||||
|
||||
/* Tensei Hakkenshi: Fuumaroku (PS2) [Vridge] */
|
||||
{0x5761,0x6283,0x4531, "HAKKEN",0},
|
||||
|
||||
/* Lucky Star: Ryouou Gakuen Outousai (PS2) [Vridge] */
|
||||
{0x481D,0x44F9,0x4E35, "LSTARPS2",0},
|
||||
|
||||
/* Bakumatsu Renka: Shinsengumi (PS2) [Vridge] */
|
||||
{0x5381,0x5701,0x665B, "SHINN",0},
|
||||
|
||||
/* Gintama Gin-san to Issho! Boku no Kabukichou Nikki (PS2) [Bandai Namco?] */
|
||||
{0x67CD,0x5CA7,0x655F, "gt25809",0},
|
||||
|
||||
};
|
||||
|
||||
static const adxkey_info adxkey9_list[] = {
|
||||
|
||||
/* Phantasy Star Online 2 */
|
||||
{0x07d2,0x1ec5,0x0c7f, NULL,0}, // guessed with degod
|
||||
|
||||
/* Dragon Ball Z: Dokkan Battle (Android/iOS) */
|
||||
{0x0003,0x0d19,0x043b, NULL,416383518}, // 0000000018D1821E
|
||||
|
||||
/* Kisou Ryouhei Gunhound EX (PSP) */
|
||||
{0x0005,0x0bcd,0x1add, NULL,683461999}, // 0000000028BCCD6F
|
||||
|
||||
/* Raramagi (Android) */
|
||||
{0x0000,0x2b99,0x3e33, NULL,45719322}, // 0000000002B99F1A (12160794 also works)
|
||||
|
||||
/* Sonic Runners (Android) */
|
||||
{0x0000,0x12fd,0x1fbd, NULL,19910623}, // 00000000012FCFDF
|
||||
|
||||
/* Fallen Princess (iOS/Android) */
|
||||
{0x5e4b,0x190d,0x76bb, NULL,145552191146490718}, // 02051AF25990FB5E
|
||||
|
||||
/* Yuuki Yuuna wa Yuusha de aru: Hanayui no Kirameki / Yuyuyui (iOS/Android) */
|
||||
{0x3f10,0x3651,0x6d31, NULL,4867249871962584729}, // 438BF1F883653699
|
||||
|
||||
/* Super Robot Wars X-Omega (iOS/Android) voices */
|
||||
{0x5152,0x7979,0x152b, NULL,165521992944278}, // 0000968A97978A96
|
||||
|
||||
/* AKA to BLUE (Android) */
|
||||
{0x03fc,0x0749,0x12EF, NULL,0}, // guessed with VGAudio (possible key: 1FE0748978 / 136909719928)
|
||||
//{0x0c03,0x0749,0x1459, NULL,0}, // 2nd guess (possible key: 6018748A2D / 412727151149)
|
||||
|
||||
/* Mashiro Witch (Android) */
|
||||
{0x2669,0x1495,0x2407, NULL,0x55D11D3349495204}, // 55D11D3349495204
|
||||
|
||||
};
|
||||
|
||||
static const int adxkey8_list_count = sizeof(adxkey8_list) / sizeof(adxkey8_list[0]);
|
||||
static const int adxkey9_list_count = sizeof(adxkey9_list) / sizeof(adxkey9_list[0]);
|
||||
|
||||
|
||||
/* preloaded list used to derive keystrings from ADX_Decoder (see VGAudio for how to calculate) */
|
||||
static const uint16_t key8_primes[0x400] = {
|
||||
0x401B,0x4021,0x4025,0x402B,0x4031,0x403F,0x4043,0x4045,0x405D,0x4061,0x4067,0x406D,0x4087,0x4091,0x40A3,0x40A9,
|
||||
0x40B1,0x40B7,0x40BD,0x40DB,0x40DF,0x40EB,0x40F7,0x40F9,0x4109,0x410B,0x4111,0x4115,0x4121,0x4133,0x4135,0x413B,
|
||||
0x413F,0x4159,0x4165,0x416B,0x4177,0x417B,0x4193,0x41AB,0x41B7,0x41BD,0x41BF,0x41CB,0x41E7,0x41EF,0x41F3,0x41F9,
|
||||
0x4205,0x4207,0x4219,0x421F,0x4223,0x4229,0x422F,0x4243,0x4253,0x4255,0x425B,0x4261,0x4273,0x427D,0x4283,0x4285,
|
||||
0x4289,0x4291,0x4297,0x429D,0x42B5,0x42C5,0x42CB,0x42D3,0x42DD,0x42E3,0x42F1,0x4307,0x430F,0x431F,0x4325,0x4327,
|
||||
0x4333,0x4337,0x4339,0x434F,0x4357,0x4369,0x438B,0x438D,0x4393,0x43A5,0x43A9,0x43AF,0x43B5,0x43BD,0x43C7,0x43CF,
|
||||
0x43E1,0x43E7,0x43EB,0x43ED,0x43F1,0x43F9,0x4409,0x440B,0x4417,0x4423,0x4429,0x443B,0x443F,0x4445,0x444B,0x4451,
|
||||
0x4453,0x4459,0x4465,0x446F,0x4483,0x448F,0x44A1,0x44A5,0x44AB,0x44AD,0x44BD,0x44BF,0x44C9,0x44D7,0x44DB,0x44F9,
|
||||
0x44FB,0x4505,0x4511,0x4513,0x452B,0x4531,0x4541,0x4549,0x4553,0x4555,0x4561,0x4577,0x457D,0x457F,0x458F,0x45A3,
|
||||
0x45AD,0x45AF,0x45BB,0x45C7,0x45D9,0x45E3,0x45EF,0x45F5,0x45F7,0x4601,0x4603,0x4609,0x4613,0x4625,0x4627,0x4633,
|
||||
0x4639,0x463D,0x4643,0x4645,0x465D,0x4679,0x467B,0x467F,0x4681,0x468B,0x468D,0x469D,0x46A9,0x46B1,0x46C7,0x46C9,
|
||||
0x46CF,0x46D3,0x46D5,0x46DF,0x46E5,0x46F9,0x4705,0x470F,0x4717,0x4723,0x4729,0x472F,0x4735,0x4739,0x474B,0x474D,
|
||||
0x4751,0x475D,0x476F,0x4771,0x477D,0x4783,0x4787,0x4789,0x4799,0x47A5,0x47B1,0x47BF,0x47C3,0x47CB,0x47DD,0x47E1,
|
||||
0x47ED,0x47FB,0x4801,0x4807,0x480B,0x4813,0x4819,0x481D,0x4831,0x483D,0x4847,0x4855,0x4859,0x485B,0x486B,0x486D,
|
||||
0x4879,0x4897,0x489B,0x48A1,0x48B9,0x48CD,0x48E5,0x48EF,0x48F7,0x4903,0x490D,0x4919,0x491F,0x492B,0x4937,0x493D,
|
||||
0x4945,0x4955,0x4963,0x4969,0x496D,0x4973,0x4997,0x49AB,0x49B5,0x49D3,0x49DF,0x49E1,0x49E5,0x49E7,0x4A03,0x4A0F,
|
||||
0x4A1D,0x4A23,0x4A39,0x4A41,0x4A45,0x4A57,0x4A5D,0x4A6B,0x4A7D,0x4A81,0x4A87,0x4A89,0x4A8F,0x4AB1,0x4AC3,0x4AC5,
|
||||
0x4AD5,0x4ADB,0x4AED,0x4AEF,0x4B07,0x4B0B,0x4B0D,0x4B13,0x4B1F,0x4B25,0x4B31,0x4B3B,0x4B43,0x4B49,0x4B59,0x4B65,
|
||||
0x4B6D,0x4B77,0x4B85,0x4BAD,0x4BB3,0x4BB5,0x4BBB,0x4BBF,0x4BCB,0x4BD9,0x4BDD,0x4BDF,0x4BE3,0x4BE5,0x4BE9,0x4BF1,
|
||||
0x4BF7,0x4C01,0x4C07,0x4C0D,0x4C0F,0x4C15,0x4C1B,0x4C21,0x4C2D,0x4C33,0x4C4B,0x4C55,0x4C57,0x4C61,0x4C67,0x4C73,
|
||||
0x4C79,0x4C7F,0x4C8D,0x4C93,0x4C99,0x4CCD,0x4CE1,0x4CE7,0x4CF1,0x4CF3,0x4CFD,0x4D05,0x4D0F,0x4D1B,0x4D27,0x4D29,
|
||||
0x4D2F,0x4D33,0x4D41,0x4D51,0x4D59,0x4D65,0x4D6B,0x4D81,0x4D83,0x4D8D,0x4D95,0x4D9B,0x4DB1,0x4DB3,0x4DC9,0x4DCF,
|
||||
0x4DD7,0x4DE1,0x4DED,0x4DF9,0x4DFB,0x4E05,0x4E0B,0x4E17,0x4E19,0x4E1D,0x4E2B,0x4E35,0x4E37,0x4E3D,0x4E4F,0x4E53,
|
||||
0x4E5F,0x4E67,0x4E79,0x4E85,0x4E8B,0x4E91,0x4E95,0x4E9B,0x4EA1,0x4EAF,0x4EB3,0x4EB5,0x4EC1,0x4ECD,0x4ED1,0x4ED7,
|
||||
0x4EE9,0x4EFB,0x4F07,0x4F09,0x4F19,0x4F25,0x4F2D,0x4F3F,0x4F49,0x4F63,0x4F67,0x4F6D,0x4F75,0x4F7B,0x4F81,0x4F85,
|
||||
0x4F87,0x4F91,0x4FA5,0x4FA9,0x4FAF,0x4FB7,0x4FBB,0x4FCF,0x4FD9,0x4FDB,0x4FFD,0x4FFF,0x5003,0x501B,0x501D,0x5029,
|
||||
0x5035,0x503F,0x5045,0x5047,0x5053,0x5071,0x5077,0x5083,0x5093,0x509F,0x50A1,0x50B7,0x50C9,0x50D5,0x50E3,0x50ED,
|
||||
0x50EF,0x50FB,0x5107,0x510B,0x510D,0x5111,0x5117,0x5123,0x5125,0x5135,0x5147,0x5149,0x5171,0x5179,0x5189,0x518F,
|
||||
0x5197,0x51A1,0x51A3,0x51A7,0x51B9,0x51C1,0x51CB,0x51D3,0x51DF,0x51E3,0x51F5,0x51F7,0x5209,0x5213,0x5215,0x5219,
|
||||
0x521B,0x521F,0x5227,0x5243,0x5245,0x524B,0x5261,0x526D,0x5273,0x5281,0x5293,0x5297,0x529D,0x52A5,0x52AB,0x52B1,
|
||||
0x52BB,0x52C3,0x52C7,0x52C9,0x52DB,0x52E5,0x52EB,0x52FF,0x5315,0x531D,0x5323,0x5341,0x5345,0x5347,0x534B,0x535D,
|
||||
0x5363,0x5381,0x5383,0x5387,0x538F,0x5395,0x5399,0x539F,0x53AB,0x53B9,0x53DB,0x53E9,0x53EF,0x53F3,0x53F5,0x53FB,
|
||||
0x53FF,0x540D,0x5411,0x5413,0x5419,0x5435,0x5437,0x543B,0x5441,0x5449,0x5453,0x5455,0x545F,0x5461,0x546B,0x546D,
|
||||
0x5471,0x548F,0x5491,0x549D,0x54A9,0x54B3,0x54C5,0x54D1,0x54DF,0x54E9,0x54EB,0x54F7,0x54FD,0x5507,0x550D,0x551B,
|
||||
0x5527,0x552B,0x5539,0x553D,0x554F,0x5551,0x555B,0x5563,0x5567,0x556F,0x5579,0x5585,0x5597,0x55A9,0x55B1,0x55B7,
|
||||
0x55C9,0x55D9,0x55E7,0x55ED,0x55F3,0x55FD,0x560B,0x560F,0x5615,0x5617,0x5623,0x562F,0x5633,0x5639,0x563F,0x564B,
|
||||
0x564D,0x565D,0x565F,0x566B,0x5671,0x5675,0x5683,0x5689,0x568D,0x568F,0x569B,0x56AD,0x56B1,0x56D5,0x56E7,0x56F3,
|
||||
0x56FF,0x5701,0x5705,0x5707,0x570B,0x5713,0x571F,0x5723,0x5747,0x574D,0x575F,0x5761,0x576D,0x5777,0x577D,0x5789,
|
||||
0x57A1,0x57A9,0x57AF,0x57B5,0x57C5,0x57D1,0x57D3,0x57E5,0x57EF,0x5803,0x580D,0x580F,0x5815,0x5827,0x582B,0x582D,
|
||||
0x5855,0x585B,0x585D,0x586D,0x586F,0x5873,0x587B,0x588D,0x5897,0x58A3,0x58A9,0x58AB,0x58B5,0x58BD,0x58C1,0x58C7,
|
||||
0x58D3,0x58D5,0x58DF,0x58F1,0x58F9,0x58FF,0x5903,0x5917,0x591B,0x5921,0x5945,0x594B,0x594D,0x5957,0x595D,0x5975,
|
||||
0x597B,0x5989,0x5999,0x599F,0x59B1,0x59B3,0x59BD,0x59D1,0x59DB,0x59E3,0x59E9,0x59ED,0x59F3,0x59F5,0x59FF,0x5A01,
|
||||
0x5A0D,0x5A11,0x5A13,0x5A17,0x5A1F,0x5A29,0x5A2F,0x5A3B,0x5A4D,0x5A5B,0x5A67,0x5A77,0x5A7F,0x5A85,0x5A95,0x5A9D,
|
||||
0x5AA1,0x5AA3,0x5AA9,0x5ABB,0x5AD3,0x5AE5,0x5AEF,0x5AFB,0x5AFD,0x5B01,0x5B0F,0x5B19,0x5B1F,0x5B25,0x5B2B,0x5B3D,
|
||||
0x5B49,0x5B4B,0x5B67,0x5B79,0x5B87,0x5B97,0x5BA3,0x5BB1,0x5BC9,0x5BD5,0x5BEB,0x5BF1,0x5BF3,0x5BFD,0x5C05,0x5C09,
|
||||
0x5C0B,0x5C0F,0x5C1D,0x5C29,0x5C2F,0x5C33,0x5C39,0x5C47,0x5C4B,0x5C4D,0x5C51,0x5C6F,0x5C75,0x5C77,0x5C7D,0x5C87,
|
||||
0x5C89,0x5CA7,0x5CBD,0x5CBF,0x5CC3,0x5CC9,0x5CD1,0x5CD7,0x5CDD,0x5CED,0x5CF9,0x5D05,0x5D0B,0x5D13,0x5D17,0x5D19,
|
||||
0x5D31,0x5D3D,0x5D41,0x5D47,0x5D4F,0x5D55,0x5D5B,0x5D65,0x5D67,0x5D6D,0x5D79,0x5D95,0x5DA3,0x5DA9,0x5DAD,0x5DB9,
|
||||
0x5DC1,0x5DC7,0x5DD3,0x5DD7,0x5DDD,0x5DEB,0x5DF1,0x5DFD,0x5E07,0x5E0D,0x5E13,0x5E1B,0x5E21,0x5E27,0x5E2B,0x5E2D,
|
||||
0x5E31,0x5E39,0x5E45,0x5E49,0x5E57,0x5E69,0x5E73,0x5E75,0x5E85,0x5E8B,0x5E9F,0x5EA5,0x5EAF,0x5EB7,0x5EBB,0x5ED9,
|
||||
0x5EFD,0x5F09,0x5F11,0x5F27,0x5F33,0x5F35,0x5F3B,0x5F47,0x5F57,0x5F5D,0x5F63,0x5F65,0x5F77,0x5F7B,0x5F95,0x5F99,
|
||||
0x5FA1,0x5FB3,0x5FBD,0x5FC5,0x5FCF,0x5FD5,0x5FE3,0x5FE7,0x5FFB,0x6011,0x6023,0x602F,0x6037,0x6053,0x605F,0x6065,
|
||||
0x606B,0x6073,0x6079,0x6085,0x609D,0x60AD,0x60BB,0x60BF,0x60CD,0x60D9,0x60DF,0x60E9,0x60F5,0x6109,0x610F,0x6113,
|
||||
0x611B,0x612D,0x6139,0x614B,0x6155,0x6157,0x615B,0x616F,0x6179,0x6187,0x618B,0x6191,0x6193,0x619D,0x61B5,0x61C7,
|
||||
0x61C9,0x61CD,0x61E1,0x61F1,0x61FF,0x6209,0x6217,0x621D,0x6221,0x6227,0x623B,0x6241,0x624B,0x6251,0x6253,0x625F,
|
||||
0x6265,0x6283,0x628D,0x6295,0x629B,0x629F,0x62A5,0x62AD,0x62D5,0x62D7,0x62DB,0x62DD,0x62E9,0x62FB,0x62FF,0x6305,
|
||||
0x630D,0x6317,0x631D,0x632F,0x6341,0x6343,0x634F,0x635F,0x6367,0x636D,0x6371,0x6377,0x637D,0x637F,0x63B3,0x63C1,
|
||||
0x63C5,0x63D9,0x63E9,0x63EB,0x63EF,0x63F5,0x6401,0x6403,0x6409,0x6415,0x6421,0x6427,0x642B,0x6439,0x6443,0x6449,
|
||||
0x644F,0x645D,0x6467,0x6475,0x6485,0x648D,0x6493,0x649F,0x64A3,0x64AB,0x64C1,0x64C7,0x64C9,0x64DB,0x64F1,0x64F7,
|
||||
0x64F9,0x650B,0x6511,0x6521,0x652F,0x6539,0x653F,0x654B,0x654D,0x6553,0x6557,0x655F,0x6571,0x657D,0x658D,0x658F,
|
||||
0x6593,0x65A1,0x65A5,0x65AD,0x65B9,0x65C5,0x65E3,0x65F3,0x65FB,0x65FF,0x6601,0x6607,0x661D,0x6629,0x6631,0x663B,
|
||||
0x6641,0x6647,0x664D,0x665B,0x6661,0x6673,0x667D,0x6689,0x668B,0x6695,0x6697,0x669B,0x66B5,0x66B9,0x66C5,0x66CD,
|
||||
0x66D1,0x66E3,0x66EB,0x66F5,0x6703,0x6713,0x6719,0x671F,0x6727,0x6731,0x6737,0x673F,0x6745,0x6751,0x675B,0x676F,
|
||||
0x6779,0x6781,0x6785,0x6791,0x67AB,0x67BD,0x67C1,0x67CD,0x67DF,0x67E5,0x6803,0x6809,0x6811,0x6817,0x682D,0x6839,
|
||||
};
|
||||
|
||||
static void derive_adx_key8(const char * key8, uint16_t * out_start, uint16_t * out_mult, uint16_t * out_add) {
|
||||
size_t key_size;
|
||||
uint16_t start = 0, mult = 0, add = 0;
|
||||
int i;
|
||||
|
||||
if (key8 == NULL || key8[0] == '\0')
|
||||
goto end;
|
||||
key_size = strlen(key8);
|
||||
start = key8_primes[0x100];
|
||||
mult = key8_primes[0x200];
|
||||
add = key8_primes[0x300];
|
||||
|
||||
for (i = 0; i < key_size; i++) {
|
||||
char c = key8[i];
|
||||
start = key8_primes[start * key8_primes[c + 0x80] % 0x400];
|
||||
mult = key8_primes[mult * key8_primes[c + 0x80] % 0x400];
|
||||
add = key8_primes[add * key8_primes[c + 0x80] % 0x400];
|
||||
}
|
||||
|
||||
end:
|
||||
*out_start = start;
|
||||
*out_mult = mult;
|
||||
*out_add = add;
|
||||
}
|
||||
|
||||
|
||||
static void derive_adx_key9(uint64_t key9, uint16_t * out_start, uint16_t * out_mult, uint16_t * out_add) {
|
||||
uint16_t start = 0, mult = 0, add = 0;
|
||||
|
||||
/* 0 is ignored by CRI's encoder, only from 1..18446744073709551615 */
|
||||
if (key9 == 0)
|
||||
goto end;
|
||||
|
||||
key9--;
|
||||
start = (int)(((key9 >> 27) & 0x7fff));
|
||||
mult = (int)(((key9 >> 12) & 0x7ffc) | 1);
|
||||
add = (int)(((key9 << 1 ) & 0x7fff) | 1);
|
||||
|
||||
/* alt from ADX_Decoder, probably the same */
|
||||
//start = ((key9 >> 27) & 0x7FFF);
|
||||
//mult = ((key9 >> 12) & 0x7FFC) | 1;
|
||||
//add = ((key9 << 1 ) & 0x7FFE) | 1;
|
||||
//mult |= add << 16;
|
||||
|
||||
end:
|
||||
*out_start = start;
|
||||
*out_mult = mult;
|
||||
*out_add = add;
|
||||
}
|
||||
|
||||
#endif/*_ADX_KEYS_H_*/
|
||||
#ifndef _ADX_KEYS_H_
|
||||
#define _ADX_KEYS_H_
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint16_t start,mult,add; /* XOR values derived from the actual key */
|
||||
char* key8; /* keystring used by type 8 encryption */
|
||||
uint64_t key9; /* keycode used by type 9 encryption */
|
||||
} adxkey_info;
|
||||
|
||||
/**
|
||||
* List of known keys, cracked from the sound files.
|
||||
* Keystrings (type 8) and keycodes (type 9) from executables / VGAudio / game's executables / 2ch.net.
|
||||
* Multiple keys may work for a game due to how they are derived.
|
||||
* start/mult/add are optional (0,0,0) if key8/9 are provided, but take priority if given.
|
||||
*/
|
||||
static const adxkey_info adxkey8_list[] = {
|
||||
|
||||
/* GOD HAND (PS2), Okami (PS2) [Clover Studio] */
|
||||
{0x49e1,0x4a57,0x553d, "karaage",0},
|
||||
|
||||
/* Blood+ (PS2) [Grasshopper Manufacture] */
|
||||
{0x5f5d,0x58bd,0x55ed, NULL,0}, // keystring not in ELF?
|
||||
|
||||
/* Killer7 (PS2) [Grasshopper Manufacture] */
|
||||
{0x50fb,0x5803,0x5701, "GHM",0},
|
||||
|
||||
/* Samurai Champloo (PS2) [Grasshopper Manufacture] */
|
||||
{0x4f3f,0x472f,0x562f, "GHMSC",0},
|
||||
|
||||
/* Raiden III (PS2) [Moss] */
|
||||
{0x66f5,0x58bd,0x4459, "(C)2005 MOSS LTD. BMW Z4",0},
|
||||
|
||||
/* Phantasy Star Universe (PC), Phantasy Star Universe: Ambition of the Illuminus (PS2) [Sonic Team] */
|
||||
{0x5deb,0x5f27,0x673f, "3x5k62bg9ptbwy",0},
|
||||
|
||||
/* Senko no Ronde [G.rev] */
|
||||
{0x46d3,0x5ced,0x474d, "ranatus",0},
|
||||
|
||||
/* NiGHTS: Journey of Dreams (Wii) [Sonic Team] */
|
||||
{0x440b,0x6539,0x5723, "sakakit4649",0},
|
||||
|
||||
/* unknown source */
|
||||
{0x586d,0x5d65,0x63eb, NULL,0}, // from guessadx (unique?)
|
||||
|
||||
/* Shuffle! On the Stage (PS2) [Navel] */
|
||||
{0x4969,0x5deb,0x467f, "SHUF",0},
|
||||
|
||||
/* Aoishiro (PS2) [Success] */
|
||||
{0x4d65,0x5eb7,0x5dfd, "wakasugi",0},
|
||||
|
||||
/* Sonic and the Black Knight (Wii) [Sonic Team] */
|
||||
{0x55b7,0x6191,0x5a77, "morio",0},
|
||||
|
||||
/* Amagami (PS2) [Enterbrain] */
|
||||
{0x5a17,0x509f,0x5bfd, "mituba",0}, /* also AHX key */
|
||||
|
||||
/* Yamasa Digi Portable: Matsuri no Tatsujin (PSP) [Yamasa] */
|
||||
{0x4c01,0x549d,0x676f, "7fa0xB9tw3",0},
|
||||
|
||||
/* Fragments Blue (PS2) [Kadokawa Shoten] */
|
||||
{0x5803,0x4555,0x47bf, "PIETA",0},
|
||||
|
||||
/* Soulcalibur IV (PS3) [Namco] */
|
||||
{0x59ed,0x4679,0x46c9, "SC4Test",0},
|
||||
|
||||
/* Senko no Ronde DUO (X360) [G.rev] */
|
||||
{0x6157,0x6809,0x4045, NULL,0}, // from guessadx
|
||||
|
||||
/* Nogizaka Haruka no Himitsu: Cosplay Hajimemashita (PS2) [Vridge] */
|
||||
{0x45af,0x5f27,0x52b1, "SKFHSIA",0},
|
||||
|
||||
/* Little Anchor (PS2) [D3 Publisher] */
|
||||
{0x5f65,0x5b3d,0x5f65, NULL,0}, // confirmed unique with guessadx
|
||||
|
||||
/* Hanayoi Romanesque: Ai to Kanashimi (PS2) [Marvelous] */
|
||||
{0x5563,0x5047,0x43ed, NULL,0}, // 2nd from guessadx, other was {0x5562,0x5047,0x1433}
|
||||
|
||||
/* Mobile Suit Gundam: Gundam vs. Gundam NEXT PLUS (PSP) [Capcom] */
|
||||
{0x4f7b,0x4fdb,0x5cbf, "CS-GGNX+",0},
|
||||
|
||||
/* Shoukan Shoujo: Elemental Girl Calling (PS2) [Bridge NetShop] */
|
||||
{0x4f7b,0x5071,0x4c61, "ELEMENGAL",0},
|
||||
|
||||
/* Rakushou! Pachi-Slot Sengen 6: Rio 2 Cruising Vanadis (PS2) [Net Corporation] */
|
||||
{0x53e9,0x586d,0x4eaf, NULL,0}, // confirmed unique with guessadx
|
||||
|
||||
/* Tears to Tiara Gaiden Avalon no Nazo (PS3) [Aquaplus] */
|
||||
{0x47e1,0x60e9,0x51c1, NULL,0}, // confirmed unique with guessadx
|
||||
|
||||
/* Neon Genesis Evangelion: Koutetsu no Girlfriend 2nd (PS2) [Broccoli] */
|
||||
{0x481d,0x4f25,0x5243, "eva2",0},
|
||||
|
||||
/* Futakoi Alternative (PS2) [Marvelous] */
|
||||
{0x413b,0x543b,0x57d1, "LOVLOV",0},
|
||||
|
||||
/* Gakuen Utopia: Manabi Straight! KiraKira Happy Festa! (PS2) [Marvelous] */
|
||||
{0x440b,0x4327,0x564b, "MANABIST",0},
|
||||
|
||||
/* Soshite Kono Uchuu ni Kirameku Kimi no Shi XXX (PS2) [Datam Polystar] */
|
||||
{0x5f5d,0x552b,0x5507, "DATAM-KK2",0},
|
||||
|
||||
/* Sakura Taisen: Atsuki Chishio Ni (PS2) [Sega] */
|
||||
{0x645d,0x6011,0x5c29, NULL,0}, // confirmed unique with guessadx
|
||||
|
||||
/* Sakura Taisen 3 ~Paris wa Moeteiru ka~ (PS2) [Sega] */
|
||||
{0x62ad,0x4b13,0x5957, NULL,0}, // confirmed unique with guessadx
|
||||
|
||||
/* Sotsugyou 2nd Generation (PS2) [Jinx] */
|
||||
{0x6305,0x509f,0x4c01, NULL,0}, // First guess from guessadx, other was {0x6307,0x509f,0x2ac5}
|
||||
|
||||
/* La Corda d'Oro (PSP) [Koei] */
|
||||
{0x55b7,0x67e5,0x5387, NULL,0}, // keystring not in ELF?
|
||||
|
||||
/* Nanatsuiro * Drops Pure!! (PS2) [Media Works] */
|
||||
{0x6731,0x645d,0x566b, NULL,0}, // confirmed unique with guessadx
|
||||
|
||||
/* Shakugan no Shana (PS2) [Vridge] */
|
||||
{0x5fc5,0x63d9,0x599f, "FUZETSU",0},
|
||||
|
||||
/* Uragiri wa Boku no Namae o Shitteiru (PS2) [Kadokawa Shoten] */
|
||||
{0x4c73,0x4d8d,0x5827, NULL,0}, // confirmed unique with guessadx
|
||||
|
||||
/* StormLover!! (PSP), StormLover Kai!! (PSP) [Vridge] */
|
||||
{0x5a11,0x67e5,0x6751, "HEXDPFMDKPQW",0}, /* unknown AHX key */
|
||||
|
||||
/* Sora no Otoshimono: DokiDoki Summer Vacation (PSP) [Kadokawa Shoten] */
|
||||
{0x5e75,0x4a89,0x4c61, "funen-gomi",0},
|
||||
|
||||
/* Boku wa Koukuu Kanseikan: Airport Hero Naha (PSP) [Sonic Powered] */
|
||||
{0x64ab,0x5297,0x632f, "sonic",0},
|
||||
|
||||
/* Lucky Star: Net Idol Meister (PSP) [Vridge, Kadokawa Shoten] */
|
||||
{0x4d81,0x5243,0x58c7, "JJOLIFJLE",0}, /* unknown AHX key */
|
||||
|
||||
/* Ishin Renka: Ryouma Gaiden (PSP) [Vridge] */
|
||||
{0x54d1,0x526d,0x5e8b, "LQAFJOIEJ",0}, /* unknown AHX key */
|
||||
|
||||
/* Lucky Star: Ryouou Gakuen Outousai Portable (PSP) [Vridge] */
|
||||
{0x4d05,0x663b,0x6343, "IUNOIRU",0}, /* unknown AHX key */
|
||||
|
||||
/* Marriage Royale: Prism Story (PSP) [Vridge] */
|
||||
{0x40a9,0x46b1,0x62ad, "ROYMAR",0}, /* unknown AHX key */
|
||||
|
||||
/* Nogizaka Haruka no Himitsu: Doujinshi Hajimemashita (PSP) [Vridge] */
|
||||
{0x4609,0x671f,0x4b65, "CLKMEOUHFLIE",0}, /* unknown AHX key */
|
||||
|
||||
/* Slotter Mania P: Mach Go Go Go III (PSP) [Dorart] */
|
||||
{0x41ef,0x463d,0x5507, "SGGK",0},
|
||||
|
||||
/* Nichijou: Uchuujin (PSP) [Vridge] */
|
||||
{0x4369,0x486d,0x5461, "LJLOUHIU787",0}, /* unknown AHX key */
|
||||
|
||||
/* R-15 Portable (PSP) [Kadokawa Shoten] */
|
||||
{0x6809,0x5fd5,0x5bb1, "R-15(Heart)Love",0},
|
||||
|
||||
/* Suzumiya Haruhi-chan no Mahjong (PSP) [Kadokawa Shoten] */
|
||||
{0x5c33,0x4133,0x4ce7, "bi88a#fas",0},
|
||||
|
||||
/* StormLover Natsu Koi!! (PSP) [Vridge] */
|
||||
{0x4133,0x5a01,0x5723, "LIKDFJUIDJOQ",0}, /* unknown AHX key */
|
||||
|
||||
/* Shounen Onmyouji: Tsubasa yo Ima, Sora e Kaere (PS2) [Kadokawa Shoten] */
|
||||
{0x55d9,0x46d3,0x5b01, "SONMYOJI",0},
|
||||
|
||||
/* Girls Bravo: Romance 15's (PS2) [Kadokawa Shoten] */
|
||||
{0x658f,0x4a89,0x5213, "GBRAVO",0},
|
||||
|
||||
/* Kashimashi! Girl Meets Girl: Hajimete no Natsu Monogatari (PS2) [Vridge] */
|
||||
{0x6109,0x5135,0x673f, "KASHIM",0},
|
||||
|
||||
/* Bakumatsu Renka: Karyuu Kenshiden (PS2) [Vridge] */
|
||||
{0x4919,0x612d,0x4919, "RENRENKA22",0},
|
||||
|
||||
/* Tensei Hakkenshi: Fuumaroku (PS2) [Vridge] */
|
||||
{0x5761,0x6283,0x4531, "HAKKEN",0},
|
||||
|
||||
/* Lucky Star: Ryouou Gakuen Outousai (PS2) [Vridge] */
|
||||
{0x481D,0x44F9,0x4E35, "LSTARPS2",0},
|
||||
|
||||
/* Bakumatsu Renka: Shinsengumi (PS2) [Vridge] */
|
||||
{0x5381,0x5701,0x665B, "SHINN",0},
|
||||
|
||||
/* Gintama Gin-san to Issho! Boku no Kabukichou Nikki (PS2) [Bandai Namco?] */
|
||||
{0x67CD,0x5CA7,0x655F, "gt25809",0},
|
||||
|
||||
/* Lucky Star: RAvish Romance (PS2) [Vridge] */
|
||||
{0x5347,0x4FB7,0x6415, "LUCKYSRARPS2",0},
|
||||
|
||||
};
|
||||
|
||||
static const adxkey_info adxkey9_list[] = {
|
||||
|
||||
/* Phantasy Star Online 2 */
|
||||
{0x07d2,0x1ec5,0x0c7f, NULL,0}, // guessed with degod
|
||||
|
||||
/* Dragon Ball Z: Dokkan Battle (Android/iOS) */
|
||||
{0x0003,0x0d19,0x043b, NULL,416383518}, // 0000000018D1821E
|
||||
|
||||
/* Kisou Ryouhei Gunhound EX (PSP) */
|
||||
{0x0005,0x0bcd,0x1add, NULL,683461999}, // 0000000028BCCD6F
|
||||
|
||||
/* Raramagi (Android) */
|
||||
{0x0000,0x2b99,0x3e33, NULL,45719322}, // 0000000002B99F1A (12160794 also works)
|
||||
|
||||
/* Sonic Runners (Android) */
|
||||
{0x0000,0x12fd,0x1fbd, NULL,19910623}, // 00000000012FCFDF
|
||||
|
||||
/* Fallen Princess (iOS/Android) */
|
||||
{0x5e4b,0x190d,0x76bb, NULL,145552191146490718}, // 02051AF25990FB5E
|
||||
|
||||
/* Yuuki Yuuna wa Yuusha de aru: Hanayui no Kirameki / Yuyuyui (iOS/Android) */
|
||||
{0x3f10,0x3651,0x6d31, NULL,4867249871962584729}, // 438BF1F883653699
|
||||
|
||||
/* Super Robot Wars X-Omega (iOS/Android) voices */
|
||||
{0x5152,0x7979,0x152b, NULL,165521992944278}, // 0000968A97978A96
|
||||
|
||||
/* AKA to BLUE (Android) */
|
||||
{0x03fc,0x0749,0x12EF, NULL,0}, // guessed with VGAudio (possible key: 1FE0748978 / 136909719928)
|
||||
//{0x0c03,0x0749,0x1459, NULL,0}, // 2nd guess (possible key: 6018748A2D / 412727151149)
|
||||
|
||||
/* Mashiro Witch (Android) */
|
||||
{0x2669,0x1495,0x2407, NULL,0x55D11D3349495204}, // 55D11D3349495204
|
||||
|
||||
/* Nogizaka46 Rhythm Festival (Android) */
|
||||
{0x2378,0x5511,0x0201, NULL,5613126134333697}, // 0013F11BC5510101
|
||||
|
||||
};
|
||||
|
||||
static const int adxkey8_list_count = sizeof(adxkey8_list) / sizeof(adxkey8_list[0]);
|
||||
static const int adxkey9_list_count = sizeof(adxkey9_list) / sizeof(adxkey9_list[0]);
|
||||
|
||||
|
||||
/* preloaded list used to derive keystrings from ADX_Decoder (see VGAudio for how to calculate) */
|
||||
static const uint16_t key8_primes[0x400] = {
|
||||
0x401B,0x4021,0x4025,0x402B,0x4031,0x403F,0x4043,0x4045,0x405D,0x4061,0x4067,0x406D,0x4087,0x4091,0x40A3,0x40A9,
|
||||
0x40B1,0x40B7,0x40BD,0x40DB,0x40DF,0x40EB,0x40F7,0x40F9,0x4109,0x410B,0x4111,0x4115,0x4121,0x4133,0x4135,0x413B,
|
||||
0x413F,0x4159,0x4165,0x416B,0x4177,0x417B,0x4193,0x41AB,0x41B7,0x41BD,0x41BF,0x41CB,0x41E7,0x41EF,0x41F3,0x41F9,
|
||||
0x4205,0x4207,0x4219,0x421F,0x4223,0x4229,0x422F,0x4243,0x4253,0x4255,0x425B,0x4261,0x4273,0x427D,0x4283,0x4285,
|
||||
0x4289,0x4291,0x4297,0x429D,0x42B5,0x42C5,0x42CB,0x42D3,0x42DD,0x42E3,0x42F1,0x4307,0x430F,0x431F,0x4325,0x4327,
|
||||
0x4333,0x4337,0x4339,0x434F,0x4357,0x4369,0x438B,0x438D,0x4393,0x43A5,0x43A9,0x43AF,0x43B5,0x43BD,0x43C7,0x43CF,
|
||||
0x43E1,0x43E7,0x43EB,0x43ED,0x43F1,0x43F9,0x4409,0x440B,0x4417,0x4423,0x4429,0x443B,0x443F,0x4445,0x444B,0x4451,
|
||||
0x4453,0x4459,0x4465,0x446F,0x4483,0x448F,0x44A1,0x44A5,0x44AB,0x44AD,0x44BD,0x44BF,0x44C9,0x44D7,0x44DB,0x44F9,
|
||||
0x44FB,0x4505,0x4511,0x4513,0x452B,0x4531,0x4541,0x4549,0x4553,0x4555,0x4561,0x4577,0x457D,0x457F,0x458F,0x45A3,
|
||||
0x45AD,0x45AF,0x45BB,0x45C7,0x45D9,0x45E3,0x45EF,0x45F5,0x45F7,0x4601,0x4603,0x4609,0x4613,0x4625,0x4627,0x4633,
|
||||
0x4639,0x463D,0x4643,0x4645,0x465D,0x4679,0x467B,0x467F,0x4681,0x468B,0x468D,0x469D,0x46A9,0x46B1,0x46C7,0x46C9,
|
||||
0x46CF,0x46D3,0x46D5,0x46DF,0x46E5,0x46F9,0x4705,0x470F,0x4717,0x4723,0x4729,0x472F,0x4735,0x4739,0x474B,0x474D,
|
||||
0x4751,0x475D,0x476F,0x4771,0x477D,0x4783,0x4787,0x4789,0x4799,0x47A5,0x47B1,0x47BF,0x47C3,0x47CB,0x47DD,0x47E1,
|
||||
0x47ED,0x47FB,0x4801,0x4807,0x480B,0x4813,0x4819,0x481D,0x4831,0x483D,0x4847,0x4855,0x4859,0x485B,0x486B,0x486D,
|
||||
0x4879,0x4897,0x489B,0x48A1,0x48B9,0x48CD,0x48E5,0x48EF,0x48F7,0x4903,0x490D,0x4919,0x491F,0x492B,0x4937,0x493D,
|
||||
0x4945,0x4955,0x4963,0x4969,0x496D,0x4973,0x4997,0x49AB,0x49B5,0x49D3,0x49DF,0x49E1,0x49E5,0x49E7,0x4A03,0x4A0F,
|
||||
0x4A1D,0x4A23,0x4A39,0x4A41,0x4A45,0x4A57,0x4A5D,0x4A6B,0x4A7D,0x4A81,0x4A87,0x4A89,0x4A8F,0x4AB1,0x4AC3,0x4AC5,
|
||||
0x4AD5,0x4ADB,0x4AED,0x4AEF,0x4B07,0x4B0B,0x4B0D,0x4B13,0x4B1F,0x4B25,0x4B31,0x4B3B,0x4B43,0x4B49,0x4B59,0x4B65,
|
||||
0x4B6D,0x4B77,0x4B85,0x4BAD,0x4BB3,0x4BB5,0x4BBB,0x4BBF,0x4BCB,0x4BD9,0x4BDD,0x4BDF,0x4BE3,0x4BE5,0x4BE9,0x4BF1,
|
||||
0x4BF7,0x4C01,0x4C07,0x4C0D,0x4C0F,0x4C15,0x4C1B,0x4C21,0x4C2D,0x4C33,0x4C4B,0x4C55,0x4C57,0x4C61,0x4C67,0x4C73,
|
||||
0x4C79,0x4C7F,0x4C8D,0x4C93,0x4C99,0x4CCD,0x4CE1,0x4CE7,0x4CF1,0x4CF3,0x4CFD,0x4D05,0x4D0F,0x4D1B,0x4D27,0x4D29,
|
||||
0x4D2F,0x4D33,0x4D41,0x4D51,0x4D59,0x4D65,0x4D6B,0x4D81,0x4D83,0x4D8D,0x4D95,0x4D9B,0x4DB1,0x4DB3,0x4DC9,0x4DCF,
|
||||
0x4DD7,0x4DE1,0x4DED,0x4DF9,0x4DFB,0x4E05,0x4E0B,0x4E17,0x4E19,0x4E1D,0x4E2B,0x4E35,0x4E37,0x4E3D,0x4E4F,0x4E53,
|
||||
0x4E5F,0x4E67,0x4E79,0x4E85,0x4E8B,0x4E91,0x4E95,0x4E9B,0x4EA1,0x4EAF,0x4EB3,0x4EB5,0x4EC1,0x4ECD,0x4ED1,0x4ED7,
|
||||
0x4EE9,0x4EFB,0x4F07,0x4F09,0x4F19,0x4F25,0x4F2D,0x4F3F,0x4F49,0x4F63,0x4F67,0x4F6D,0x4F75,0x4F7B,0x4F81,0x4F85,
|
||||
0x4F87,0x4F91,0x4FA5,0x4FA9,0x4FAF,0x4FB7,0x4FBB,0x4FCF,0x4FD9,0x4FDB,0x4FFD,0x4FFF,0x5003,0x501B,0x501D,0x5029,
|
||||
0x5035,0x503F,0x5045,0x5047,0x5053,0x5071,0x5077,0x5083,0x5093,0x509F,0x50A1,0x50B7,0x50C9,0x50D5,0x50E3,0x50ED,
|
||||
0x50EF,0x50FB,0x5107,0x510B,0x510D,0x5111,0x5117,0x5123,0x5125,0x5135,0x5147,0x5149,0x5171,0x5179,0x5189,0x518F,
|
||||
0x5197,0x51A1,0x51A3,0x51A7,0x51B9,0x51C1,0x51CB,0x51D3,0x51DF,0x51E3,0x51F5,0x51F7,0x5209,0x5213,0x5215,0x5219,
|
||||
0x521B,0x521F,0x5227,0x5243,0x5245,0x524B,0x5261,0x526D,0x5273,0x5281,0x5293,0x5297,0x529D,0x52A5,0x52AB,0x52B1,
|
||||
0x52BB,0x52C3,0x52C7,0x52C9,0x52DB,0x52E5,0x52EB,0x52FF,0x5315,0x531D,0x5323,0x5341,0x5345,0x5347,0x534B,0x535D,
|
||||
0x5363,0x5381,0x5383,0x5387,0x538F,0x5395,0x5399,0x539F,0x53AB,0x53B9,0x53DB,0x53E9,0x53EF,0x53F3,0x53F5,0x53FB,
|
||||
0x53FF,0x540D,0x5411,0x5413,0x5419,0x5435,0x5437,0x543B,0x5441,0x5449,0x5453,0x5455,0x545F,0x5461,0x546B,0x546D,
|
||||
0x5471,0x548F,0x5491,0x549D,0x54A9,0x54B3,0x54C5,0x54D1,0x54DF,0x54E9,0x54EB,0x54F7,0x54FD,0x5507,0x550D,0x551B,
|
||||
0x5527,0x552B,0x5539,0x553D,0x554F,0x5551,0x555B,0x5563,0x5567,0x556F,0x5579,0x5585,0x5597,0x55A9,0x55B1,0x55B7,
|
||||
0x55C9,0x55D9,0x55E7,0x55ED,0x55F3,0x55FD,0x560B,0x560F,0x5615,0x5617,0x5623,0x562F,0x5633,0x5639,0x563F,0x564B,
|
||||
0x564D,0x565D,0x565F,0x566B,0x5671,0x5675,0x5683,0x5689,0x568D,0x568F,0x569B,0x56AD,0x56B1,0x56D5,0x56E7,0x56F3,
|
||||
0x56FF,0x5701,0x5705,0x5707,0x570B,0x5713,0x571F,0x5723,0x5747,0x574D,0x575F,0x5761,0x576D,0x5777,0x577D,0x5789,
|
||||
0x57A1,0x57A9,0x57AF,0x57B5,0x57C5,0x57D1,0x57D3,0x57E5,0x57EF,0x5803,0x580D,0x580F,0x5815,0x5827,0x582B,0x582D,
|
||||
0x5855,0x585B,0x585D,0x586D,0x586F,0x5873,0x587B,0x588D,0x5897,0x58A3,0x58A9,0x58AB,0x58B5,0x58BD,0x58C1,0x58C7,
|
||||
0x58D3,0x58D5,0x58DF,0x58F1,0x58F9,0x58FF,0x5903,0x5917,0x591B,0x5921,0x5945,0x594B,0x594D,0x5957,0x595D,0x5975,
|
||||
0x597B,0x5989,0x5999,0x599F,0x59B1,0x59B3,0x59BD,0x59D1,0x59DB,0x59E3,0x59E9,0x59ED,0x59F3,0x59F5,0x59FF,0x5A01,
|
||||
0x5A0D,0x5A11,0x5A13,0x5A17,0x5A1F,0x5A29,0x5A2F,0x5A3B,0x5A4D,0x5A5B,0x5A67,0x5A77,0x5A7F,0x5A85,0x5A95,0x5A9D,
|
||||
0x5AA1,0x5AA3,0x5AA9,0x5ABB,0x5AD3,0x5AE5,0x5AEF,0x5AFB,0x5AFD,0x5B01,0x5B0F,0x5B19,0x5B1F,0x5B25,0x5B2B,0x5B3D,
|
||||
0x5B49,0x5B4B,0x5B67,0x5B79,0x5B87,0x5B97,0x5BA3,0x5BB1,0x5BC9,0x5BD5,0x5BEB,0x5BF1,0x5BF3,0x5BFD,0x5C05,0x5C09,
|
||||
0x5C0B,0x5C0F,0x5C1D,0x5C29,0x5C2F,0x5C33,0x5C39,0x5C47,0x5C4B,0x5C4D,0x5C51,0x5C6F,0x5C75,0x5C77,0x5C7D,0x5C87,
|
||||
0x5C89,0x5CA7,0x5CBD,0x5CBF,0x5CC3,0x5CC9,0x5CD1,0x5CD7,0x5CDD,0x5CED,0x5CF9,0x5D05,0x5D0B,0x5D13,0x5D17,0x5D19,
|
||||
0x5D31,0x5D3D,0x5D41,0x5D47,0x5D4F,0x5D55,0x5D5B,0x5D65,0x5D67,0x5D6D,0x5D79,0x5D95,0x5DA3,0x5DA9,0x5DAD,0x5DB9,
|
||||
0x5DC1,0x5DC7,0x5DD3,0x5DD7,0x5DDD,0x5DEB,0x5DF1,0x5DFD,0x5E07,0x5E0D,0x5E13,0x5E1B,0x5E21,0x5E27,0x5E2B,0x5E2D,
|
||||
0x5E31,0x5E39,0x5E45,0x5E49,0x5E57,0x5E69,0x5E73,0x5E75,0x5E85,0x5E8B,0x5E9F,0x5EA5,0x5EAF,0x5EB7,0x5EBB,0x5ED9,
|
||||
0x5EFD,0x5F09,0x5F11,0x5F27,0x5F33,0x5F35,0x5F3B,0x5F47,0x5F57,0x5F5D,0x5F63,0x5F65,0x5F77,0x5F7B,0x5F95,0x5F99,
|
||||
0x5FA1,0x5FB3,0x5FBD,0x5FC5,0x5FCF,0x5FD5,0x5FE3,0x5FE7,0x5FFB,0x6011,0x6023,0x602F,0x6037,0x6053,0x605F,0x6065,
|
||||
0x606B,0x6073,0x6079,0x6085,0x609D,0x60AD,0x60BB,0x60BF,0x60CD,0x60D9,0x60DF,0x60E9,0x60F5,0x6109,0x610F,0x6113,
|
||||
0x611B,0x612D,0x6139,0x614B,0x6155,0x6157,0x615B,0x616F,0x6179,0x6187,0x618B,0x6191,0x6193,0x619D,0x61B5,0x61C7,
|
||||
0x61C9,0x61CD,0x61E1,0x61F1,0x61FF,0x6209,0x6217,0x621D,0x6221,0x6227,0x623B,0x6241,0x624B,0x6251,0x6253,0x625F,
|
||||
0x6265,0x6283,0x628D,0x6295,0x629B,0x629F,0x62A5,0x62AD,0x62D5,0x62D7,0x62DB,0x62DD,0x62E9,0x62FB,0x62FF,0x6305,
|
||||
0x630D,0x6317,0x631D,0x632F,0x6341,0x6343,0x634F,0x635F,0x6367,0x636D,0x6371,0x6377,0x637D,0x637F,0x63B3,0x63C1,
|
||||
0x63C5,0x63D9,0x63E9,0x63EB,0x63EF,0x63F5,0x6401,0x6403,0x6409,0x6415,0x6421,0x6427,0x642B,0x6439,0x6443,0x6449,
|
||||
0x644F,0x645D,0x6467,0x6475,0x6485,0x648D,0x6493,0x649F,0x64A3,0x64AB,0x64C1,0x64C7,0x64C9,0x64DB,0x64F1,0x64F7,
|
||||
0x64F9,0x650B,0x6511,0x6521,0x652F,0x6539,0x653F,0x654B,0x654D,0x6553,0x6557,0x655F,0x6571,0x657D,0x658D,0x658F,
|
||||
0x6593,0x65A1,0x65A5,0x65AD,0x65B9,0x65C5,0x65E3,0x65F3,0x65FB,0x65FF,0x6601,0x6607,0x661D,0x6629,0x6631,0x663B,
|
||||
0x6641,0x6647,0x664D,0x665B,0x6661,0x6673,0x667D,0x6689,0x668B,0x6695,0x6697,0x669B,0x66B5,0x66B9,0x66C5,0x66CD,
|
||||
0x66D1,0x66E3,0x66EB,0x66F5,0x6703,0x6713,0x6719,0x671F,0x6727,0x6731,0x6737,0x673F,0x6745,0x6751,0x675B,0x676F,
|
||||
0x6779,0x6781,0x6785,0x6791,0x67AB,0x67BD,0x67C1,0x67CD,0x67DF,0x67E5,0x6803,0x6809,0x6811,0x6817,0x682D,0x6839,
|
||||
};
|
||||
|
||||
static void derive_adx_key8(const char * key8, uint16_t * out_start, uint16_t * out_mult, uint16_t * out_add) {
|
||||
size_t key_size;
|
||||
uint16_t start = 0, mult = 0, add = 0;
|
||||
int i;
|
||||
|
||||
if (key8 == NULL || key8[0] == '\0')
|
||||
goto end;
|
||||
key_size = strlen(key8);
|
||||
start = key8_primes[0x100];
|
||||
mult = key8_primes[0x200];
|
||||
add = key8_primes[0x300];
|
||||
|
||||
for (i = 0; i < key_size; i++) {
|
||||
char c = key8[i];
|
||||
start = key8_primes[start * key8_primes[c + 0x80] % 0x400];
|
||||
mult = key8_primes[mult * key8_primes[c + 0x80] % 0x400];
|
||||
add = key8_primes[add * key8_primes[c + 0x80] % 0x400];
|
||||
}
|
||||
|
||||
end:
|
||||
*out_start = start;
|
||||
*out_mult = mult;
|
||||
*out_add = add;
|
||||
}
|
||||
|
||||
|
||||
static void derive_adx_key9(uint64_t key9, uint16_t * out_start, uint16_t * out_mult, uint16_t * out_add) {
|
||||
uint16_t start = 0, mult = 0, add = 0;
|
||||
|
||||
/* 0 is ignored by CRI's encoder, only from 1..18446744073709551615 */
|
||||
if (key9 == 0)
|
||||
goto end;
|
||||
|
||||
key9--;
|
||||
start = (int)(((key9 >> 27) & 0x7fff));
|
||||
mult = (int)(((key9 >> 12) & 0x7ffc) | 1);
|
||||
add = (int)(((key9 << 1 ) & 0x7fff) | 1);
|
||||
|
||||
/* alt from ADX_Decoder, probably the same */
|
||||
//start = ((key9 >> 27) & 0x7FFF);
|
||||
//mult = ((key9 >> 12) & 0x7FFC) | 1;
|
||||
//add = ((key9 << 1 ) & 0x7FFE) | 1;
|
||||
//mult |= add << 16;
|
||||
|
||||
end:
|
||||
*out_start = start;
|
||||
*out_mult = mult;
|
||||
*out_add = add;
|
||||
}
|
||||
|
||||
#endif/*_ADX_KEYS_H_*/
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,270 +1,270 @@
|
|||
#ifndef _EA_EAAC_STREAMFILE_H_
|
||||
#define _EA_EAAC_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
#include "ea_eaac_opus_streamfile.h"
|
||||
|
||||
#define XMA_FRAME_SIZE 0x800
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
int version;
|
||||
int codec;
|
||||
int streamed;
|
||||
int stream_number;
|
||||
int stream_count;
|
||||
off_t stream_offset;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* offset that corresponds to physical_offset */
|
||||
off_t physical_offset; /* actual file offset */
|
||||
|
||||
uint32_t block_flag; /* current block flags */
|
||||
size_t block_size; /* current block size */
|
||||
size_t skip_size; /* size to skip from a block start to reach data start */
|
||||
size_t data_size; /* logical size of the block */
|
||||
size_t extra_size; /* extra padding/etc size of the block */
|
||||
|
||||
size_t logical_size;
|
||||
} eaac_io_data;
|
||||
|
||||
|
||||
/* Reads skipping EA's block headers, so the resulting data is smaller or larger than physical data.
|
||||
* physical/logical_offset will be at the start of a block and only advance when a block is done */
|
||||
static size_t eaac_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, eaac_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
/* ignore bad reads */
|
||||
if (offset < 0 || offset > data->logical_size) {
|
||||
return total_read;
|
||||
}
|
||||
|
||||
/* previous offset: re-start as we can't map logical<>physical offsets
|
||||
* (kinda slow as it trashes buffers, but shouldn't happen often) */
|
||||
if (offset < data->logical_offset) {
|
||||
;VGM_LOG("EAAC IO: restart offset=%lx + %x, po=%lx, lo=%lx\n", offset, length, data->physical_offset, data->logical_offset);
|
||||
data->physical_offset = data->stream_offset;
|
||||
data->logical_offset = 0x00;
|
||||
data->data_size = 0;
|
||||
data->extra_size = 0;
|
||||
}
|
||||
|
||||
/* read blocks, one at a time */
|
||||
while (length > 0) {
|
||||
|
||||
/* ignore EOF (implicitly handles block end flags) */
|
||||
if (data->logical_offset >= data->logical_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* process new block */
|
||||
if (data->data_size == 0) {
|
||||
data->block_flag = (uint8_t)read_8bit(data->physical_offset+0x00,streamfile);
|
||||
data->block_size = read_32bitBE(data->physical_offset+0x00,streamfile) & 0x00FFFFFF;
|
||||
|
||||
/* ignore header block */
|
||||
if (data->version == 1 && data->block_flag == 0x48) {
|
||||
data->physical_offset += data->block_size;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch(data->codec) {
|
||||
case 0x03: { /* EA-XMA */
|
||||
/* block format: 0x04=num-samples, (size*4 + N XMA packets) per stream (with 1/2ch XMA headers) */
|
||||
int i;
|
||||
|
||||
data->skip_size = 0x04 + 0x04;
|
||||
for (i = 0; i < data->stream_number; i++) {
|
||||
data->skip_size += read_32bitBE(data->physical_offset+data->skip_size, streamfile) / 4;
|
||||
}
|
||||
data->data_size = read_32bitBE(data->physical_offset+data->skip_size, streamfile) / 4; /* why size*4...? */
|
||||
data->skip_size += 0x04; /* skip mini header */
|
||||
data->data_size -= 0x04; /* remove mini header */
|
||||
if (data->data_size % XMA_FRAME_SIZE)
|
||||
data->extra_size = XMA_FRAME_SIZE - (data->data_size % XMA_FRAME_SIZE);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x05: /* EALayer3 v1 */
|
||||
case 0x06: /* EALayer3 v2 "PCM" */
|
||||
case 0x07: /* EALayer3 v2 "Spike" */
|
||||
case 0x0b: /* EAMP3 */
|
||||
case 0x0c: /* EAOpus */
|
||||
data->skip_size = 0x08;
|
||||
data->data_size = data->block_size - data->skip_size;
|
||||
break;
|
||||
|
||||
case 0x0a: /* EATrax */
|
||||
data->skip_size = 0x08;
|
||||
data->data_size = read_32bitBE(data->physical_offset+0x04,streamfile); /* also block_size - 0x08 */
|
||||
break;
|
||||
|
||||
default:
|
||||
return total_read;
|
||||
}
|
||||
}
|
||||
|
||||
/* move to next block */
|
||||
if (offset >= data->logical_offset + data->data_size + data->extra_size) {
|
||||
data->physical_offset += data->block_size;
|
||||
data->logical_offset += data->data_size + data->extra_size;
|
||||
data->data_size = 0;
|
||||
data->extra_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* read data */
|
||||
{
|
||||
size_t bytes_consumed, bytes_done, to_read;
|
||||
|
||||
bytes_consumed = offset - data->logical_offset;
|
||||
|
||||
switch(data->codec) {
|
||||
case 0x03: { /* EA-XMA */
|
||||
if (bytes_consumed < data->data_size) { /* offset falls within actual data */
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile);
|
||||
}
|
||||
else { /* offset falls within logical padded data */
|
||||
to_read = data->data_size + data->extra_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
memset(dest, 0xFF, to_read); /* no real need though, padding is ignored */
|
||||
bytes_done = to_read;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile);
|
||||
break;
|
||||
}
|
||||
|
||||
total_read += bytes_done;
|
||||
dest += bytes_done;
|
||||
offset += bytes_done;
|
||||
length -= bytes_done;
|
||||
|
||||
if (bytes_done != to_read || bytes_done == 0) {
|
||||
break; /* error/EOF */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
|
||||
static size_t eaac_io_size(STREAMFILE *streamfile, eaac_io_data* data) {
|
||||
off_t physical_offset, max_physical_offset;
|
||||
size_t logical_size = 0;
|
||||
|
||||
if (data->logical_size)
|
||||
return data->logical_size;
|
||||
|
||||
physical_offset = data->stream_offset;
|
||||
max_physical_offset = get_streamfile_size(streamfile);
|
||||
|
||||
/* get size of the logical stream */
|
||||
while (physical_offset < max_physical_offset) {
|
||||
uint32_t block_flag, block_size, data_size, skip_size;
|
||||
int i;
|
||||
|
||||
block_flag = (uint8_t)read_8bit(physical_offset+0x00,streamfile);
|
||||
block_size = read_32bitBE(physical_offset+0x00,streamfile) & 0x00FFFFFF;
|
||||
|
||||
if (block_size == 0)
|
||||
break; /* bad data */
|
||||
|
||||
if (data->version == 0 && block_flag != 0x00 && block_flag != 0x80)
|
||||
break; /* unknown block */
|
||||
|
||||
if (data->version == 1 && block_flag == 0x48) {
|
||||
physical_offset += block_size;
|
||||
continue; /* skip header block */
|
||||
}
|
||||
if (data->version == 1 && block_flag == 0x45)
|
||||
break; /* stop on last block (always empty) */
|
||||
if (data->version == 1 && block_flag != 0x44)
|
||||
break; /* unknown block */
|
||||
|
||||
switch(data->codec) {
|
||||
case 0x03: /* EA-XMA */
|
||||
skip_size = 0x04 + 0x04;
|
||||
for (i = 0; i < data->stream_number; i++) {
|
||||
skip_size += read_32bitBE(physical_offset + skip_size, streamfile) / 4; /* why size*4...? */
|
||||
}
|
||||
data_size = read_32bitBE(physical_offset + skip_size, streamfile) / 4;
|
||||
skip_size += 0x04; /* skip mini header */
|
||||
data_size -= 0x04; /* remove mini header */
|
||||
if (data_size % XMA_FRAME_SIZE)
|
||||
data_size += XMA_FRAME_SIZE - (data_size % XMA_FRAME_SIZE); /* extra padding */
|
||||
break;
|
||||
|
||||
case 0x05: /* EALayer3 v1 */
|
||||
case 0x06: /* EALayer3 v2 "PCM" */
|
||||
case 0x07: /* EALayer3 v2 "Spike" */
|
||||
case 0x0b: /* EAMP3 */
|
||||
case 0x0c: /* EAOpus */
|
||||
data_size = block_size - 0x08;
|
||||
break;
|
||||
|
||||
case 0x0a: /* EATrax */
|
||||
data_size = read_32bitBE(physical_offset+0x04,streamfile); /* also block_size - 0x08 */
|
||||
break;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
physical_offset += block_size;
|
||||
logical_size += data_size;
|
||||
|
||||
if (data->version == 0 && (!data->streamed || block_flag == 0x80))
|
||||
break; /* stop on last block */
|
||||
}
|
||||
|
||||
/* logical size can be bigger in EA-XMA though */
|
||||
if (physical_offset > get_streamfile_size(streamfile)) {
|
||||
VGM_LOG("EA EAAC: wrong size\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
data->logical_size = logical_size;
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
|
||||
/* Prepares custom IO for some blocked EAAudioCore formats, that need clean reads without block headers:
|
||||
* - EA-XMA: deflated XMA in multistreams (separate 1/2ch packets)
|
||||
* - EALayer3: MPEG granule 1 can go in the next block (in V2"P" mainly, others could use layout blocked_sns)
|
||||
* - EATrax: ATRAC9 frames can be split between blooks
|
||||
* - EAOpus: multiple Opus packets of frame size + Opus data per block
|
||||
*/
|
||||
static STREAMFILE* setup_eaac_streamfile(STREAMFILE *sf, int version, int codec, int streamed, int stream_number, int stream_count, off_t stream_offset) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
eaac_io_data io_data = {0};
|
||||
|
||||
io_data.version = version;
|
||||
io_data.codec = codec;
|
||||
io_data.streamed = streamed;
|
||||
io_data.stream_number = stream_number;
|
||||
io_data.stream_count = stream_count;
|
||||
io_data.stream_offset = stream_offset;
|
||||
io_data.physical_offset = stream_offset;
|
||||
io_data.logical_size = eaac_io_size(sf, &io_data); /* force init */
|
||||
|
||||
/* setup subfile */
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(eaac_io_data), eaac_io_read, eaac_io_size);
|
||||
new_sf = open_buffer_streamfile_f(new_sf, 0); /* EA-XMA and multichannel EALayer3 benefit from this */
|
||||
if (codec == 0x0c && stream_count > 1) /* multichannel opus */
|
||||
new_sf = open_io_eaac_opus_streamfile_f(new_sf, stream_number, stream_count);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _EA_EAAC_STREAMFILE_H_ */
|
||||
#ifndef _EA_EAAC_STREAMFILE_H_
|
||||
#define _EA_EAAC_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
#include "ea_eaac_opus_streamfile.h"
|
||||
|
||||
#define XMA_FRAME_SIZE 0x800
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
int version;
|
||||
int codec;
|
||||
int streamed;
|
||||
int stream_number;
|
||||
int stream_count;
|
||||
off_t stream_offset;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* offset that corresponds to physical_offset */
|
||||
off_t physical_offset; /* actual file offset */
|
||||
|
||||
uint32_t block_flag; /* current block flags */
|
||||
size_t block_size; /* current block size */
|
||||
size_t skip_size; /* size to skip from a block start to reach data start */
|
||||
size_t data_size; /* logical size of the block */
|
||||
size_t extra_size; /* extra padding/etc size of the block */
|
||||
|
||||
size_t logical_size;
|
||||
} eaac_io_data;
|
||||
|
||||
|
||||
/* Reads skipping EA's block headers, so the resulting data is smaller or larger than physical data.
|
||||
* physical/logical_offset will be at the start of a block and only advance when a block is done */
|
||||
static size_t eaac_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, eaac_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
/* ignore bad reads */
|
||||
if (offset < 0 || offset > data->logical_size) {
|
||||
return total_read;
|
||||
}
|
||||
|
||||
/* previous offset: re-start as we can't map logical<>physical offsets
|
||||
* (kinda slow as it trashes buffers, but shouldn't happen often) */
|
||||
if (offset < data->logical_offset) {
|
||||
;VGM_LOG("EAAC IO: restart offset=%lx + %x, po=%lx, lo=%lx\n", offset, length, data->physical_offset, data->logical_offset);
|
||||
data->physical_offset = data->stream_offset;
|
||||
data->logical_offset = 0x00;
|
||||
data->data_size = 0;
|
||||
data->extra_size = 0;
|
||||
}
|
||||
|
||||
/* read blocks, one at a time */
|
||||
while (length > 0) {
|
||||
|
||||
/* ignore EOF (implicitly handles block end flags) */
|
||||
if (data->logical_offset >= data->logical_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* process new block */
|
||||
if (data->data_size == 0) {
|
||||
data->block_flag = (uint8_t)read_8bit(data->physical_offset+0x00,streamfile);
|
||||
data->block_size = read_32bitBE(data->physical_offset+0x00,streamfile) & 0x00FFFFFF;
|
||||
|
||||
/* ignore header block */
|
||||
if (data->version == 1 && data->block_flag == 0x48) {
|
||||
data->physical_offset += data->block_size;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch(data->codec) {
|
||||
case 0x03: { /* EA-XMA */
|
||||
/* block format: 0x04=num-samples, (size*4 + N XMA packets) per stream (with 1/2ch XMA headers) */
|
||||
int i;
|
||||
|
||||
data->skip_size = 0x04 + 0x04;
|
||||
for (i = 0; i < data->stream_number; i++) {
|
||||
data->skip_size += read_32bitBE(data->physical_offset+data->skip_size, streamfile) / 4;
|
||||
}
|
||||
data->data_size = read_32bitBE(data->physical_offset+data->skip_size, streamfile) / 4; /* why size*4...? */
|
||||
data->skip_size += 0x04; /* skip mini header */
|
||||
data->data_size -= 0x04; /* remove mini header */
|
||||
if (data->data_size % XMA_FRAME_SIZE)
|
||||
data->extra_size = XMA_FRAME_SIZE - (data->data_size % XMA_FRAME_SIZE);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x05: /* EALayer3 v1 */
|
||||
case 0x06: /* EALayer3 v2 "PCM" */
|
||||
case 0x07: /* EALayer3 v2 "Spike" */
|
||||
case 0x0b: /* EAMP3 */
|
||||
case 0x0c: /* EAOpus */
|
||||
data->skip_size = 0x08;
|
||||
data->data_size = data->block_size - data->skip_size;
|
||||
break;
|
||||
|
||||
case 0x0a: /* EATrax */
|
||||
data->skip_size = 0x08;
|
||||
data->data_size = read_32bitBE(data->physical_offset+0x04,streamfile); /* also block_size - 0x08 */
|
||||
break;
|
||||
|
||||
default:
|
||||
return total_read;
|
||||
}
|
||||
}
|
||||
|
||||
/* move to next block */
|
||||
if (offset >= data->logical_offset + data->data_size + data->extra_size) {
|
||||
data->physical_offset += data->block_size;
|
||||
data->logical_offset += data->data_size + data->extra_size;
|
||||
data->data_size = 0;
|
||||
data->extra_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* read data */
|
||||
{
|
||||
size_t bytes_consumed, bytes_done, to_read;
|
||||
|
||||
bytes_consumed = offset - data->logical_offset;
|
||||
|
||||
switch(data->codec) {
|
||||
case 0x03: { /* EA-XMA */
|
||||
if (bytes_consumed < data->data_size) { /* offset falls within actual data */
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile);
|
||||
}
|
||||
else { /* offset falls within logical padded data */
|
||||
to_read = data->data_size + data->extra_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
memset(dest, 0xFF, to_read); /* no real need though, padding is ignored */
|
||||
bytes_done = to_read;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile);
|
||||
break;
|
||||
}
|
||||
|
||||
total_read += bytes_done;
|
||||
dest += bytes_done;
|
||||
offset += bytes_done;
|
||||
length -= bytes_done;
|
||||
|
||||
if (bytes_done != to_read || bytes_done == 0) {
|
||||
break; /* error/EOF */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
|
||||
static size_t eaac_io_size(STREAMFILE *streamfile, eaac_io_data* data) {
|
||||
off_t physical_offset, max_physical_offset;
|
||||
size_t logical_size = 0;
|
||||
|
||||
if (data->logical_size)
|
||||
return data->logical_size;
|
||||
|
||||
physical_offset = data->stream_offset;
|
||||
max_physical_offset = get_streamfile_size(streamfile);
|
||||
|
||||
/* get size of the logical stream */
|
||||
while (physical_offset < max_physical_offset) {
|
||||
uint32_t block_flag, block_size, data_size, skip_size;
|
||||
int i;
|
||||
|
||||
block_flag = (uint8_t)read_8bit(physical_offset+0x00,streamfile);
|
||||
block_size = read_32bitBE(physical_offset+0x00,streamfile) & 0x00FFFFFF;
|
||||
|
||||
if (block_size == 0)
|
||||
break; /* bad data */
|
||||
|
||||
if (data->version == 0 && block_flag != 0x00 && block_flag != 0x80)
|
||||
break; /* unknown block */
|
||||
|
||||
if (data->version == 1 && block_flag == 0x48) {
|
||||
physical_offset += block_size;
|
||||
continue; /* skip header block */
|
||||
}
|
||||
if (data->version == 1 && block_flag == 0x45)
|
||||
break; /* stop on last block (always empty) */
|
||||
if (data->version == 1 && block_flag != 0x44)
|
||||
break; /* unknown block */
|
||||
|
||||
switch(data->codec) {
|
||||
case 0x03: /* EA-XMA */
|
||||
skip_size = 0x04 + 0x04;
|
||||
for (i = 0; i < data->stream_number; i++) {
|
||||
skip_size += read_32bitBE(physical_offset + skip_size, streamfile) / 4; /* why size*4...? */
|
||||
}
|
||||
data_size = read_32bitBE(physical_offset + skip_size, streamfile) / 4;
|
||||
skip_size += 0x04; /* skip mini header */
|
||||
data_size -= 0x04; /* remove mini header */
|
||||
if (data_size % XMA_FRAME_SIZE)
|
||||
data_size += XMA_FRAME_SIZE - (data_size % XMA_FRAME_SIZE); /* extra padding */
|
||||
break;
|
||||
|
||||
case 0x05: /* EALayer3 v1 */
|
||||
case 0x06: /* EALayer3 v2 "PCM" */
|
||||
case 0x07: /* EALayer3 v2 "Spike" */
|
||||
case 0x0b: /* EAMP3 */
|
||||
case 0x0c: /* EAOpus */
|
||||
data_size = block_size - 0x08;
|
||||
break;
|
||||
|
||||
case 0x0a: /* EATrax */
|
||||
data_size = read_32bitBE(physical_offset+0x04,streamfile); /* also block_size - 0x08 */
|
||||
break;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
physical_offset += block_size;
|
||||
logical_size += data_size;
|
||||
|
||||
if (data->version == 0 && (!data->streamed || block_flag == 0x80))
|
||||
break; /* stop on last block */
|
||||
}
|
||||
|
||||
/* logical size can be bigger in EA-XMA though */
|
||||
if (physical_offset > get_streamfile_size(streamfile)) {
|
||||
VGM_LOG("EA EAAC: wrong size\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
data->logical_size = logical_size;
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
|
||||
/* Prepares custom IO for some blocked EAAudioCore formats, that need clean reads without block headers:
|
||||
* - EA-XMA: deflated XMA in multistreams (separate 1/2ch packets)
|
||||
* - EALayer3: MPEG granule 1 can go in the next block (in V2"P" mainly, others could use layout blocked_sns)
|
||||
* - EATrax: ATRAC9 frames can be split between blooks
|
||||
* - EAOpus: multiple Opus packets of frame size + Opus data per block
|
||||
*/
|
||||
static STREAMFILE* setup_eaac_audio_streamfile(STREAMFILE *sf, int version, int codec, int streamed, int stream_number, int stream_count, off_t stream_offset) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
eaac_io_data io_data = {0};
|
||||
|
||||
io_data.version = version;
|
||||
io_data.codec = codec;
|
||||
io_data.streamed = streamed;
|
||||
io_data.stream_number = stream_number;
|
||||
io_data.stream_count = stream_count;
|
||||
io_data.stream_offset = stream_offset;
|
||||
io_data.physical_offset = stream_offset;
|
||||
io_data.logical_size = eaac_io_size(sf, &io_data); /* force init */
|
||||
|
||||
/* setup subfile */
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(eaac_io_data), eaac_io_read, eaac_io_size);
|
||||
new_sf = open_buffer_streamfile_f(new_sf, 0); /* EA-XMA and multichannel EALayer3 benefit from this */
|
||||
if (codec == 0x0c && stream_count > 1) /* multichannel opus */
|
||||
new_sf = open_io_eaac_opus_streamfile_f(new_sf, stream_number, stream_count);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _EA_EAAC_STREAMFILE_H_ */
|
||||
|
|
|
@ -690,7 +690,7 @@ static STREAMFILE* open_mapfile_pair(STREAMFILE *streamFile, int track, int num_
|
|||
continue;
|
||||
}
|
||||
|
||||
strncpy(buf, mus_name, PATH_LIMIT);
|
||||
strncpy(buf, mus_name, PATH_LIMIT - 1);
|
||||
pch = strtok(buf, ","); //TODO: not thread safe in std C
|
||||
for (j = 0; j < track && pch; j++) {
|
||||
pch = strtok(NULL, ",");
|
||||
|
@ -699,9 +699,9 @@ static STREAMFILE* open_mapfile_pair(STREAMFILE *streamFile, int track, int num_
|
|||
|
||||
if (use_mask) {
|
||||
file_name[file_len - map_len] = '\0';
|
||||
strncat(file_name, pch + 1, PATH_LIMIT);
|
||||
strncat(file_name, pch + 1, PATH_LIMIT - 1);
|
||||
} else {
|
||||
strncpy(file_name, pch, PATH_LIMIT);
|
||||
strncpy(file_name, pch, PATH_LIMIT - 1);
|
||||
}
|
||||
|
||||
musFile = open_streamfile_by_filename(streamFile, file_name);
|
||||
|
|
|
@ -5,16 +5,10 @@ VGMSTREAM * init_vgmstream_gin_header(STREAMFILE *streamFile, off_t offset);
|
|||
|
||||
/* .gin - EA engine sounds [Need for Speed: Most Wanted (multi)] */
|
||||
VGMSTREAM * init_vgmstream_gin(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
|
||||
if (!check_extensions(streamFile, "gin"))
|
||||
goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_gin_header(streamFile, 0x00);
|
||||
if (!vgmstream)
|
||||
goto fail;
|
||||
|
||||
return vgmstream;
|
||||
return init_vgmstream_gin_header(streamFile, 0x00);
|
||||
|
||||
fail:
|
||||
return NULL;
|
||||
|
|
|
@ -1,311 +1,323 @@
|
|||
#ifndef _HCA_KEYS_H_
|
||||
#define _HCA_KEYS_H_
|
||||
|
||||
#include "hca_keys_awb.h"
|
||||
|
||||
typedef struct {
|
||||
uint64_t key; /* hca key or seed ('user') key */
|
||||
const uint16_t *subkeys; /* scramble subkey table for seed key */
|
||||
size_t subkeys_size; /* size of the derivation subkey table */
|
||||
} hcakey_info;
|
||||
|
||||
|
||||
/**
|
||||
* List of known keys, extracted from the game files (mostly found in 2ch.net).
|
||||
* CRI's tools expect an unsigned 64 bit number string, but keys are commonly found online in hex form.
|
||||
* Keys only use 56 bits though, so the upper 8 bits can be ignored.
|
||||
*
|
||||
* ACB+AWB after mid 2018 use a user seed key + a scramble subkey in the AWB (normally 16b LE at 0x0e)
|
||||
* to create the final HCA key, which means there is one key per AWB (so most HCA have a unique key).
|
||||
* vgmstream derives the key if subkey table is provided.
|
||||
*/
|
||||
static const hcakey_info hcakey_list[] = {
|
||||
|
||||
// CRI HCA decoder default
|
||||
{9621963164387704}, // CF222F1FE0748978
|
||||
|
||||
// Phantasy Star Online 2 (multi?)
|
||||
// used by most console games
|
||||
{0xCC55463930DBE1AB}, // CC55463930DBE1AB / 14723751768204501419
|
||||
|
||||
// Old Phantasy Star Online 2 (multi?)
|
||||
{61891147883431481}, // 30DBE1ABCC554639
|
||||
|
||||
// Jojo All Star Battle (PS3)
|
||||
{19700307}, // 00000000012C9A53
|
||||
|
||||
// Ro-Kyu-Bu! Himitsu no Otoshimono (PSP)
|
||||
{2012082716}, // 0000000077EDF21C
|
||||
|
||||
// VRIDGE Inc. games:
|
||||
// - HatsuKare * Renai Debut Sengen! (PSP)
|
||||
// - Seitokai no Ichizon Lv. 2 Portable (PSP)
|
||||
// - Koi wa Kousoku ni Shibararenai! (PSP)
|
||||
// - StormLover 2nd (PSP)
|
||||
// - Prince of Stride (PSVita)
|
||||
// - Ro-Kyu-Bu! Naisho no Shutter Chance (PSVita)
|
||||
{1234253142}, // 0000000049913556
|
||||
|
||||
// Idolm@ster Cinderella Stage (iOS/Android)
|
||||
// Shadowverse (iOS/Android)
|
||||
{59751358413602}, // 00003657F27E3B22
|
||||
|
||||
// Grimoire ~Shiritsu Grimoire Mahou Gakuen~ (iOS/Android)
|
||||
{5027916581011272}, // 0011DCDD0DC57F48
|
||||
|
||||
// Idol Connect (iOS/Android)
|
||||
{2424}, // 0000000000000978
|
||||
|
||||
// Kamen Rider Battle Rush (iOS/Android)
|
||||
{29423500797988784}, // 00688884A11CCFB0
|
||||
|
||||
// SD Gundam Strikers (iOS/Android)
|
||||
{30260840980773}, // 00001B85A6AD6125
|
||||
|
||||
// Sonic Runners (iOS/Android)
|
||||
{19910623}, // 00000000012FCFDF
|
||||
|
||||
// Fate/Grand Order (iOS/Android) base assets
|
||||
{12345}, // 0000000000003039
|
||||
|
||||
// Fate/Grand Order (iOS/Android) download assets *unconfirmed
|
||||
{9117927877783581796}, // 7E89631892EBF464
|
||||
|
||||
// Raramagi (iOS/Android)
|
||||
{45719322}, // 0000000002B99F1A
|
||||
|
||||
// Idolm@ster Million Live (iOS/Android)
|
||||
{765765765765765}, // 0002B875BC731A85
|
||||
|
||||
// Kurokishi to Shiro no Maou (iOS/Android)
|
||||
{3003875739822025258}, // 29AFE911F5816A2A
|
||||
|
||||
// Puella Magi Madoka Magica Side Story: Magia Record (iOS/Android)
|
||||
// Hortensia Saga (iOS/Android)
|
||||
{20536401}, // 0000000001395C51
|
||||
|
||||
// The Tower of Princess (iOS/Android)
|
||||
{9101518402445063}, // 002055C8634B5F07
|
||||
|
||||
// Fallen Princess (iOS/Android)
|
||||
{145552191146490718}, // 02051AF25990FB5E
|
||||
|
||||
// Diss World (iOS/Android)
|
||||
{9001712656335836006}, // 7CEC81F7C3091366
|
||||
|
||||
// Ikemen Vampire - Ijin-tachi to Koi no Yuuwaku (iOS/Android)
|
||||
{45152594117267709}, // 00A06A0B8D0C10FD
|
||||
|
||||
// Super Robot Wars X-Omega (iOS/Android)
|
||||
{165521992944278}, // 0000968A97978A96
|
||||
|
||||
// BanG Dream! Girls Band Party! (iOS/Android)
|
||||
{8910}, // 00000000000022CE
|
||||
|
||||
// Tokyo 7th Sisters (iOS/Android) *unconfirmed
|
||||
{0xFDAE531AAB414BA1}, // FDAE531AAB414BA1
|
||||
|
||||
// One Piece Dance Battle (iOS/Android)
|
||||
{1905818}, // 00000000001D149A
|
||||
|
||||
// Derby Stallion Masters (iOS/Android)
|
||||
{19840202}, // 00000000012EBCCA
|
||||
|
||||
// World Chain (iOS/Android)
|
||||
{4892292804961027794}, // 43E4EA62B8E6C6D2
|
||||
|
||||
// Yuuki Yuuna wa Yuusha de aru - Hanayui no Kirameki / Yuyuyui (iOS/Android)
|
||||
{4867249871962584729}, // 438BF1F883653699
|
||||
|
||||
// Tekken Mobile (iOS/Android)
|
||||
{0xFFFFFFFFFFFFFFFF}, // FFFFFFFFFFFFFFFF / 18446744073709551615
|
||||
|
||||
// Tales of the Rays (iOS/Android)
|
||||
{9516284}, // 00000000009134FC
|
||||
|
||||
// Skylock - Kamigami to Unmei no Itsutsuko (iOS/Android)
|
||||
{49160768297}, // 0000000B7235CB29
|
||||
|
||||
// Tokyo Ghoul: Re Invoke (iOS/Android)
|
||||
{6929101074247145}, // 00189DFB1024ADE9
|
||||
|
||||
// Azur Lane (iOS/Android)
|
||||
{621561580448882}, // 0002354E95356C72
|
||||
|
||||
// One Piece Treasure Cruise (iOS/Android)
|
||||
{1224}, // 00000000000004C8
|
||||
|
||||
// Schoolgirl Strikers ~Twinkle Melodies~ (iOS/Android)
|
||||
{0xDB5B61B8343D0000}, // DB5B61B8343D0000
|
||||
|
||||
// Bad Apple Wars (PSVita)
|
||||
{241352432}, // 000000000E62BEF0
|
||||
|
||||
// Koi to Senkyo to Chocolate Portable (PSP)
|
||||
{243812156}, // 000000000E88473C
|
||||
|
||||
// Custom Drive (PSP)
|
||||
{2012062010}, // 0000000077EDA13A
|
||||
|
||||
// Root Letter (PSVita)
|
||||
{1547531215412131}, // 00057F78B05F9BA3
|
||||
|
||||
// Pro Evolution Soccer 2018 / Winning Eleven 2018 (Android)
|
||||
{14121473}, // 0000000000D77A01
|
||||
|
||||
// Kirara Fantasia (Android/iOS)
|
||||
{51408295487268137}, // 00B6A3928706E529
|
||||
|
||||
// A3! (iOS/Android)
|
||||
{914306251}, // 00000000367F34CB
|
||||
|
||||
// Weekly Shonen Jump: Ore Collection! (iOS/Android)
|
||||
{11708691}, // 0000000000B2A913
|
||||
|
||||
// Monster Gear Versus (iOS/Android)
|
||||
{0xB1E30F346415B475}, // B1E30F346415B475
|
||||
|
||||
// Yumeiro Cast (iOS/Android)
|
||||
{14418}, // 0000000000003852
|
||||
|
||||
// Ikki Tousen: Straight Striker (iOS/Android)
|
||||
{1000}, // 00000000000003E8
|
||||
|
||||
// Zero kara Hajimeru Mahou no Sho (iOS/Android)
|
||||
{0xD2E836E662F20000}, // D2E836E662F20000
|
||||
|
||||
// Soul Reverse Zero (iOS/Android)
|
||||
{2873513618}, // 00000000AB465692
|
||||
|
||||
// Jojo's Bizarre Adventure: Diamond Records (iOS/Android) [additional data]
|
||||
{0x820212864CAB35DE}, // 820212864CAB35DE
|
||||
|
||||
// HUNTER x HUNTER: World Hunt (iOS/Android)
|
||||
{71777214294589695}, // 00FF00FF00FF00FF
|
||||
|
||||
// \Comepri/ Comedy Prince (iOS/Android)
|
||||
{201537197868}, // 0000002EEC8D972C
|
||||
|
||||
// Puzzle of Empires (iOS/Android)
|
||||
{13687846}, // 0000000000D0DC26
|
||||
|
||||
// Aozora Under Girls! (iOS/Android)
|
||||
{4988006236073}, // 000004895C56FFA9
|
||||
|
||||
// Castle & Dragon (iOS/Android)
|
||||
{20140528}, // 00000000013351F0
|
||||
|
||||
// Uta no Prince sama Shining Live (iOS/Android)
|
||||
{2122831366}, // 000000007E87D606
|
||||
|
||||
// Sevens Story (iOS/Android)
|
||||
{629427372852}, // 000000928CCB8334
|
||||
|
||||
// MinGol: Everybody's Golf (iOS/Android)
|
||||
{1430028151061218}, // 0005149A5FF67AE2
|
||||
|
||||
// AKB48 Group Tsui ni Koushiki Otoge demashita. (iOS/Android)
|
||||
{831021912315111419}, // 0B886206BC1BA7FB
|
||||
|
||||
// Sen no Kaizoku (iOS/Android)
|
||||
{81368371967}, // 00000012F1EED2FF
|
||||
|
||||
// I Chu (iOS/Android)
|
||||
{13456}, // 0000000000003490
|
||||
|
||||
// Shinobi Nightmare (iOS/Android)
|
||||
{369481198260487572}, // 0520A93135808594
|
||||
|
||||
// Bungo Stray Dogs: Mayoi Inu Kaikitan (iOS/Android)
|
||||
{1655728931134731873}, // 16FA54B0C09F7661
|
||||
|
||||
// Super Sentai Legend Wars (iOS/Android)
|
||||
{4017992759667450}, // 000E4657D7266AFA
|
||||
|
||||
// Metal Saga: The Ark of Wastes (iOS/Android)
|
||||
{100097101118097115}, // 01639DC87B30C6DB
|
||||
|
||||
// Taga Tame no Alchemist (iOS/Android)
|
||||
{5047159794308}, // 00000497222AAA84
|
||||
|
||||
// Shin Tennis no Ouji-sama: Rising Beat (iOS/Android) voices?
|
||||
{4902201417679}, // 0000047561F95FCF
|
||||
|
||||
// Kai-ri-Sei Million Arthur (Vita)
|
||||
{1782351729464341796}, // 18BC2F7463867524
|
||||
|
||||
// Dx2 Shin Megami Tensei Liberation (iOS/Android)
|
||||
{118714477}, // 000000000713706D
|
||||
|
||||
// Oira (Cygames) [iOS/Android]
|
||||
{46460622}, // 0000000002C4EECE
|
||||
|
||||
// Dragon Ball Legends (Bandai Namco) [iOS/Android]
|
||||
{7335633962698440504}, // 65CD683924EE7F38
|
||||
|
||||
// Princess Connect Re:Dive (iOS/Android/PC)
|
||||
{3201512}, // 000000000030D9E8
|
||||
|
||||
// PriPara: All Idol Perfect Stage (Takara Tomy) [Switch]
|
||||
{217735759}, // 000000000CFA624F
|
||||
|
||||
// Space Invaders Extreme (Taito Corporation, Backbone Entertainment) [PC]
|
||||
{91380310056}, // 0000001546B0E028
|
||||
|
||||
// CR Another God Hades Advent (Universal Entertainment Corporation) [iOS/Android]
|
||||
{64813795}, // 0000000003DCFAE3
|
||||
|
||||
// Onsen Musume: Yunohana Kore Kushon (Android) voices
|
||||
{6667}, // 0000000000001A0B
|
||||
|
||||
/* Libra of Precatus (Android) */
|
||||
{7894523655423589588}, // 6D8EFB700870FCD4
|
||||
|
||||
/* Mashiro Witch (Android) */
|
||||
{6183755869466481156}, // 55D11D3349495204
|
||||
|
||||
/* Iris Mysteria! (Android) */
|
||||
{62049655719861786}, // 00DC71D5479E1E1A
|
||||
|
||||
/* Kotodaman (Android) */
|
||||
{19850716}, // 00000000012EE5DC
|
||||
|
||||
/* Puchiguru Love Live! (Android) */
|
||||
{355541041372}, // 00000052C7E5C0DC
|
||||
|
||||
/* Dolls Order (Android) */
|
||||
{153438415134838}, // 00008B8D2A3AA076
|
||||
|
||||
/* Fantasy Life Online (Android) */
|
||||
{123456789}, // 00000000075BCD15
|
||||
|
||||
/* Wonder Gravity (Android) */
|
||||
{30623969886430861}, // 006CCC569EB1668D
|
||||
|
||||
/* Ryu ga Gotoku Online (Android) */
|
||||
{59361939}, // 000000000389CA93
|
||||
|
||||
/* Sengoku BASARA Battle Party (Android) */
|
||||
{836575858265}, // 000000C2C7CE8E59
|
||||
|
||||
/* DAME x PRINCE (Android) */
|
||||
{217019410378917901}, // 030302010100080D
|
||||
|
||||
/* Uta Macross SmaPho De Culture (Android) */
|
||||
{396798934275978741}, // 0581B68744C5F5F5
|
||||
|
||||
/* Touhou Cannonball (Android) */
|
||||
{5465717035832233}, // 00136B0A6A5D13A9
|
||||
|
||||
/* Love Live! School idol festival ALL STARS (Android) */
|
||||
{6498535309877346413}, // 5A2F6F6F0192806D
|
||||
|
||||
/* BLACKSTAR -Theater Starless- (Android) */
|
||||
{121837007188}, // 0000001C5E0D3154
|
||||
|
||||
/* Dragalia Lost (Cygames) [iOS/Android] */
|
||||
{2967411924141, subkeys_dgl, sizeof(subkeys_dgl) / sizeof(subkeys_dgl[0]) }, // 000002B2E7889CAD
|
||||
|
||||
};
|
||||
|
||||
#endif/*_HCA_KEYS_H_*/
|
||||
#ifndef _HCA_KEYS_H_
|
||||
#define _HCA_KEYS_H_
|
||||
|
||||
#include "hca_keys_awb.h"
|
||||
|
||||
typedef struct {
|
||||
uint64_t key; /* hca key or seed ('user') key */
|
||||
const uint16_t *subkeys; /* scramble subkey table for seed key */
|
||||
size_t subkeys_size; /* size of the derivation subkey table */
|
||||
} hcakey_info;
|
||||
|
||||
|
||||
/**
|
||||
* List of known keys, extracted from the game files (mostly found in 2ch.net).
|
||||
* CRI's tools expect an unsigned 64 bit number string, but keys are commonly found online in hex form.
|
||||
* Keys only use 56 bits though, so the upper 8 bits can be ignored.
|
||||
*
|
||||
* Some ACB+AWB after mid 2018 use a user seed key + a scramble subkey in the AWB (normally 16b LE at 0x0e)
|
||||
* to create the final HCA key, which means there is one key per AWB (so most HCA have a unique key).
|
||||
* vgmstream derives the key if subkey table is provided.
|
||||
*/
|
||||
static const hcakey_info hcakey_list[] = {
|
||||
|
||||
// CRI HCA decoder default
|
||||
{9621963164387704}, // CF222F1FE0748978
|
||||
|
||||
// Phantasy Star Online 2 (multi?)
|
||||
// used by most console games
|
||||
{0xCC55463930DBE1AB}, // CC55463930DBE1AB / 14723751768204501419
|
||||
|
||||
// Old Phantasy Star Online 2 (multi?)
|
||||
{61891147883431481}, // 30DBE1ABCC554639
|
||||
|
||||
// Jojo All Star Battle (PS3)
|
||||
{19700307}, // 00000000012C9A53
|
||||
|
||||
// Ro-Kyu-Bu! Himitsu no Otoshimono (PSP)
|
||||
{2012082716}, // 0000000077EDF21C
|
||||
|
||||
// VRIDGE Inc. games:
|
||||
// - HatsuKare * Renai Debut Sengen! (PSP)
|
||||
// - Seitokai no Ichizon Lv. 2 Portable (PSP)
|
||||
// - Koi wa Kousoku ni Shibararenai! (PSP)
|
||||
// - StormLover 2nd (PSP)
|
||||
// - Prince of Stride (PSVita)
|
||||
// - Ro-Kyu-Bu! Naisho no Shutter Chance (PSVita)
|
||||
{1234253142}, // 0000000049913556
|
||||
|
||||
// Idolm@ster Cinderella Stage (iOS/Android)
|
||||
// Shadowverse (iOS/Android)
|
||||
{59751358413602}, // 00003657F27E3B22
|
||||
|
||||
// Grimoire ~Shiritsu Grimoire Mahou Gakuen~ (iOS/Android)
|
||||
{5027916581011272}, // 0011DCDD0DC57F48
|
||||
|
||||
// Idol Connect (iOS/Android)
|
||||
{2424}, // 0000000000000978
|
||||
|
||||
// Kamen Rider Battle Rush (iOS/Android)
|
||||
{29423500797988784}, // 00688884A11CCFB0
|
||||
|
||||
// SD Gundam Strikers (iOS/Android)
|
||||
{30260840980773}, // 00001B85A6AD6125
|
||||
|
||||
// Sonic Runners (iOS/Android)
|
||||
{19910623}, // 00000000012FCFDF
|
||||
|
||||
// Fate/Grand Order (iOS/Android) base assets
|
||||
{12345}, // 0000000000003039
|
||||
|
||||
// Fate/Grand Order (iOS/Android) download assets *unconfirmed
|
||||
{9117927877783581796}, // 7E89631892EBF464
|
||||
|
||||
// Raramagi (iOS/Android)
|
||||
{45719322}, // 0000000002B99F1A
|
||||
|
||||
// Idolm@ster Million Live (iOS/Android)
|
||||
{765765765765765}, // 0002B875BC731A85
|
||||
|
||||
// Kurokishi to Shiro no Maou (iOS/Android)
|
||||
{3003875739822025258}, // 29AFE911F5816A2A
|
||||
|
||||
// Puella Magi Madoka Magica Side Story: Magia Record (iOS/Android)
|
||||
// Hortensia Saga (iOS/Android)
|
||||
{20536401}, // 0000000001395C51
|
||||
|
||||
// The Tower of Princess (iOS/Android)
|
||||
{9101518402445063}, // 002055C8634B5F07
|
||||
|
||||
// Fallen Princess (iOS/Android)
|
||||
{145552191146490718}, // 02051AF25990FB5E
|
||||
|
||||
// Diss World (iOS/Android)
|
||||
{9001712656335836006}, // 7CEC81F7C3091366
|
||||
|
||||
// Ikemen Vampire - Ijin-tachi to Koi no Yuuwaku (iOS/Android)
|
||||
{45152594117267709}, // 00A06A0B8D0C10FD
|
||||
|
||||
// Super Robot Wars X-Omega (iOS/Android)
|
||||
{165521992944278}, // 0000968A97978A96
|
||||
|
||||
// BanG Dream! Girls Band Party! (iOS/Android)
|
||||
{8910}, // 00000000000022CE
|
||||
|
||||
// Tokyo 7th Sisters (iOS/Android) *unconfirmed
|
||||
{0xFDAE531AAB414BA1}, // FDAE531AAB414BA1
|
||||
|
||||
// One Piece Dance Battle (iOS/Android)
|
||||
{1905818}, // 00000000001D149A
|
||||
|
||||
// Derby Stallion Masters (iOS/Android)
|
||||
{19840202}, // 00000000012EBCCA
|
||||
|
||||
// World Chain (iOS/Android)
|
||||
{4892292804961027794}, // 43E4EA62B8E6C6D2
|
||||
|
||||
// Yuuki Yuuna wa Yuusha de aru - Hanayui no Kirameki / Yuyuyui (iOS/Android)
|
||||
{4867249871962584729}, // 438BF1F883653699
|
||||
|
||||
// Tekken Mobile (iOS/Android)
|
||||
{0xFFFFFFFFFFFFFFFF}, // FFFFFFFFFFFFFFFF / 18446744073709551615
|
||||
|
||||
// Tales of the Rays (iOS/Android)
|
||||
{9516284}, // 00000000009134FC
|
||||
|
||||
// Skylock - Kamigami to Unmei no Itsutsuko (iOS/Android)
|
||||
{49160768297}, // 0000000B7235CB29
|
||||
|
||||
// Tokyo Ghoul: Re Invoke (iOS/Android)
|
||||
{6929101074247145}, // 00189DFB1024ADE9
|
||||
|
||||
// Azur Lane (iOS/Android)
|
||||
{621561580448882}, // 0002354E95356C72
|
||||
|
||||
// One Piece Treasure Cruise (iOS/Android)
|
||||
{1224}, // 00000000000004C8
|
||||
|
||||
// Schoolgirl Strikers ~Twinkle Melodies~ (iOS/Android)
|
||||
{0xDB5B61B8343D0000}, // DB5B61B8343D0000
|
||||
|
||||
// Bad Apple Wars (PSVita)
|
||||
{241352432}, // 000000000E62BEF0
|
||||
|
||||
// Koi to Senkyo to Chocolate Portable (PSP)
|
||||
{243812156}, // 000000000E88473C
|
||||
|
||||
// Custom Drive (PSP)
|
||||
{2012062010}, // 0000000077EDA13A
|
||||
|
||||
// Root Letter (PSVita)
|
||||
{1547531215412131}, // 00057F78B05F9BA3
|
||||
|
||||
// Pro Evolution Soccer 2018 / Winning Eleven 2018 (Android)
|
||||
{14121473}, // 0000000000D77A01
|
||||
|
||||
// Kirara Fantasia (Android/iOS)
|
||||
{51408295487268137}, // 00B6A3928706E529
|
||||
|
||||
// A3! (iOS/Android)
|
||||
{914306251}, // 00000000367F34CB
|
||||
|
||||
// Weekly Shonen Jump: Ore Collection! (iOS/Android)
|
||||
{11708691}, // 0000000000B2A913
|
||||
|
||||
// Monster Gear Versus (iOS/Android)
|
||||
{0xB1E30F346415B475}, // B1E30F346415B475
|
||||
|
||||
// Yumeiro Cast (iOS/Android)
|
||||
{14418}, // 0000000000003852
|
||||
|
||||
// Ikki Tousen: Straight Striker (iOS/Android)
|
||||
{1000}, // 00000000000003E8
|
||||
|
||||
// Zero kara Hajimeru Mahou no Sho (iOS/Android)
|
||||
{0xD2E836E662F20000}, // D2E836E662F20000
|
||||
|
||||
// Soul Reverse Zero (iOS/Android)
|
||||
{2873513618}, // 00000000AB465692
|
||||
|
||||
// Jojo's Bizarre Adventure: Diamond Records (iOS/Android) [additional data]
|
||||
{0x820212864CAB35DE}, // 820212864CAB35DE
|
||||
|
||||
// HUNTER x HUNTER: World Hunt (iOS/Android)
|
||||
{71777214294589695}, // 00FF00FF00FF00FF
|
||||
|
||||
// \Comepri/ Comedy Prince (iOS/Android)
|
||||
{201537197868}, // 0000002EEC8D972C
|
||||
|
||||
// Puzzle of Empires (iOS/Android)
|
||||
{13687846}, // 0000000000D0DC26
|
||||
|
||||
// Aozora Under Girls! (iOS/Android)
|
||||
{4988006236073}, // 000004895C56FFA9
|
||||
|
||||
// Castle & Dragon (iOS/Android)
|
||||
{20140528}, // 00000000013351F0
|
||||
|
||||
// Uta no Prince sama Shining Live (iOS/Android)
|
||||
{2122831366}, // 000000007E87D606
|
||||
|
||||
// Sevens Story (iOS/Android)
|
||||
{629427372852}, // 000000928CCB8334
|
||||
|
||||
// MinGol: Everybody's Golf (iOS/Android)
|
||||
{1430028151061218}, // 0005149A5FF67AE2
|
||||
|
||||
// AKB48 Group Tsui ni Koushiki Otoge demashita. (iOS/Android)
|
||||
{831021912315111419}, // 0B886206BC1BA7FB
|
||||
|
||||
// Sen no Kaizoku (iOS/Android)
|
||||
{81368371967}, // 00000012F1EED2FF
|
||||
|
||||
// I Chu (iOS/Android)
|
||||
{13456}, // 0000000000003490
|
||||
|
||||
// Shinobi Nightmare (iOS/Android)
|
||||
{369481198260487572}, // 0520A93135808594
|
||||
|
||||
// Bungo Stray Dogs: Mayoi Inu Kaikitan (iOS/Android)
|
||||
{1655728931134731873}, // 16FA54B0C09F7661
|
||||
|
||||
// Super Sentai Legend Wars (iOS/Android)
|
||||
{4017992759667450}, // 000E4657D7266AFA
|
||||
|
||||
// Metal Saga: The Ark of Wastes (iOS/Android)
|
||||
{100097101118097115}, // 01639DC87B30C6DB
|
||||
|
||||
// Taga Tame no Alchemist (iOS/Android)
|
||||
{5047159794308}, // 00000497222AAA84
|
||||
|
||||
// Shin Tennis no Ouji-sama: Rising Beat (iOS/Android) voices?
|
||||
{4902201417679}, // 0000047561F95FCF
|
||||
|
||||
// Kai-ri-Sei Million Arthur (Vita)
|
||||
{1782351729464341796}, // 18BC2F7463867524
|
||||
|
||||
// Dx2 Shin Megami Tensei Liberation (iOS/Android)
|
||||
{118714477}, // 000000000713706D
|
||||
|
||||
// Oira (Cygames) [iOS/Android]
|
||||
{46460622}, // 0000000002C4EECE
|
||||
|
||||
// Dragon Ball Legends (Bandai Namco) [iOS/Android]
|
||||
{7335633962698440504}, // 65CD683924EE7F38
|
||||
|
||||
// Princess Connect Re:Dive (iOS/Android/PC)
|
||||
{3201512}, // 000000000030D9E8
|
||||
|
||||
// PriPara: All Idol Perfect Stage (Takara Tomy) [Switch]
|
||||
{217735759}, // 000000000CFA624F
|
||||
|
||||
// Space Invaders Extreme (Taito Corporation, Backbone Entertainment) [PC]
|
||||
{91380310056}, // 0000001546B0E028
|
||||
|
||||
// CR Another God Hades Advent (Universal Entertainment Corporation) [iOS/Android]
|
||||
{64813795}, // 0000000003DCFAE3
|
||||
|
||||
// Onsen Musume: Yunohana Kore Kushon (Android) voices
|
||||
{6667}, // 0000000000001A0B
|
||||
|
||||
/* Libra of Precatus (Android) */
|
||||
{7894523655423589588}, // 6D8EFB700870FCD4
|
||||
|
||||
/* Mashiro Witch (Android) */
|
||||
{6183755869466481156}, // 55D11D3349495204
|
||||
|
||||
/* Iris Mysteria! (Android) */
|
||||
{62049655719861786}, // 00DC71D5479E1E1A
|
||||
|
||||
/* Kotodaman (Android) */
|
||||
{19850716}, // 00000000012EE5DC
|
||||
|
||||
/* Puchiguru Love Live! (Android) */
|
||||
{355541041372}, // 00000052C7E5C0DC
|
||||
|
||||
/* Dolls Order (Android) */
|
||||
{153438415134838}, // 00008B8D2A3AA076
|
||||
|
||||
/* Fantasy Life Online (Android) */
|
||||
{123456789}, // 00000000075BCD15
|
||||
|
||||
/* Wonder Gravity (Android) */
|
||||
{30623969886430861}, // 006CCC569EB1668D
|
||||
|
||||
/* Ryu ga Gotoku Online (Android) */
|
||||
{59361939}, // 000000000389CA93
|
||||
|
||||
/* Sengoku BASARA Battle Party (Android) */
|
||||
{836575858265}, // 000000C2C7CE8E59
|
||||
|
||||
/* DAME x PRINCE (Android) */
|
||||
{217019410378917901}, // 030302010100080D
|
||||
|
||||
/* Uta Macross SmaPho De Culture (Android) */
|
||||
{396798934275978741}, // 0581B68744C5F5F5
|
||||
|
||||
/* Touhou Cannonball (Android) */
|
||||
{5465717035832233}, // 00136B0A6A5D13A9
|
||||
|
||||
/* Love Live! School idol festival ALL STARS (Android) */
|
||||
{6498535309877346413}, // 5A2F6F6F0192806D
|
||||
|
||||
/* BLACKSTAR -Theater Starless- (Android) */
|
||||
{121837007188}, // 0000001C5E0D3154
|
||||
|
||||
/* Nogizaka46 Rhythm Festival (Android) */
|
||||
{5613126134333697}, // 0013F11BC5510101
|
||||
|
||||
/* IDOLiSH7 (Android) */
|
||||
{8548758374946935437}, // 76A34A72E15B928D
|
||||
|
||||
/* Phantom of the Kill (Android) */
|
||||
{33624594140214547}, // 00777563E571B513
|
||||
|
||||
/* Dankira!!! Boys, be DANCING! (Android) */
|
||||
{3957325206121219506}, // 36EB3E4EE38E05B2
|
||||
|
||||
/* Dragalia Lost (iOS/Android) */
|
||||
{2967411924141, subkeys_dgl, sizeof(subkeys_dgl) / sizeof(subkeys_dgl[0]) }, // 000002B2E7889CAD
|
||||
|
||||
};
|
||||
|
||||
#endif/*_HCA_KEYS_H_*/
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
#include "meta.h"
|
||||
|
||||
|
||||
/* IVAG - Namco header (from NUS3) [THE iDOLM@STER 2 (PS3), THE iDOLM@STER: Gravure For You! (PS3)] */
|
||||
VGMSTREAM * init_vgmstream_ivag(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
|
||||
int loop_flag = 0;
|
||||
int channel_count;
|
||||
|
||||
/* checks */
|
||||
/* .ivag: header id (since format can't be found outside NUS3) */
|
||||
if (!check_extensions(streamFile, "ivag"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00,streamFile) != 0x49564147) /* "IVAG" */
|
||||
goto fail;
|
||||
|
||||
/* 0x04: null */
|
||||
channel_count = read_32bitBE(0x08, streamFile);
|
||||
loop_flag = (read_32bitBE(0x18, streamFile) != 0);
|
||||
|
||||
/* skip VAGp headers per channel (size 0x40) */
|
||||
start_offset = 0x40 + (0x40 * channel_count);
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_IVAG;
|
||||
|
||||
vgmstream->sample_rate = read_32bitBE(0x0C,streamFile);
|
||||
vgmstream->num_samples = read_32bitBE(0x10,streamFile);
|
||||
vgmstream->loop_start_sample = read_32bitBE(0x14,streamFile);
|
||||
vgmstream->loop_end_sample = read_32bitBE(0x18,streamFile);
|
||||
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = read_32bitBE(0x1C,streamFile);
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
|
@ -28,7 +28,7 @@ VGMSTREAM * init_vgmstream_ngc_mdsp_std(STREAMFILE *streamFile);
|
|||
VGMSTREAM * init_vgmstream_ngc_dsp_stm(STREAMFILE *streamFile);
|
||||
VGMSTREAM * init_vgmstream_ngc_mpdsp(STREAMFILE *streamFile);
|
||||
VGMSTREAM * init_vgmstream_ngc_dsp_std_int(STREAMFILE *streamFile);
|
||||
VGMSTREAM * init_vgmstream_idsp_nus3(STREAMFILE *streamFile);
|
||||
VGMSTREAM * init_vgmstream_idsp_namco(STREAMFILE *streamFile);
|
||||
VGMSTREAM * init_vgmstream_sadb(STREAMFILE *streamFile);
|
||||
VGMSTREAM * init_vgmstream_sadf(STREAMFILE *streamFile);
|
||||
VGMSTREAM * init_vgmstream_idsp_tt(STREAMFILE *streamFile);
|
||||
|
@ -542,7 +542,7 @@ VGMSTREAM * init_vgmstream_mss(STREAMFILE* streamFile);
|
|||
|
||||
VGMSTREAM * init_vgmstream_ps2_hsf(STREAMFILE* streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ps3_ivag(STREAMFILE* streamFile);
|
||||
VGMSTREAM * init_vgmstream_ivag(STREAMFILE* streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ps2_2pfs(STREAMFILE* streamFile);
|
||||
|
||||
|
@ -746,6 +746,7 @@ VGMSTREAM * init_vgmstream_hd3_bd3(STREAMFILE *streamFile);
|
|||
VGMSTREAM * init_vgmstream_bnk_sony(STREAMFILE *streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_nus3bank(STREAMFILE *streamFile);
|
||||
VGMSTREAM * init_vgmstream_nus3bank_encrypted(STREAMFILE *streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_scd_sscf(STREAMFILE *streamFile);
|
||||
|
||||
|
|
|
@ -1,128 +1,128 @@
|
|||
#include "meta.h"
|
||||
#include "mta2_streamfile.h"
|
||||
|
||||
|
||||
/* MTA2 - found in Metal Gear Solid 4 (PS3) */
|
||||
VGMSTREAM * init_vgmstream_mta2(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channel_count, sample_rate;
|
||||
int32_t loop_start, loop_end;
|
||||
|
||||
|
||||
/* checks */
|
||||
if ( !check_extensions(streamFile,"mta2"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00,streamFile) != 0x4d544132) /* "MTA2" */
|
||||
goto fail;
|
||||
/* allow truncated files for now? */
|
||||
//if (read_32bitBE(0x04, streamFile) + 0x08 != get_streamfile_size(streamFile))
|
||||
// goto fail;
|
||||
|
||||
/* base header (everything is very similar to MGS3's MTAF but BE) */
|
||||
/* 0x08(4): version? (1), 0x0c(52): null */
|
||||
|
||||
/* HEAD chunk */
|
||||
if (read_32bitBE(0x40, streamFile) != 0x48454144) /* "HEAD" */
|
||||
goto fail;
|
||||
if (read_32bitBE(0x44, streamFile) != 0xB0) /* HEAD size */
|
||||
goto fail;
|
||||
|
||||
/* 0x48(4): null, 0x4c: ? (0x10), 0x50(4): 0x7F (vol?), 0x54(2): 0x40 (pan?) */
|
||||
channel_count = read_16bitBE(0x56, streamFile); /* counting all tracks */
|
||||
/* 0x60(4): full block size (0x110 * channels), indirectly channels_per_track = channels / (block_size / 0x110) */
|
||||
/* 0x80 .. 0xf8: null */
|
||||
|
||||
loop_start = read_32bitBE(0x58, streamFile);
|
||||
loop_end = read_32bitBE(0x5c, streamFile);
|
||||
loop_flag = (loop_start != loop_end); /* also flag possibly @ 0x73 */
|
||||
#if 0
|
||||
/* those values look like some kind of loop offsets */
|
||||
if (loop_start/0x100 != read_32bitBE(0x68, streamFile) ||
|
||||
loop_end /0x100 != read_32bitBE(0x6C, streamFile) ) {
|
||||
VGM_LOG("MTA2: wrong loop points\n");
|
||||
goto fail;
|
||||
}
|
||||
#endif
|
||||
|
||||
sample_rate = (int)read_f32be(0x7c, streamFile); /* sample rate in 32b float (WHY?) typically 48000.0 */
|
||||
if (sample_rate == 0)
|
||||
sample_rate = 48000; /* default when not specified (most of the time) */
|
||||
|
||||
|
||||
/* TRKP chunks (x16) */
|
||||
/* just seem to contain pan/vol stuff (0x7f/0x40), TRKP per track (sometimes +1 main track?) */
|
||||
/* there is channel layout bitmask at 0x0f (ex. 1ch = 0x04, 3ch = 0x07, 4ch = 0x33, 6ch = 0x3f), surely:
|
||||
* FL 0x01, FR 0x02, FC = 0x04, BL = 0x08, BR = 0x10, BC = 0x20 */
|
||||
|
||||
start_offset = 0x800;
|
||||
|
||||
/* DATA chunk */
|
||||
if (read_32bitBE(0x7f8, streamFile) != 0x44415441) // "DATA"
|
||||
goto fail;
|
||||
//if (read_32bitBE(0x7fc, streamFile) + start_offset != get_streamfile_size(streamFile))
|
||||
// goto fail;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = loop_end;
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
|
||||
vgmstream->coding_type = coding_MTA2;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->meta_type = meta_MTA2;
|
||||
|
||||
/* open the file for reading */
|
||||
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ****************************************************************************** */
|
||||
|
||||
/* MTA2 in containers */
|
||||
VGMSTREAM * init_vgmstream_mta2_container(STREAMFILE *streamFile) {
|
||||
VGMSTREAM *vgmstream = NULL;
|
||||
STREAMFILE *temp_streamFile = NULL;
|
||||
off_t subfile_offset;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .dbm: iPod metadata + mta2 with KCEJ blocks, .bgm: mta2 with KCEJ blocks (fake?) */
|
||||
if ( !check_extensions(streamFile,"dbm,bgm,mta2"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00,streamFile) == 0x444C424D) { /* "DLBM" */
|
||||
subfile_offset = 0x800;
|
||||
}
|
||||
else if (read_32bitBE(0x00,streamFile) == 0x00000010) {
|
||||
subfile_offset = 0x00;
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
/* subfile size is implicit in KCEJ blocks */
|
||||
|
||||
temp_streamFile = setup_mta2_streamfile(streamFile, subfile_offset, 1, "mta2");
|
||||
if (!temp_streamFile) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_mta2(temp_streamFile);
|
||||
close_streamfile(temp_streamFile);
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "mta2_streamfile.h"
|
||||
|
||||
|
||||
/* MTA2 - found in Metal Gear Solid 4 (PS3) */
|
||||
VGMSTREAM * init_vgmstream_mta2(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channel_count, sample_rate;
|
||||
int32_t loop_start, loop_end;
|
||||
|
||||
|
||||
/* checks */
|
||||
if ( !check_extensions(streamFile,"mta2"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00,streamFile) != 0x4d544132) /* "MTA2" */
|
||||
goto fail;
|
||||
/* allow truncated files for now? */
|
||||
//if (read_32bitBE(0x04, streamFile) + 0x08 != get_streamfile_size(streamFile))
|
||||
// goto fail;
|
||||
|
||||
/* base header (everything is very similar to MGS3's MTAF but BE) */
|
||||
/* 0x08(4): version? (1), 0x0c(52): null */
|
||||
|
||||
/* HEAD chunk */
|
||||
if (read_32bitBE(0x40, streamFile) != 0x48454144) /* "HEAD" */
|
||||
goto fail;
|
||||
if (read_32bitBE(0x44, streamFile) != 0xB0) /* HEAD size */
|
||||
goto fail;
|
||||
|
||||
/* 0x48(4): null, 0x4c: ? (0x10), 0x50(4): 0x7F (vol?), 0x54(2): 0x40 (pan?) */
|
||||
channel_count = read_16bitBE(0x56, streamFile); /* counting all tracks */
|
||||
/* 0x60(4): full block size (0x110 * channels), indirectly channels_per_track = channels / (block_size / 0x110) */
|
||||
/* 0x80 .. 0xf8: null */
|
||||
|
||||
loop_start = read_32bitBE(0x58, streamFile);
|
||||
loop_end = read_32bitBE(0x5c, streamFile);
|
||||
loop_flag = (loop_start != loop_end); /* also flag possibly @ 0x73 */
|
||||
#if 0
|
||||
/* those values look like some kind of loop offsets */
|
||||
if (loop_start/0x100 != read_32bitBE(0x68, streamFile) ||
|
||||
loop_end /0x100 != read_32bitBE(0x6C, streamFile) ) {
|
||||
VGM_LOG("MTA2: wrong loop points\n");
|
||||
goto fail;
|
||||
}
|
||||
#endif
|
||||
|
||||
sample_rate = (int)read_f32be(0x7c, streamFile); /* sample rate in 32b float (WHY?) typically 48000.0 */
|
||||
if (sample_rate == 0)
|
||||
sample_rate = 48000; /* default when not specified (most of the time) */
|
||||
|
||||
|
||||
/* TRKP chunks (x16) */
|
||||
/* just seem to contain pan/vol stuff (0x7f/0x40), TRKP per track (sometimes +1 main track?) */
|
||||
/* there is channel layout bitmask at 0x0f (ex. 1ch = 0x04, 3ch = 0x07, 4ch = 0x33, 6ch = 0x3f), surely:
|
||||
* FL 0x01, FR 0x02, FC = 0x04, BL = 0x08, BR = 0x10, BC = 0x20 */
|
||||
|
||||
start_offset = 0x800;
|
||||
|
||||
/* DATA chunk */
|
||||
if (read_32bitBE(0x7f8, streamFile) != 0x44415441) // "DATA"
|
||||
goto fail;
|
||||
//if (read_32bitBE(0x7fc, streamFile) + start_offset != get_streamfile_size(streamFile))
|
||||
// goto fail;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = loop_end;
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
|
||||
vgmstream->coding_type = coding_MTA2;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->meta_type = meta_MTA2;
|
||||
|
||||
/* open the file for reading */
|
||||
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ****************************************************************************** */
|
||||
|
||||
/* MTA2 in containers */
|
||||
VGMSTREAM * init_vgmstream_mta2_container(STREAMFILE *streamFile) {
|
||||
VGMSTREAM *vgmstream = NULL;
|
||||
STREAMFILE *temp_streamFile = NULL;
|
||||
off_t subfile_offset;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .dbm: iPod metadata + mta2 with KCEJ blocks, .bgm: mta2 with KCEJ blocks (fake?) */
|
||||
if ( !check_extensions(streamFile,"dbm,bgm,mta2"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00,streamFile) == 0x444C424D) { /* "DLBM" */
|
||||
subfile_offset = 0x800;
|
||||
}
|
||||
else if (read_32bitBE(0x00,streamFile) == 0x00000010) {
|
||||
subfile_offset = 0x00;
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
/* subfile size is implicit in KCEJ blocks */
|
||||
|
||||
temp_streamFile = setup_mta2_streamfile(streamFile, subfile_offset, 1, "mta2");
|
||||
if (!temp_streamFile) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_mta2(temp_streamFile);
|
||||
close_streamfile(temp_streamFile);
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -1,135 +1,135 @@
|
|||
#ifndef _MTA2_STREAMFILE_H_
|
||||
#define _MTA2_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
int big_endian;
|
||||
uint32_t target_type;
|
||||
off_t stream_offset;
|
||||
size_t stream_size;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* fake offset */
|
||||
off_t physical_offset; /* actual offset */
|
||||
size_t block_size; /* current size */
|
||||
size_t skip_size; /* size from block start to reach data */
|
||||
size_t data_size; /* usable size in a block */
|
||||
|
||||
size_t logical_size;
|
||||
} mta2_io_data;
|
||||
|
||||
|
||||
static size_t mta2_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, mta2_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
uint32_t (*read_u32)(off_t,STREAMFILE*) = data->big_endian ? read_u32be : read_u32le;
|
||||
|
||||
|
||||
/* re-start when previous offset (can't map logical<>physical offsets) */
|
||||
if (data->logical_offset < 0 || offset < data->logical_offset) {
|
||||
;VGM_LOG("IO restart: offset=%lx + %x, po=%lx, lo=%lx\n", offset, length, data->physical_offset, data->logical_offset);
|
||||
data->physical_offset = data->stream_offset;
|
||||
data->logical_offset = 0x00;
|
||||
data->data_size = 0;
|
||||
}
|
||||
|
||||
/* read blocks */
|
||||
while (length > 0) {
|
||||
|
||||
/* ignore EOF */
|
||||
if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* process new block */
|
||||
if (data->data_size == 0) {
|
||||
uint32_t block_type, block_size, block_track;
|
||||
|
||||
block_type = read_u32(data->physical_offset+0x00, sf); /* subtype and type */
|
||||
block_size = read_u32(data->physical_offset+0x04, sf);
|
||||
//block_unk = read_u32(data->physical_offset+0x08, streamfile); /* usually 0 except for 0xF0 'end' block */
|
||||
block_track = read_u32(data->physical_offset+0x0c, sf);
|
||||
|
||||
if (block_type != data->target_type || block_size == 0xFFFFFFFF)
|
||||
break;
|
||||
|
||||
data->block_size = block_size;
|
||||
data->skip_size = 0x10;
|
||||
data->data_size = block_size - data->skip_size;
|
||||
/* no audio data (padding block), but write first (header) */
|
||||
if (block_track == 0 && data->logical_offset > 0)
|
||||
data->data_size = 0;
|
||||
}
|
||||
|
||||
/* move to next block */
|
||||
if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) {
|
||||
data->physical_offset += data->block_size;
|
||||
data->logical_offset += data->data_size;
|
||||
data->data_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* read data */
|
||||
{
|
||||
size_t bytes_consumed, bytes_done, to_read;
|
||||
|
||||
bytes_consumed = offset - data->logical_offset;
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, sf);
|
||||
|
||||
total_read += bytes_done;
|
||||
dest += bytes_done;
|
||||
offset += bytes_done;
|
||||
length -= bytes_done;
|
||||
|
||||
if (bytes_done != to_read || bytes_done == 0) {
|
||||
break; /* error/EOF */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t mta2_io_size(STREAMFILE *streamfile, mta2_io_data* data) {
|
||||
uint8_t buf[1];
|
||||
|
||||
if (data->logical_size > 0)
|
||||
return data->logical_size;
|
||||
|
||||
/* force a fake read at max offset, to get max logical_offset (will be reset next read) */
|
||||
mta2_io_read(streamfile, buf, 0x7FFFFFFF, 1, data);
|
||||
data->logical_size = data->logical_offset;
|
||||
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
/* Handles removing KCE Japan-style blocks in MTA2 streams
|
||||
* (these blocks exist in most KCEJ games and aren't actually related to audio) */
|
||||
static STREAMFILE* setup_mta2_streamfile(STREAMFILE *sf, off_t stream_offset, int big_endian, const char *extension) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
mta2_io_data io_data = {0};
|
||||
uint32_t (*read_u32)(off_t,STREAMFILE*) = big_endian ? read_u32be : read_u32le;
|
||||
|
||||
|
||||
/* blocks must start with a 'new sub-stream' id */
|
||||
if (read_u32(stream_offset+0x00, sf) != 0x00000010)
|
||||
return NULL;
|
||||
|
||||
io_data.target_type = read_u32(stream_offset + 0x0c, sf);
|
||||
io_data.stream_offset = stream_offset + 0x10;
|
||||
io_data.stream_size = get_streamfile_size(sf) - io_data.stream_offset;
|
||||
io_data.big_endian = big_endian;
|
||||
io_data.logical_offset = -1; /* force phys offset reset */
|
||||
|
||||
/* setup subfile */
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(mta2_io_data), mta2_io_read, mta2_io_size);
|
||||
if (extension)
|
||||
new_sf = open_fakename_streamfile_f(new_sf, NULL, extension);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _MTA2_STREAMFILE_H_ */
|
||||
#ifndef _MTA2_STREAMFILE_H_
|
||||
#define _MTA2_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
int big_endian;
|
||||
uint32_t target_type;
|
||||
off_t stream_offset;
|
||||
size_t stream_size;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* fake offset */
|
||||
off_t physical_offset; /* actual offset */
|
||||
size_t block_size; /* current size */
|
||||
size_t skip_size; /* size from block start to reach data */
|
||||
size_t data_size; /* usable size in a block */
|
||||
|
||||
size_t logical_size;
|
||||
} mta2_io_data;
|
||||
|
||||
|
||||
static size_t mta2_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, mta2_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
uint32_t (*read_u32)(off_t,STREAMFILE*) = data->big_endian ? read_u32be : read_u32le;
|
||||
|
||||
|
||||
/* re-start when previous offset (can't map logical<>physical offsets) */
|
||||
if (data->logical_offset < 0 || offset < data->logical_offset) {
|
||||
;VGM_LOG("IO restart: offset=%lx + %x, po=%lx, lo=%lx\n", offset, length, data->physical_offset, data->logical_offset);
|
||||
data->physical_offset = data->stream_offset;
|
||||
data->logical_offset = 0x00;
|
||||
data->data_size = 0;
|
||||
}
|
||||
|
||||
/* read blocks */
|
||||
while (length > 0) {
|
||||
|
||||
/* ignore EOF */
|
||||
if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* process new block */
|
||||
if (data->data_size == 0) {
|
||||
uint32_t block_type, block_size, block_track;
|
||||
|
||||
block_type = read_u32(data->physical_offset+0x00, sf); /* subtype and type */
|
||||
block_size = read_u32(data->physical_offset+0x04, sf);
|
||||
//block_unk = read_u32(data->physical_offset+0x08, streamfile); /* usually 0 except for 0xF0 'end' block */
|
||||
block_track = read_u32(data->physical_offset+0x0c, sf);
|
||||
|
||||
if (block_type != data->target_type || block_size == 0xFFFFFFFF)
|
||||
break;
|
||||
|
||||
data->block_size = block_size;
|
||||
data->skip_size = 0x10;
|
||||
data->data_size = block_size - data->skip_size;
|
||||
/* no audio data (padding block), but write first (header) */
|
||||
if (block_track == 0 && data->logical_offset > 0)
|
||||
data->data_size = 0;
|
||||
}
|
||||
|
||||
/* move to next block */
|
||||
if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) {
|
||||
data->physical_offset += data->block_size;
|
||||
data->logical_offset += data->data_size;
|
||||
data->data_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* read data */
|
||||
{
|
||||
size_t bytes_consumed, bytes_done, to_read;
|
||||
|
||||
bytes_consumed = offset - data->logical_offset;
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, sf);
|
||||
|
||||
total_read += bytes_done;
|
||||
dest += bytes_done;
|
||||
offset += bytes_done;
|
||||
length -= bytes_done;
|
||||
|
||||
if (bytes_done != to_read || bytes_done == 0) {
|
||||
break; /* error/EOF */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t mta2_io_size(STREAMFILE *streamfile, mta2_io_data* data) {
|
||||
uint8_t buf[1];
|
||||
|
||||
if (data->logical_size > 0)
|
||||
return data->logical_size;
|
||||
|
||||
/* force a fake read at max offset, to get max logical_offset (will be reset next read) */
|
||||
mta2_io_read(streamfile, buf, 0x7FFFFFFF, 1, data);
|
||||
data->logical_size = data->logical_offset;
|
||||
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
/* Handles removing KCE Japan-style blocks in MTA2 streams
|
||||
* (these blocks exist in most KCEJ games and aren't actually related to audio) */
|
||||
static STREAMFILE* setup_mta2_streamfile(STREAMFILE *sf, off_t stream_offset, int big_endian, const char *extension) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
mta2_io_data io_data = {0};
|
||||
uint32_t (*read_u32)(off_t,STREAMFILE*) = big_endian ? read_u32be : read_u32le;
|
||||
|
||||
|
||||
/* blocks must start with a 'new sub-stream' id */
|
||||
if (read_u32(stream_offset+0x00, sf) != 0x00000010)
|
||||
return NULL;
|
||||
|
||||
io_data.target_type = read_u32(stream_offset + 0x0c, sf);
|
||||
io_data.stream_offset = stream_offset + 0x10;
|
||||
io_data.stream_size = get_streamfile_size(sf) - io_data.stream_offset;
|
||||
io_data.big_endian = big_endian;
|
||||
io_data.logical_offset = -1; /* force phys offset reset */
|
||||
|
||||
/* setup subfile */
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(mta2_io_data), mta2_io_read, mta2_io_size);
|
||||
if (extension)
|
||||
new_sf = open_fakename_streamfile_f(new_sf, NULL, extension);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _MTA2_STREAMFILE_H_ */
|
||||
|
|
|
@ -228,6 +228,12 @@ static int parse_musx_stream(STREAMFILE *streamFile, musx_header *musx) {
|
|||
musx->codec = DAT;
|
||||
break;
|
||||
|
||||
case 0x50435F5F: /* "PC__" */
|
||||
default_channels = 2;
|
||||
default_sample_rate = 44100;
|
||||
musx->codec = DAT;
|
||||
break;
|
||||
|
||||
default:
|
||||
VGM_LOG("MUSX: unknown platform %x\n", musx->platform);
|
||||
goto fail;
|
||||
|
@ -294,18 +300,26 @@ static int parse_musx_stream(STREAMFILE *streamFile, musx_header *musx) {
|
|||
musx->loop_flag = (musx->loop_start_sample >= 0);
|
||||
}
|
||||
|
||||
/* fix some v10 sizes */
|
||||
/* fix some v10 platform (like PSP) sizes */
|
||||
if (musx->stream_size == 0) {
|
||||
off_t offset;
|
||||
musx->stream_size = musx->file_size - musx->stream_offset;
|
||||
|
||||
/* remove padding */
|
||||
offset = musx->stream_offset + musx->stream_size - 0x04;
|
||||
while (offset > 0) {
|
||||
if (read_32bit(offset, streamFile) != 0xABABABAB)
|
||||
break;
|
||||
musx->stream_size -= 0x04;
|
||||
offset -= 0x04;
|
||||
/* always padded to nearest 0x800 sector */
|
||||
if (musx->stream_size > 0x800) {
|
||||
uint8_t buf[0x800];
|
||||
int pos;
|
||||
off_t offset = musx->stream_offset + musx->stream_size - 0x800;
|
||||
|
||||
if (read_streamfile(buf, offset, sizeof(buf), streamFile) != 0x800)
|
||||
goto fail;
|
||||
|
||||
pos = 0x800 - 0x04;
|
||||
while (pos > 0) {
|
||||
if (get_u32be(buf + pos) != 0xABABABAB)
|
||||
break;
|
||||
musx->stream_size -= 0x04;
|
||||
pos -= 0x04;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -334,9 +348,9 @@ static int parse_musx(STREAMFILE *streamFile, musx_header *musx) {
|
|||
musx->is_old = 1;
|
||||
break;
|
||||
|
||||
case 4: /* Spyro: A Hero's Tail (PS2/Xbox/GC) */
|
||||
case 4: /* Spyro: A Hero's Tail (PS2/Xbox/GC), Athens 2004 (PC) */
|
||||
case 5: /* Predator: Concrete Jungle (PS2/Xbox), Robots (GC) */
|
||||
case 6: /* Batman Begins (GC), Ice Age 2 (PS2) */
|
||||
case 6: /* Batman Begins (GC), Ice Age 2 (PS2/PC) */
|
||||
musx->platform = read_32bitBE(0x10,streamFile);
|
||||
/* 0x14: some id/hash? */
|
||||
/* 0x18: platform number? */
|
||||
|
@ -352,7 +366,10 @@ static int parse_musx(STREAMFILE *streamFile, musx_header *musx) {
|
|||
/* 0x1c: null */
|
||||
/* 0x20: hash */
|
||||
musx->tables_offset = 0; /* no tables */
|
||||
musx->big_endian = (musx->platform == 0x5749495F || musx->platform == 0x5053335F); /* "GC__" / "PS3_" (only after header) */
|
||||
musx->big_endian = (musx->platform == 0x47435F5F || /* "GC__" */
|
||||
musx->platform == 0x58455F5F || /* "XE__" */
|
||||
musx->platform == 0x5053335F || /* "PS3_" */
|
||||
musx->platform == 0x5749495F); /* "WII_" */
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -438,6 +455,7 @@ static int parse_musx(STREAMFILE *streamFile, musx_header *musx) {
|
|||
uint32_t miniheader = read_32bitBE(0x40, streamFile);
|
||||
switch(miniheader) {
|
||||
case 0x44415434: /* "DAT4" */
|
||||
case 0x44415435: /* "DAT5" */
|
||||
case 0x44415438: /* "DAT8" */
|
||||
/* found on PS3/Wii (but not always?) */
|
||||
musx->stream_size = read_32bitLE(0x44, streamFile);
|
||||
|
@ -446,7 +464,14 @@ static int parse_musx(STREAMFILE *streamFile, musx_header *musx) {
|
|||
musx->loops_offset = 0x50;
|
||||
break;
|
||||
default:
|
||||
musx->loops_offset = 0x30;
|
||||
if (read_32bitBE(0x30, streamFile) == 0x00 &&
|
||||
read_32bitBE(0x34, streamFile) == 0x00) {
|
||||
/* no subheader [Spider-Man 4 (Wii)] */
|
||||
musx->loops_offset = 0x00;
|
||||
} else {
|
||||
/* loop info only [G-Force (PS3)] */
|
||||
musx->loops_offset = 0x30;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -529,9 +554,6 @@ static int parse_musx(STREAMFILE *streamFile, musx_header *musx) {
|
|||
musx->channels = 1;
|
||||
}
|
||||
|
||||
|
||||
VGM_LOG("to=%lx, %lx, %x\n", target_offset, musx->stream_offset, musx->stream_size);
|
||||
|
||||
if (coef_size == 0)
|
||||
musx->coefs_offset = 0; /* disable for later detection */
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,119 +1,119 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
typedef enum { IDSP, OPUS, } nus3audio_codec;
|
||||
|
||||
/* .nus3audio - Namco's newest newest audio container [Super Smash Bros. Ultimate (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_nus3audio(STREAMFILE *streamFile) {
|
||||
VGMSTREAM *vgmstream = NULL;
|
||||
STREAMFILE *temp_streamFile = NULL;
|
||||
off_t subfile_offset = 0, name_offset = 0;
|
||||
size_t subfile_size = 0;
|
||||
nus3audio_codec codec;
|
||||
const char* fake_ext = NULL;
|
||||
int total_subsongs, target_subsong = streamFile->stream_index;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "nus3audio"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,streamFile) != 0x4E555333) /* "NUS3" */
|
||||
goto fail;
|
||||
if (read_32bitLE(0x04,streamFile) + 0x08 != get_streamfile_size(streamFile))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x08,streamFile) != 0x41554449) /* "AUDI" */
|
||||
goto fail;
|
||||
|
||||
|
||||
/* parse existing chunks */
|
||||
{
|
||||
off_t offset = 0x0c;
|
||||
size_t file_size = get_streamfile_size(streamFile);
|
||||
uint32_t codec_id = 0;
|
||||
|
||||
total_subsongs = 0;
|
||||
|
||||
while (offset < file_size) {
|
||||
uint32_t chunk_id = (uint32_t)read_32bitBE(offset+0x00, streamFile);
|
||||
size_t chunk_size = (size_t)read_32bitLE(offset+0x04, streamFile);
|
||||
|
||||
switch(chunk_id) {
|
||||
case 0x494E4458: /* "INDX": audio index */
|
||||
total_subsongs = read_32bitLE(offset+0x08 + 0x00,streamFile);
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
||||
break;
|
||||
|
||||
case 0x4E4D4F46: /* "NMOF": name offsets (absolute, inside TNNM) */
|
||||
name_offset = read_32bitLE(offset+0x08 + 0x04*(target_subsong-1),streamFile);
|
||||
break;
|
||||
|
||||
case 0x41444F46: /* "ADOF": audio offsets (absolute, inside PACK) */
|
||||
subfile_offset = read_32bitLE(offset+0x08 + 0x08*(target_subsong-1) + 0x00,streamFile);
|
||||
subfile_size = read_32bitLE(offset+0x08 + 0x08*(target_subsong-1) + 0x04,streamFile);
|
||||
break;
|
||||
|
||||
case 0x544E4944: /* "TNID": tone ids? */
|
||||
case 0x544E4E4D: /* "TNNM": tone names */
|
||||
case 0x4A554E4B: /* "JUNK": padding */
|
||||
case 0x5041434B: /* "PACK": main data */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
offset += 0x08 + chunk_size;
|
||||
}
|
||||
|
||||
if (total_subsongs == 0 || subfile_offset == 0 || subfile_size == 0) {
|
||||
VGM_LOG("NUS3AUDIO: subfile not found\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
codec_id = read_32bitBE(subfile_offset, streamFile);
|
||||
switch(codec_id) {
|
||||
case 0x49445350: /* "IDSP" */
|
||||
codec = IDSP;
|
||||
fake_ext = "idsp";
|
||||
break;
|
||||
case 0x4F505553: /* "OPUS" */
|
||||
codec = OPUS;
|
||||
fake_ext = "opus";
|
||||
break;
|
||||
default:
|
||||
VGM_LOG("NUS3AUDIO: unknown codec %x\n", codec_id);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
temp_streamFile = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, fake_ext);
|
||||
if (!temp_streamFile) goto fail;
|
||||
|
||||
/* init the VGMSTREAM */
|
||||
switch(codec) {
|
||||
case IDSP:
|
||||
vgmstream = init_vgmstream_idsp_nus3(temp_streamFile);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
case OPUS:
|
||||
vgmstream = init_vgmstream_opus_nus3(temp_streamFile);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
if (name_offset) /* null-terminated */
|
||||
read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,streamFile);
|
||||
|
||||
close_streamfile(temp_streamFile);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
typedef enum { IDSP, OPUS, } nus3audio_codec;
|
||||
|
||||
/* .nus3audio - Namco's newest newest audio container [Super Smash Bros. Ultimate (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_nus3audio(STREAMFILE *streamFile) {
|
||||
VGMSTREAM *vgmstream = NULL;
|
||||
STREAMFILE *temp_streamFile = NULL;
|
||||
off_t subfile_offset = 0, name_offset = 0;
|
||||
size_t subfile_size = 0;
|
||||
nus3audio_codec codec;
|
||||
const char* fake_ext = NULL;
|
||||
int total_subsongs, target_subsong = streamFile->stream_index;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "nus3audio"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,streamFile) != 0x4E555333) /* "NUS3" */
|
||||
goto fail;
|
||||
if (read_32bitLE(0x04,streamFile) + 0x08 != get_streamfile_size(streamFile))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x08,streamFile) != 0x41554449) /* "AUDI" */
|
||||
goto fail;
|
||||
|
||||
|
||||
/* parse existing chunks */
|
||||
{
|
||||
off_t offset = 0x0c;
|
||||
size_t file_size = get_streamfile_size(streamFile);
|
||||
uint32_t codec_id = 0;
|
||||
|
||||
total_subsongs = 0;
|
||||
|
||||
while (offset < file_size) {
|
||||
uint32_t chunk_id = (uint32_t)read_32bitBE(offset+0x00, streamFile);
|
||||
size_t chunk_size = (size_t)read_32bitLE(offset+0x04, streamFile);
|
||||
|
||||
switch(chunk_id) {
|
||||
case 0x494E4458: /* "INDX": audio index */
|
||||
total_subsongs = read_32bitLE(offset+0x08 + 0x00,streamFile);
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
||||
break;
|
||||
|
||||
case 0x4E4D4F46: /* "NMOF": name offsets (absolute, inside TNNM) */
|
||||
name_offset = read_32bitLE(offset+0x08 + 0x04*(target_subsong-1),streamFile);
|
||||
break;
|
||||
|
||||
case 0x41444F46: /* "ADOF": audio offsets (absolute, inside PACK) */
|
||||
subfile_offset = read_32bitLE(offset+0x08 + 0x08*(target_subsong-1) + 0x00,streamFile);
|
||||
subfile_size = read_32bitLE(offset+0x08 + 0x08*(target_subsong-1) + 0x04,streamFile);
|
||||
break;
|
||||
|
||||
case 0x544E4944: /* "TNID": tone ids? */
|
||||
case 0x544E4E4D: /* "TNNM": tone names */
|
||||
case 0x4A554E4B: /* "JUNK": padding */
|
||||
case 0x5041434B: /* "PACK": main data */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
offset += 0x08 + chunk_size;
|
||||
}
|
||||
|
||||
if (total_subsongs == 0 || subfile_offset == 0 || subfile_size == 0) {
|
||||
VGM_LOG("NUS3AUDIO: subfile not found\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
codec_id = read_32bitBE(subfile_offset, streamFile);
|
||||
switch(codec_id) {
|
||||
case 0x49445350: /* "IDSP" */
|
||||
codec = IDSP;
|
||||
fake_ext = "idsp";
|
||||
break;
|
||||
case 0x4F505553: /* "OPUS" */
|
||||
codec = OPUS;
|
||||
fake_ext = "opus";
|
||||
break;
|
||||
default:
|
||||
VGM_LOG("NUS3AUDIO: unknown codec %x\n", codec_id);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
temp_streamFile = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, fake_ext);
|
||||
if (!temp_streamFile) goto fail;
|
||||
|
||||
/* init the VGMSTREAM */
|
||||
switch(codec) {
|
||||
case IDSP:
|
||||
vgmstream = init_vgmstream_idsp_namco(temp_streamFile);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
case OPUS:
|
||||
vgmstream = init_vgmstream_opus_nus3(temp_streamFile);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
if (name_offset) /* null-terminated */
|
||||
read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,streamFile);
|
||||
|
||||
close_streamfile(temp_streamFile);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,183 +1,269 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
typedef enum { /*XMA_RAW, ATRAC3,*/ IDSP, ATRAC9, OPUS, BNSF, /*PCM, XMA_RIFF*/ } nus3bank_codec;
|
||||
|
||||
/* .nus3bank - Namco's newest audio container [Super Smash Bros (Wii U), idolmaster (PS4))] */
|
||||
VGMSTREAM * init_vgmstream_nus3bank(STREAMFILE *streamFile) {
|
||||
VGMSTREAM *vgmstream = NULL;
|
||||
STREAMFILE *temp_streamFile = NULL;
|
||||
off_t tone_offset = 0, pack_offset = 0, name_offset = 0, subfile_offset = 0;
|
||||
size_t name_size = 0, subfile_size = 0;
|
||||
nus3bank_codec codec;
|
||||
const char* fake_ext;
|
||||
int total_subsongs, target_subsong = streamFile->stream_index;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "nus3bank"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,streamFile) != 0x4E555333) /* "NUS3" */
|
||||
goto fail;
|
||||
if (read_32bitBE(0x08,streamFile) != 0x42414E4B) /* "BANK" */
|
||||
goto fail;
|
||||
if (read_32bitBE(0x0c,streamFile) != 0x544F4320) /* "TOC\0" */
|
||||
goto fail;
|
||||
|
||||
/* parse TOC with all existing chunks and sizes (offsets must be derived) */
|
||||
{
|
||||
int i;
|
||||
off_t offset = 0x14 + read_32bitLE(0x10, streamFile); /* TOC size */
|
||||
size_t chunk_count = read_32bitLE(0x14, streamFile); /* rarely not 7 (ex. SMB U's snd_bgm_CRS12_Simple_Result_Final) */
|
||||
|
||||
for (i = 0; i < chunk_count; i++) {
|
||||
uint32_t chunk_id = (uint32_t)read_32bitBE(0x18+(i*0x08)+0x00, streamFile);
|
||||
size_t chunk_size = (size_t)read_32bitLE(0x18+(i*0x08)+0x04, streamFile);
|
||||
|
||||
switch(chunk_id) {
|
||||
case 0x544F4E45: /* "TONE": stream info */
|
||||
tone_offset = 0x08 + offset;
|
||||
break;
|
||||
case 0x5041434B: /* "PACK": audio streams */
|
||||
pack_offset = 0x08 + offset;
|
||||
break;
|
||||
|
||||
case 0x50524F50: /* "PROP": project info */
|
||||
case 0x42494E46: /* "BINF": bank info (filename) */
|
||||
case 0x47525020: /* "GRP ": ? */
|
||||
case 0x44544F4E: /* "DTON": ? */
|
||||
case 0x4D41524B: /* "MARK": ? */
|
||||
case 0x4A554E4B: /* "JUNK": padding */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
offset += 0x08 + chunk_size;
|
||||
}
|
||||
|
||||
if (tone_offset == 0 || pack_offset == 0) {
|
||||
VGM_LOG("NUS3BANK: chunks found\n");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* parse tones */
|
||||
{
|
||||
int i;
|
||||
uint32_t codec_id = 0;
|
||||
size_t entries = read_32bitLE(tone_offset+0x00, streamFile);
|
||||
|
||||
/* get actual number of subsongs */
|
||||
total_subsongs = 0;
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
|
||||
for (i = 0; i < entries; i++) {
|
||||
off_t header_offset, header_suboffset, stream_name_offset, stream_offset;
|
||||
size_t stream_name_size, stream_size;
|
||||
off_t tone_header_offset = read_32bitLE(tone_offset+0x04+(i*0x08)+0x00, streamFile);
|
||||
size_t tone_header_size = read_32bitLE(tone_offset+0x04+(i*0x08)+0x04, streamFile);
|
||||
|
||||
if (tone_header_size <= 0x0c) {
|
||||
continue; /* ignore non-sounds */
|
||||
}
|
||||
|
||||
header_offset = tone_offset + tone_header_offset;
|
||||
|
||||
stream_name_size = read_8bit(header_offset+0x0c, streamFile); /* includes null */
|
||||
stream_name_offset = header_offset+0x0d;
|
||||
header_suboffset = 0x0c + 0x01 + stream_name_size;
|
||||
if (header_suboffset % 0x04) /* padded */
|
||||
header_suboffset += (0x04 - (header_suboffset % 0x04));
|
||||
if (read_32bitLE(header_offset+header_suboffset+0x04, streamFile) != 0x08) {
|
||||
continue; /* ignore non-sounds, too */
|
||||
}
|
||||
|
||||
stream_offset = read_32bitLE(header_offset+header_suboffset+0x08, streamFile) + pack_offset;
|
||||
stream_size = read_32bitLE(header_offset+header_suboffset+0x0c, streamFile);
|
||||
if (stream_size == 0) {
|
||||
continue; /* happens in some sfx packs */
|
||||
}
|
||||
|
||||
total_subsongs++;
|
||||
if (total_subsongs == target_subsong) {
|
||||
//;VGM_LOG("NUS3BANK: subsong header offset %lx\n", header_offset);
|
||||
subfile_offset = stream_offset;
|
||||
subfile_size = stream_size;
|
||||
name_size = stream_name_size;
|
||||
name_offset = stream_name_offset;
|
||||
//todo improve, codec may be related to header values at 0x00/0x06/0x08
|
||||
codec_id = read_32bitBE(subfile_offset, streamFile);
|
||||
}
|
||||
/* continue counting subsongs */
|
||||
}
|
||||
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
||||
if (subfile_offset == 0 || codec_id == 0) {
|
||||
VGM_LOG("NUS3BANK: subsong not found\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
switch(codec_id) {
|
||||
case 0x49445350: /* "IDSP" [Super Smash Bros. for 3DS (3DS)] */
|
||||
codec = IDSP;
|
||||
fake_ext = "idsp";
|
||||
break;
|
||||
case 0x52494646: /* "RIFF" [idolm@ster: Platinum Stars (PS4)] */
|
||||
//todo this can be standard PCM RIFF too, ex. Mario Kart Arcade GP DX (PC) (works but should have better detection)
|
||||
codec = ATRAC9;
|
||||
fake_ext = "at9";
|
||||
break;
|
||||
case 0x4F505553: /* "OPUS" [Taiko no Tatsujin (Switch)] */
|
||||
codec = OPUS;
|
||||
fake_ext = "opus";
|
||||
break;
|
||||
case 0x424E5346: /* "BNSF" [Naruto Shippuden Ultimate Ninja Storm 4 (PC)] */
|
||||
codec = BNSF;
|
||||
fake_ext = "bnsf";
|
||||
break;
|
||||
default:
|
||||
VGM_LOG("NUS3BANK: unknown codec %x\n", codec_id);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
//;VGM_LOG("NUS3BANK: subfile=%lx, size=%x\n", subfile_offset, subfile_size);
|
||||
|
||||
temp_streamFile = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, fake_ext);
|
||||
if (!temp_streamFile) goto fail;
|
||||
|
||||
/* init the VGMSTREAM */
|
||||
switch(codec) {
|
||||
case IDSP:
|
||||
vgmstream = init_vgmstream_idsp_nus3(temp_streamFile);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
case OPUS:
|
||||
vgmstream = init_vgmstream_opus_nus3(temp_streamFile);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
case ATRAC9:
|
||||
vgmstream = init_vgmstream_riff(temp_streamFile);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
case BNSF:
|
||||
vgmstream = init_vgmstream_bnsf(temp_streamFile);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
if (name_offset)
|
||||
read_string(vgmstream->stream_name,name_size, name_offset,streamFile);
|
||||
|
||||
|
||||
close_streamfile(temp_streamFile);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
#include "nus3bank_streamfile.h"
|
||||
|
||||
typedef enum { IDSP, IVAG, BNSF, RIFF, OPUS, RIFF_ENC, } nus3bank_codec;
|
||||
|
||||
/* .nus3bank - Namco's newest audio container [Super Smash Bros (Wii U), idolmaster (PS4))] */
|
||||
VGMSTREAM * init_vgmstream_nus3bank(STREAMFILE *streamFile) {
|
||||
VGMSTREAM *vgmstream = NULL;
|
||||
STREAMFILE *temp_sf = NULL;
|
||||
off_t tone_offset = 0, pack_offset = 0, name_offset = 0, subfile_offset = 0;
|
||||
size_t name_size = 0, subfile_size = 0;
|
||||
nus3bank_codec codec;
|
||||
const char* fake_ext;
|
||||
int total_subsongs, target_subsong = streamFile->stream_index;
|
||||
|
||||
/* checks */
|
||||
/* .nub2: early [THE iDOLM@STER 2 (PS3/X360)]
|
||||
* .nus3bank: standard */
|
||||
if (!check_extensions(streamFile, "nub2,nus3bank"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,streamFile) != 0x4E555333) /* "NUS3" */
|
||||
goto fail;
|
||||
if (read_32bitBE(0x08,streamFile) != 0x42414E4B) /* "BANK" */
|
||||
goto fail;
|
||||
if (read_32bitBE(0x0c,streamFile) != 0x544F4320) /* "TOC\0" */
|
||||
goto fail;
|
||||
|
||||
/* header is always LE, while contained files may use another endianness */
|
||||
|
||||
/* parse TOC with all existing chunks and sizes (offsets must be derived) */
|
||||
{
|
||||
int i;
|
||||
off_t offset = 0x14 + read_32bitLE(0x10, streamFile); /* TOC size */
|
||||
size_t chunk_count = read_32bitLE(0x14, streamFile); /* rarely not 7 (ex. SMB U's snd_bgm_CRS12_Simple_Result_Final) */
|
||||
|
||||
for (i = 0; i < chunk_count; i++) {
|
||||
uint32_t chunk_id = (uint32_t)read_32bitBE(0x18+(i*0x08)+0x00, streamFile);
|
||||
size_t chunk_size = (size_t)read_32bitLE(0x18+(i*0x08)+0x04, streamFile);
|
||||
|
||||
switch(chunk_id) {
|
||||
case 0x544F4E45: /* "TONE": stream info */
|
||||
tone_offset = 0x08 + offset;
|
||||
break;
|
||||
case 0x5041434B: /* "PACK": audio streams */
|
||||
pack_offset = 0x08 + offset;
|
||||
break;
|
||||
|
||||
case 0x50524F50: /* "PROP": project info */
|
||||
case 0x42494E46: /* "BINF": bank info (filename) */
|
||||
case 0x47525020: /* "GRP ": cues/events with names? */
|
||||
case 0x44544F4E: /* "DTON": related to GRP? */
|
||||
case 0x4D41524B: /* "MARK": ? */
|
||||
case 0x4A554E4B: /* "JUNK": padding */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
offset += 0x08 + chunk_size;
|
||||
}
|
||||
|
||||
if (tone_offset == 0 || pack_offset == 0) {
|
||||
VGM_LOG("NUS3BANK: chunks found\n");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* parse tones */
|
||||
{
|
||||
int i;
|
||||
uint32_t codec_id = 0;
|
||||
size_t entries = read_32bitLE(tone_offset+0x00, streamFile);
|
||||
|
||||
/* get actual number of subsongs */
|
||||
total_subsongs = 0;
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
|
||||
for (i = 0; i < entries; i++) {
|
||||
off_t offset, tone_header_offset, stream_name_offset, stream_offset;
|
||||
size_t tone_header_size, stream_name_size, stream_size;
|
||||
uint8_t flags2;
|
||||
|
||||
tone_header_offset = read_32bitLE(tone_offset+0x04+(i*0x08)+0x00, streamFile);
|
||||
tone_header_size = read_32bitLE(tone_offset+0x04+(i*0x08)+0x04, streamFile);
|
||||
|
||||
offset = tone_offset + tone_header_offset;
|
||||
//;VGM_LOG("NUS3BANK: tone at %lx, size %x\n", tone_offset + tone_header_offset, tone_header_size);
|
||||
|
||||
if (tone_header_size <= 0x0c) {
|
||||
//VGM_LOG("NUS3BANK: bad tone at %lx, size %x\n", tone_offset + tone_header_offset, tone_header_size);
|
||||
continue; /* ignore non-sounds */
|
||||
}
|
||||
|
||||
/* 0x00: type? normally 0x00 and rarely 0x09 */
|
||||
/* 0x04: usually -1, found when tone is not a stream (most flags are off too) */
|
||||
/* 0x06: flags1 */
|
||||
flags2 = read_8bit(offset + 0x07, streamFile);
|
||||
offset += 0x08;
|
||||
|
||||
/* flags3-6 (early .nub2 and some odd non-stream don't have them) */
|
||||
if (flags2 & 0x80) {
|
||||
offset += 0x04;
|
||||
}
|
||||
|
||||
stream_name_size = read_8bit(offset + 0x00, streamFile); /* includes null */
|
||||
stream_name_offset = offset + 0x01;
|
||||
offset += align_size_to_block(0x01 + stream_name_size, 0x04); /* padded if needed */
|
||||
|
||||
/* 0x00: subtype? should be 0 */
|
||||
if (read_32bitLE(offset + 0x04, streamFile) != 0x08) { /* flag? */
|
||||
//;VGM_LOG("NUS3BANK: bad tone type at %lx, size %x\n", tone_offset + tone_header_offset, tone_header_size);
|
||||
continue;
|
||||
}
|
||||
|
||||
stream_offset = read_32bitLE(offset + 0x08, streamFile) + pack_offset;
|
||||
stream_size = read_32bitLE(offset + 0x0c, streamFile);
|
||||
//;VGM_LOG("NUS3BANK: so=%lx, ss=%x\n", stream_offset, stream_size);
|
||||
|
||||
/* Beyond are a bunch of sub-chunks of unknown size with floats and stuff, that seemingly
|
||||
* appear depending on flags1-6. One is a small stream header, which contains basic
|
||||
* sample rate/channels/loops/etc, but it's actually optional and maybe controlled by
|
||||
* flags 3-6 (ex. not found in .nub2) */
|
||||
|
||||
/* happens in some sfx packs (ex. Taiko no Tatsujin Switch's se_minigame) */
|
||||
if (stream_size == 0) {
|
||||
//;VGM_LOG("NUS3BANK: bad tone stream size at %lx, size %x\n", tone_offset + tone_header_offset, tone_header_size);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
total_subsongs++;
|
||||
if (total_subsongs == target_subsong) {
|
||||
//;VGM_LOG("NUS3BANK: subsong header offset %lx\n", offset);
|
||||
subfile_offset = stream_offset;
|
||||
subfile_size = stream_size;
|
||||
name_size = stream_name_size;
|
||||
name_offset = stream_name_offset;
|
||||
}
|
||||
/* continue counting subsongs */
|
||||
}
|
||||
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
||||
if (subfile_offset == 0) {
|
||||
VGM_LOG("NUS3BANK: subsong not found\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
//todo improve, codec may be in one of the tone sub-chunks (or other chunk? one bank seems to use one codec)
|
||||
codec_id = read_32bitBE(subfile_offset, streamFile);
|
||||
switch(codec_id) {
|
||||
case 0x49445350: /* "IDSP" [Super Smash Bros. for 3DS (3DS)] */
|
||||
codec = IDSP;
|
||||
fake_ext = "idsp";
|
||||
break;
|
||||
|
||||
case 0x52494646: /* "RIFF" [THE iDOLM@STER 2 (PS3), Mario Kart Arcade GP DX (PC), idolm@ster: Platinum Stars (PS4)] */
|
||||
codec = RIFF;
|
||||
fake_ext = "wav"; //TODO: works but should have better detection
|
||||
break;
|
||||
|
||||
case 0x4F505553: /* "OPUS" [Taiko no Tatsujin (Switch)] */
|
||||
codec = OPUS;
|
||||
fake_ext = "opus";
|
||||
break;
|
||||
|
||||
case 0x424E5346: /* "BNSF" [Naruto Shippuden Ultimate Ninja Storm 4 (PC)] */
|
||||
codec = BNSF;
|
||||
fake_ext = "bnsf";
|
||||
break;
|
||||
|
||||
case 0x49564147: /* "IVAG" [THE iDOLM@STER 2 (PS3), THE iDOLM@STER: Gravure For You! (PS3)] */
|
||||
codec = IVAG;
|
||||
fake_ext = "ivag";
|
||||
break;
|
||||
|
||||
case 0x552AAF17: /* "RIFF" with encrypted header (not data) [THE iDOLM@STER 2 (X360)] */
|
||||
codec = RIFF_ENC;
|
||||
fake_ext = "xma";
|
||||
break;
|
||||
|
||||
default:
|
||||
VGM_LOG("NUS3BANK: unknown codec %x\n", codec_id);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
//;VGM_LOG("NUS3BANK: subfile=%lx, size=%x\n", subfile_offset, subfile_size);
|
||||
|
||||
temp_sf = setup_subfile_streamfile(streamFile, subfile_offset, subfile_size, fake_ext);
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
/* init the VGMSTREAM */
|
||||
switch(codec) {
|
||||
case IDSP:
|
||||
vgmstream = init_vgmstream_idsp_namco(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
|
||||
case OPUS:
|
||||
vgmstream = init_vgmstream_opus_nus3(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
|
||||
case RIFF:
|
||||
vgmstream = init_vgmstream_riff(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
|
||||
case BNSF:
|
||||
vgmstream = init_vgmstream_bnsf(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
|
||||
case IVAG:
|
||||
vgmstream = init_vgmstream_ivag(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
|
||||
case RIFF_ENC:
|
||||
vgmstream = init_vgmstream_nus3bank_encrypted(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
if (name_offset)
|
||||
read_string(vgmstream->stream_name,name_size, name_offset,streamFile);
|
||||
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_sf);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* encrypted RIFF from the above, in case kids try to extract and play single files */
|
||||
VGMSTREAM* init_vgmstream_nus3bank_encrypted(STREAMFILE *sf) {
|
||||
VGMSTREAM *vgmstream = NULL;
|
||||
STREAMFILE *temp_sf = NULL;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "nus3bank,xma"))
|
||||
goto fail;
|
||||
if (read_u32be(0x00, sf) != 0x552AAF17) /* "RIFF" encrypted */
|
||||
goto fail;
|
||||
|
||||
temp_sf = setup_nus3bank_streamfile(sf, 0x00);
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_xma(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_sf);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
#ifndef _NUS3BANK_STREAMFILE_H_
|
||||
#define _NUS3BANK_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
static uint32_t swap_endian32(uint32_t v) {
|
||||
return ((v & 0xff000000) >> 24u) |
|
||||
((v & 0x00ff0000) >> 8u) |
|
||||
((v & 0x0000ff00) << 8u) |
|
||||
((v & 0x000000ff) << 24u);
|
||||
}
|
||||
|
||||
|
||||
#define KEY_MAX_SIZE 0x1000
|
||||
|
||||
typedef struct {
|
||||
uint8_t key[KEY_MAX_SIZE];
|
||||
int key_len;
|
||||
} io_data_t;
|
||||
|
||||
static size_t io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, io_data_t *data) {
|
||||
int i;
|
||||
size_t bytes = read_streamfile(dest, offset, length, sf);
|
||||
|
||||
/* decrypt data (xor) */
|
||||
if (offset < data->key_len) {
|
||||
for (i = 0; i < bytes; i++) {
|
||||
if (offset + i < data->key_len)
|
||||
dest[i] ^= data->key[offset + i];
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/* decrypts RIFF streams in NUS3BANK */
|
||||
static STREAMFILE* setup_nus3bank_streamfile(STREAMFILE *sf, off_t start) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
io_data_t io_data = {0};
|
||||
|
||||
/* setup key */
|
||||
{
|
||||
uint32_t base_key, chunk_key;
|
||||
uint8_t buf[KEY_MAX_SIZE];
|
||||
uint32_t chunk_type, chunk_size;
|
||||
int pos, data_pos, bytes;
|
||||
|
||||
/* Header is XORed with a base key and a derived chunk type/size key, while chunk data is XORed with
|
||||
* unencrypted data itself, so we need to find where "data" starts then do another pass to properly set key.
|
||||
* Original code handles RIFF's "data" and also BNSF's "sdat" too, encrypted BNSF aren't known though. */
|
||||
|
||||
bytes = read_streamfile(buf, start, sizeof(buf), sf);
|
||||
if (bytes < 0x800) goto fail; /* files of 1 XMA block do exist, but not less */
|
||||
|
||||
base_key = 0x0763E951;
|
||||
chunk_type = get_u32be(buf + 0x00) ^ base_key;
|
||||
chunk_size = get_u32be(buf + 0x04) ^ base_key;
|
||||
if (chunk_type != 0x52494646) /* "RIFF" */
|
||||
goto fail;
|
||||
|
||||
chunk_key = base_key ^ (((chunk_size >> 16u) & 0x0000FFFF) | ((chunk_size << 16u) & 0xFFFF0000)); /* ROTr 16 size */
|
||||
|
||||
/* find "data" */
|
||||
pos = 0x0c;
|
||||
while(pos < sizeof(buf)) {
|
||||
chunk_type = get_u32be(buf + pos + 0x00) ^ chunk_key;
|
||||
chunk_size = get_u32be(buf + pos + 0x04) ^ chunk_key;
|
||||
chunk_size = swap_endian32(chunk_size);
|
||||
pos += 0x08;
|
||||
|
||||
if (chunk_type == 0x64617461) { /* "data" */
|
||||
data_pos = pos;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pos + chunk_size > sizeof(buf) - 0x08) {
|
||||
VGM_LOG("NUS3 SF: header too big\n");
|
||||
goto fail; /* max around 0x400 */
|
||||
}
|
||||
|
||||
pos += chunk_size;
|
||||
}
|
||||
|
||||
|
||||
/* setup key */
|
||||
put_u32be(io_data.key + 0x00, base_key);
|
||||
put_u32be(io_data.key + 0x04, base_key);
|
||||
put_u32be(io_data.key + 0x08, chunk_key);
|
||||
pos = 0x0c; /* after WAVE */
|
||||
|
||||
while (pos < data_pos) {
|
||||
chunk_type = get_u32be(buf + pos + 0x00) ^ chunk_key;
|
||||
chunk_size = get_u32be(buf + pos + 0x04) ^ chunk_key;
|
||||
chunk_size = swap_endian32(chunk_size);
|
||||
|
||||
put_u32be(io_data.key + pos + 0x00, chunk_key);
|
||||
put_u32be(io_data.key + pos + 0x04, chunk_key);
|
||||
pos += 0x08;
|
||||
|
||||
if (pos >= data_pos)
|
||||
break;
|
||||
|
||||
/* buf must contain data of at least chunk_size */
|
||||
if (data_pos + chunk_size >= sizeof(buf)) {
|
||||
VGM_LOG("NUS3 SF: chunk too big\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
memcpy(io_data.key + pos, buf + data_pos, chunk_size);
|
||||
|
||||
pos += chunk_size;
|
||||
}
|
||||
|
||||
io_data.key_len = data_pos;
|
||||
}
|
||||
|
||||
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_streamfile(new_sf, &io_data, sizeof(io_data_t), io_read, NULL);
|
||||
return new_sf;
|
||||
fail:
|
||||
close_streamfile(sf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* _NUS3BANK_STREAMFILE_H_ */
|
|
@ -1,84 +1,84 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* 2PFS - from Konami Games [Mahoromatic: Moetto - KiraKira Maid-San (PS2), GANTZ The Game (PS2)] */
|
||||
VGMSTREAM * init_vgmstream_ps2_2pfs(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channel_count, version, interleave;
|
||||
int loop_start_block, loop_end_block; /* block number */
|
||||
int loop_start_adjust, loop_end_adjust; /* loops start/end a few samples into the start/end block */
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .sap: standard
|
||||
* .2psf: header id? (Mahoromatic) */
|
||||
if (!check_extensions(streamFile, "sap,2psf"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00,streamFile) != 0x32504653) /* "2PFS" */
|
||||
goto fail;
|
||||
|
||||
version = read_16bitLE(0x04,streamFile);
|
||||
if (version != 0x01 && version != 0x02) /* v1: Mahoromatic, v2: Gantz */
|
||||
goto fail;
|
||||
|
||||
|
||||
channel_count = read_8bit(0x40,streamFile);
|
||||
loop_flag = read_8bit(0x41,streamFile);
|
||||
start_offset = 0x800;
|
||||
interleave = 0x1000;
|
||||
|
||||
/* other header values
|
||||
* 0x06: unknown, v1=0x0004 v2=0x0001
|
||||
* 0x08: unique file id
|
||||
* 0x0c: base header size (v1=0x50, v2=0x60) + datasize (without the 0x800 full header size)
|
||||
* 0x10-0x30: unknown (v1 differs from v2)
|
||||
* 0x38-0x40: unknown (v1 same as v2)
|
||||
* 0x4c: unknown, some kind of total samples? (v2 only)
|
||||
*/
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_PS2_2PFS;
|
||||
vgmstream->num_samples = read_32bitLE(0x34,streamFile) * 28 / 16 / channel_count;
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = interleave;
|
||||
|
||||
if (version == 0x01) {
|
||||
vgmstream->sample_rate = read_32bitLE(0x44,streamFile);
|
||||
loop_start_adjust = read_16bitLE(0x42,streamFile);
|
||||
loop_start_block = read_32bitLE(0x48,streamFile);
|
||||
loop_end_block = read_32bitLE(0x4c,streamFile);
|
||||
}
|
||||
else {
|
||||
vgmstream->sample_rate = read_32bitLE(0x48,streamFile);
|
||||
loop_start_adjust = read_32bitLE(0x44,streamFile);
|
||||
loop_start_block = read_32bitLE(0x50,streamFile);
|
||||
loop_end_block = read_32bitLE(0x54,streamFile);
|
||||
}
|
||||
loop_end_adjust = interleave; /* loops end after all samples in the end_block AFAIK */
|
||||
|
||||
if (loop_flag) {
|
||||
/* block to offset > offset to sample + adjust (number of frames into the block) */
|
||||
vgmstream->loop_start_sample =
|
||||
ps_bytes_to_samples(loop_start_block * channel_count * interleave, channel_count)
|
||||
+ ps_bytes_to_samples(loop_start_adjust * channel_count, channel_count);
|
||||
vgmstream->loop_end_sample =
|
||||
ps_bytes_to_samples(loop_end_block * channel_count * interleave, channel_count)
|
||||
+ ps_bytes_to_samples(loop_end_adjust * channel_count, channel_count);
|
||||
}
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
if (vgmstream) close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* 2PFS - from Konami Games [Mahoromatic: Moetto - KiraKira Maid-San (PS2), GANTZ The Game (PS2)] */
|
||||
VGMSTREAM * init_vgmstream_ps2_2pfs(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channel_count, version, interleave;
|
||||
int loop_start_block, loop_end_block; /* block number */
|
||||
int loop_start_adjust, loop_end_adjust; /* loops start/end a few samples into the start/end block */
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .sap: standard
|
||||
* .2psf: header id? (Mahoromatic) */
|
||||
if (!check_extensions(streamFile, "sap,2psf"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00,streamFile) != 0x32504653) /* "2PFS" */
|
||||
goto fail;
|
||||
|
||||
version = read_16bitLE(0x04,streamFile);
|
||||
if (version != 0x01 && version != 0x02) /* v1: Mahoromatic, v2: Gantz */
|
||||
goto fail;
|
||||
|
||||
|
||||
channel_count = read_8bit(0x40,streamFile);
|
||||
loop_flag = read_8bit(0x41,streamFile);
|
||||
start_offset = 0x800;
|
||||
interleave = 0x1000;
|
||||
|
||||
/* other header values
|
||||
* 0x06: unknown, v1=0x0004 v2=0x0001
|
||||
* 0x08: unique file id
|
||||
* 0x0c: base header size (v1=0x50, v2=0x60) + datasize (without the 0x800 full header size)
|
||||
* 0x10-0x30: unknown (v1 differs from v2)
|
||||
* 0x38-0x40: unknown (v1 same as v2)
|
||||
* 0x4c: unknown, some kind of total samples? (v2 only)
|
||||
*/
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_PS2_2PFS;
|
||||
vgmstream->num_samples = read_32bitLE(0x34,streamFile) * 28 / 16 / channel_count;
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = interleave;
|
||||
|
||||
if (version == 0x01) {
|
||||
vgmstream->sample_rate = read_32bitLE(0x44,streamFile);
|
||||
loop_start_adjust = read_16bitLE(0x42,streamFile);
|
||||
loop_start_block = read_32bitLE(0x48,streamFile);
|
||||
loop_end_block = read_32bitLE(0x4c,streamFile);
|
||||
}
|
||||
else {
|
||||
vgmstream->sample_rate = read_32bitLE(0x48,streamFile);
|
||||
loop_start_adjust = read_32bitLE(0x44,streamFile);
|
||||
loop_start_block = read_32bitLE(0x50,streamFile);
|
||||
loop_end_block = read_32bitLE(0x54,streamFile);
|
||||
}
|
||||
loop_end_adjust = interleave; /* loops end after all samples in the end_block AFAIK */
|
||||
|
||||
if (loop_flag) {
|
||||
/* block to offset > offset to sample + adjust (number of frames into the block) */
|
||||
vgmstream->loop_start_sample =
|
||||
ps_bytes_to_samples(loop_start_block * channel_count * interleave, channel_count)
|
||||
+ ps_bytes_to_samples(loop_start_adjust * channel_count, channel_count);
|
||||
vgmstream->loop_end_sample =
|
||||
ps_bytes_to_samples(loop_end_block * channel_count * interleave, channel_count)
|
||||
+ ps_bytes_to_samples(loop_end_adjust * channel_count, channel_count);
|
||||
}
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
if (vgmstream) close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
#include "meta.h"
|
||||
#include "../util.h"
|
||||
|
||||
/* IVAG
|
||||
- The Idolm@ster: Gravure For You! Vol. 3 (PS3)
|
||||
|
||||
Appears to be two VAGp streams interleaved.
|
||||
*/
|
||||
VGMSTREAM * init_vgmstream_ps3_ivag(STREAMFILE *streamFile)
|
||||
{
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
char filename[PATH_LIMIT];
|
||||
|
||||
off_t start_offset;
|
||||
|
||||
int loop_flag = 0;
|
||||
int channel_count;
|
||||
|
||||
/* check extension, case insensitive */
|
||||
streamFile->get_name(streamFile,filename,sizeof(filename));
|
||||
if (strcasecmp("ivag",filename_extension(filename))) goto fail;
|
||||
|
||||
/* check header */
|
||||
if (read_32bitBE(0x00,streamFile) != 0x49564147) // "IVAG"
|
||||
goto fail;
|
||||
|
||||
// channel count
|
||||
channel_count = read_32bitBE(0x08, streamFile);
|
||||
|
||||
// header size
|
||||
start_offset = 0x40 + (0x40 * channel_count);
|
||||
|
||||
// loop flag
|
||||
if ((read_32bitBE(0x14, streamFile) != 0 ||
|
||||
(read_32bitBE(0x18, streamFile) != 0)))
|
||||
{
|
||||
loop_flag = 1;
|
||||
}
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
/* fill in the vital statistics */
|
||||
vgmstream->channels = channel_count;
|
||||
vgmstream->sample_rate = read_32bitBE(0x0C,streamFile);
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->num_samples = read_32bitBE(0x10,streamFile);
|
||||
|
||||
if (loop_flag)
|
||||
{
|
||||
vgmstream->loop_start_sample = read_32bitBE(0x14,streamFile);
|
||||
vgmstream->loop_end_sample = read_32bitBE(0x18,streamFile);
|
||||
}
|
||||
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = read_32bitBE(0x1C,streamFile);
|
||||
vgmstream->meta_type = meta_PS3_IVAG;
|
||||
|
||||
/* open the file for reading */
|
||||
{
|
||||
int i;
|
||||
STREAMFILE * file;
|
||||
file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
if (!file) goto fail;
|
||||
|
||||
for (i=0;i<channel_count;i++)
|
||||
{
|
||||
vgmstream->ch[i].streamfile = file;
|
||||
|
||||
vgmstream->ch[i].channel_start_offset=
|
||||
vgmstream->ch[i].offset=start_offset + (vgmstream->interleave_block_size * i);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return vgmstream;
|
||||
|
||||
/* clean up anything we may have opened */
|
||||
fail:
|
||||
if (vgmstream) close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
|
@ -1,190 +1,190 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* SGXD - Sony/SCEI's format (SGB+SGH / SGD / SGX) */
|
||||
VGMSTREAM * init_vgmstream_sgxd(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
STREAMFILE * streamHeader = NULL;
|
||||
off_t start_offset, data_offset, chunk_offset, name_offset = 0;
|
||||
size_t stream_size;
|
||||
|
||||
int is_sgx, is_sgb = 0;
|
||||
int loop_flag, channels, type;
|
||||
int sample_rate, num_samples, loop_start_sample, loop_end_sample;
|
||||
int total_subsongs, target_subsong = streamFile->stream_index;
|
||||
|
||||
|
||||
/* check extension, case insensitive */
|
||||
/* .sgx: header+data (Genji), .sgd: header+data, .sgh/sgd: header/data */
|
||||
if (!check_extensions(streamFile,"sgx,sgd,sgb"))
|
||||
goto fail;
|
||||
is_sgx = check_extensions(streamFile,"sgx");
|
||||
is_sgb = check_extensions(streamFile,"sgb");
|
||||
|
||||
/* SGB+SGH: use SGH as header; otherwise use the current file as header */
|
||||
if (is_sgb) {
|
||||
streamHeader = open_streamfile_by_ext(streamFile, "sgh");
|
||||
if (!streamHeader) goto fail;
|
||||
} else {
|
||||
streamHeader = streamFile;
|
||||
}
|
||||
|
||||
|
||||
/* SGXD base (size 0x10) */
|
||||
if (read_32bitBE(0x00,streamHeader) != 0x53475844) /* "SGXD" */
|
||||
goto fail;
|
||||
/* 0x04 SGX: full header_size; SGD/SGH: unknown header_size (counting from 0x0/0x8/0x10, varies) */
|
||||
/* 0x08 SGX: first chunk offset? (0x10); SGD/SGH: full header_size */
|
||||
/* 0x0c SGX/SGH: full data size with padding; SGD: full data size + 0x80000000 with padding */
|
||||
if (is_sgb) {
|
||||
data_offset = 0x00;
|
||||
} else if ( is_sgx ) {
|
||||
data_offset = read_32bitLE(0x04,streamHeader);
|
||||
} else {
|
||||
data_offset = read_32bitLE(0x08,streamHeader);
|
||||
}
|
||||
|
||||
|
||||
/* typical chunks: WAVE, RGND, NAME (strings for WAVE or RGND), SEQD (related to SFX), WSUR, WMKR, BUSS */
|
||||
/* WAVE chunk (size 0x10 + files * 0x38 + optional padding) */
|
||||
if (is_sgx) { /* position after chunk+size */
|
||||
if (read_32bitBE(0x10,streamHeader) != 0x57415645) goto fail; /* "WAVE" */
|
||||
chunk_offset = 0x18;
|
||||
} else {
|
||||
if (!find_chunk_le(streamHeader, 0x57415645,0x10,0, &chunk_offset,NULL)) goto fail; /* "WAVE" */
|
||||
}
|
||||
/* 0x04 SGX: unknown; SGD/SGH: chunk length, 0x08 null */
|
||||
|
||||
/* check multi-streams (usually only SE containers; Puppeteer) */
|
||||
total_subsongs = read_32bitLE(chunk_offset+0x04,streamHeader);
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
||||
|
||||
/* read stream header */
|
||||
{
|
||||
off_t stream_offset;
|
||||
chunk_offset += 0x08 + 0x38 * (target_subsong-1); /* position in target header*/
|
||||
|
||||
/* 0x00 ? (00/01/02) */
|
||||
if (!is_sgx) /* meaning unknown in .sgx; offset 0 = not a stream (a RGND sample) */
|
||||
name_offset = read_32bitLE(chunk_offset+0x04,streamHeader);
|
||||
type = read_8bit(chunk_offset+0x08,streamHeader);
|
||||
channels = read_8bit(chunk_offset+0x09,streamHeader);
|
||||
/* 0x0a null */
|
||||
sample_rate = read_32bitLE(chunk_offset+0x0c,streamHeader);
|
||||
|
||||
/* 0x10 info_type: meaning of the next value
|
||||
* (00=null, 30/40=data size without padding (ADPCM, ATRAC3plus), 80/A0=block size (AC3) */
|
||||
/* 0x14 info_value (see above) */
|
||||
/* 0x18 unknown (ex. 0x0008/0010/3307/CC02/etc)x2 */
|
||||
/* 0x1c null */
|
||||
|
||||
num_samples = read_32bitLE(chunk_offset+0x20,streamHeader);
|
||||
loop_start_sample = read_32bitLE(chunk_offset+0x24,streamHeader);
|
||||
loop_end_sample = read_32bitLE(chunk_offset+0x28,streamHeader);
|
||||
stream_size = read_32bitLE(chunk_offset+0x2c,streamHeader); /* stream size (without padding) / interleave (for type3) */
|
||||
|
||||
if (is_sgx) {
|
||||
stream_offset = 0x0;
|
||||
} else{
|
||||
stream_offset = read_32bitLE(chunk_offset+0x30,streamHeader);
|
||||
}
|
||||
/* 0x34 SGX: unknown; SGD/SGH: stream size (with padding) / interleave */
|
||||
|
||||
loop_flag = loop_start_sample!=0xffffffff && loop_end_sample!=0xffffffff;
|
||||
start_offset = data_offset + stream_offset;
|
||||
}
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = num_samples;
|
||||
vgmstream->loop_start_sample = loop_start_sample;
|
||||
vgmstream->loop_end_sample = loop_end_sample;
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
vgmstream->stream_size = stream_size;
|
||||
vgmstream->meta_type = meta_SGXD;
|
||||
if (name_offset)
|
||||
read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,streamHeader);
|
||||
|
||||
switch (type) {
|
||||
#ifdef VGM_USE_VORBIS
|
||||
case 0x02: /* Ogg Vorbis [Ni no Kuni: Wrath of the White Witch Remastered (PC)] (codec hijack?) */
|
||||
vgmstream->codec_data = init_ogg_vorbis(streamFile, start_offset, stream_size, NULL);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_OGG_VORBIS;
|
||||
vgmstream->layout_type = layout_none;
|
||||
break;
|
||||
#endif
|
||||
case 0x03: /* PS-ADPCM [Genji (PS3), Ape Escape Move (PS3)]*/
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
if (is_sgx || is_sgb) {
|
||||
vgmstream->interleave_block_size = 0x10;
|
||||
} else { /* this only seems to happen with SFX */
|
||||
vgmstream->interleave_block_size = stream_size;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case 0x04: { /* ATRAC3plus [Kurohyo 1/2 (PSP), BraveStory (PSP)] */
|
||||
vgmstream->codec_data = init_ffmpeg_atrac3_riff(streamFile, start_offset, NULL);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
/* SGXD's sample rate has priority over RIFF's sample rate (may not match) */
|
||||
/* loop/sample values are relative (without skip) vs RIFF (with skip), matching "smpl" otherwise */
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case 0x05: /* Short PS-ADPCM [Afrika (PS3)] */
|
||||
vgmstream->coding_type = coding_PSX_cfg;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x4;
|
||||
|
||||
break;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case 0x06: { /* AC3 [Tokyo Jungle (PS3), Afrika (PS3)] */
|
||||
ffmpeg_codec_data *ffmpeg_data;
|
||||
|
||||
ffmpeg_data = init_ffmpeg_offset(streamFile, start_offset, stream_size);
|
||||
if ( !ffmpeg_data ) goto fail;
|
||||
vgmstream->codec_data = ffmpeg_data;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
/* manually set skip_samples if FFmpeg didn't do it */
|
||||
if (ffmpeg_data->skipSamples <= 0) {
|
||||
/* PS3 AC3 consistently has 256 encoder delay samples, and there are ~1000-2000 samples after num_samples.
|
||||
* Skipping them marginally improves full loops in some Tokyo Jungle tracks (ex. a_1.sgd). */
|
||||
ffmpeg_set_skip_samples(ffmpeg_data, 256);
|
||||
}
|
||||
/* SGXD loop/sample values are relative (without skip samples), no need to adjust */
|
||||
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* open the file for reading */
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
|
||||
if (is_sgb && streamHeader) close_streamfile(streamHeader);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
if (is_sgb && streamHeader) close_streamfile(streamHeader);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* SGXD - Sony/SCEI's format (SGB+SGH / SGD / SGX) */
|
||||
VGMSTREAM * init_vgmstream_sgxd(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
STREAMFILE * streamHeader = NULL;
|
||||
off_t start_offset, data_offset, chunk_offset, name_offset = 0;
|
||||
size_t stream_size;
|
||||
|
||||
int is_sgx, is_sgb = 0;
|
||||
int loop_flag, channels, type;
|
||||
int sample_rate, num_samples, loop_start_sample, loop_end_sample;
|
||||
int total_subsongs, target_subsong = streamFile->stream_index;
|
||||
|
||||
|
||||
/* check extension, case insensitive */
|
||||
/* .sgx: header+data (Genji), .sgd: header+data, .sgh/sgd: header/data */
|
||||
if (!check_extensions(streamFile,"sgx,sgd,sgb"))
|
||||
goto fail;
|
||||
is_sgx = check_extensions(streamFile,"sgx");
|
||||
is_sgb = check_extensions(streamFile,"sgb");
|
||||
|
||||
/* SGB+SGH: use SGH as header; otherwise use the current file as header */
|
||||
if (is_sgb) {
|
||||
streamHeader = open_streamfile_by_ext(streamFile, "sgh");
|
||||
if (!streamHeader) goto fail;
|
||||
} else {
|
||||
streamHeader = streamFile;
|
||||
}
|
||||
|
||||
|
||||
/* SGXD base (size 0x10) */
|
||||
if (read_32bitBE(0x00,streamHeader) != 0x53475844) /* "SGXD" */
|
||||
goto fail;
|
||||
/* 0x04 SGX: full header_size; SGD/SGH: unknown header_size (counting from 0x0/0x8/0x10, varies) */
|
||||
/* 0x08 SGX: first chunk offset? (0x10); SGD/SGH: full header_size */
|
||||
/* 0x0c SGX/SGH: full data size with padding; SGD: full data size + 0x80000000 with padding */
|
||||
if (is_sgb) {
|
||||
data_offset = 0x00;
|
||||
} else if ( is_sgx ) {
|
||||
data_offset = read_32bitLE(0x04,streamHeader);
|
||||
} else {
|
||||
data_offset = read_32bitLE(0x08,streamHeader);
|
||||
}
|
||||
|
||||
|
||||
/* typical chunks: WAVE, RGND, NAME (strings for WAVE or RGND), SEQD (related to SFX), WSUR, WMKR, BUSS */
|
||||
/* WAVE chunk (size 0x10 + files * 0x38 + optional padding) */
|
||||
if (is_sgx) { /* position after chunk+size */
|
||||
if (read_32bitBE(0x10,streamHeader) != 0x57415645) goto fail; /* "WAVE" */
|
||||
chunk_offset = 0x18;
|
||||
} else {
|
||||
if (!find_chunk_le(streamHeader, 0x57415645,0x10,0, &chunk_offset,NULL)) goto fail; /* "WAVE" */
|
||||
}
|
||||
/* 0x04 SGX: unknown; SGD/SGH: chunk length, 0x08 null */
|
||||
|
||||
/* check multi-streams (usually only SE containers; Puppeteer) */
|
||||
total_subsongs = read_32bitLE(chunk_offset+0x04,streamHeader);
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
||||
|
||||
/* read stream header */
|
||||
{
|
||||
off_t stream_offset;
|
||||
chunk_offset += 0x08 + 0x38 * (target_subsong-1); /* position in target header*/
|
||||
|
||||
/* 0x00 ? (00/01/02) */
|
||||
if (!is_sgx) /* meaning unknown in .sgx; offset 0 = not a stream (a RGND sample) */
|
||||
name_offset = read_32bitLE(chunk_offset+0x04,streamHeader);
|
||||
type = read_8bit(chunk_offset+0x08,streamHeader);
|
||||
channels = read_8bit(chunk_offset+0x09,streamHeader);
|
||||
/* 0x0a null */
|
||||
sample_rate = read_32bitLE(chunk_offset+0x0c,streamHeader);
|
||||
|
||||
/* 0x10 info_type: meaning of the next value
|
||||
* (00=null, 30/40=data size without padding (ADPCM, ATRAC3plus), 80/A0=block size (AC3) */
|
||||
/* 0x14 info_value (see above) */
|
||||
/* 0x18 unknown (ex. 0x0008/0010/3307/CC02/etc)x2 */
|
||||
/* 0x1c null */
|
||||
|
||||
num_samples = read_32bitLE(chunk_offset+0x20,streamHeader);
|
||||
loop_start_sample = read_32bitLE(chunk_offset+0x24,streamHeader);
|
||||
loop_end_sample = read_32bitLE(chunk_offset+0x28,streamHeader);
|
||||
stream_size = read_32bitLE(chunk_offset+0x2c,streamHeader); /* stream size (without padding) / interleave (for type3) */
|
||||
|
||||
if (is_sgx) {
|
||||
stream_offset = 0x0;
|
||||
} else{
|
||||
stream_offset = read_32bitLE(chunk_offset+0x30,streamHeader);
|
||||
}
|
||||
/* 0x34 SGX: unknown; SGD/SGH: stream size (with padding) / interleave */
|
||||
|
||||
loop_flag = loop_start_sample!=0xffffffff && loop_end_sample!=0xffffffff;
|
||||
start_offset = data_offset + stream_offset;
|
||||
}
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = num_samples;
|
||||
vgmstream->loop_start_sample = loop_start_sample;
|
||||
vgmstream->loop_end_sample = loop_end_sample;
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
vgmstream->stream_size = stream_size;
|
||||
vgmstream->meta_type = meta_SGXD;
|
||||
if (name_offset)
|
||||
read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,streamHeader);
|
||||
|
||||
switch (type) {
|
||||
#ifdef VGM_USE_VORBIS
|
||||
case 0x02: /* Ogg Vorbis [Ni no Kuni: Wrath of the White Witch Remastered (PC)] (codec hijack?) */
|
||||
vgmstream->codec_data = init_ogg_vorbis(streamFile, start_offset, stream_size, NULL);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_OGG_VORBIS;
|
||||
vgmstream->layout_type = layout_none;
|
||||
break;
|
||||
#endif
|
||||
case 0x03: /* PS-ADPCM [Genji (PS3), Ape Escape Move (PS3)]*/
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
if (is_sgx || is_sgb) {
|
||||
vgmstream->interleave_block_size = 0x10;
|
||||
} else { /* this only seems to happen with SFX */
|
||||
vgmstream->interleave_block_size = stream_size;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case 0x04: { /* ATRAC3plus [Kurohyo 1/2 (PSP), BraveStory (PSP)] */
|
||||
vgmstream->codec_data = init_ffmpeg_atrac3_riff(streamFile, start_offset, NULL);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
/* SGXD's sample rate has priority over RIFF's sample rate (may not match) */
|
||||
/* loop/sample values are relative (without skip) vs RIFF (with skip), matching "smpl" otherwise */
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case 0x05: /* Short PS-ADPCM [Afrika (PS3)] */
|
||||
vgmstream->coding_type = coding_PSX_cfg;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x4;
|
||||
|
||||
break;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case 0x06: { /* AC3 [Tokyo Jungle (PS3), Afrika (PS3)] */
|
||||
ffmpeg_codec_data *ffmpeg_data;
|
||||
|
||||
ffmpeg_data = init_ffmpeg_offset(streamFile, start_offset, stream_size);
|
||||
if ( !ffmpeg_data ) goto fail;
|
||||
vgmstream->codec_data = ffmpeg_data;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
/* manually set skip_samples if FFmpeg didn't do it */
|
||||
if (ffmpeg_data->skipSamples <= 0) {
|
||||
/* PS3 AC3 consistently has 256 encoder delay samples, and there are ~1000-2000 samples after num_samples.
|
||||
* Skipping them marginally improves full loops in some Tokyo Jungle tracks (ex. a_1.sgd). */
|
||||
ffmpeg_set_skip_samples(ffmpeg_data, 256);
|
||||
}
|
||||
/* SGXD loop/sample values are relative (without skip samples), no need to adjust */
|
||||
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* open the file for reading */
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
|
||||
if (is_sgb && streamHeader) close_streamfile(streamHeader);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
if (is_sgb && streamHeader) close_streamfile(streamHeader);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,103 +1,100 @@
|
|||
#include "meta.h"
|
||||
#include "../layout/layout.h"
|
||||
#include "../util.h"
|
||||
|
||||
/* THP (Just play audio from .thp movie file)
|
||||
by fastelbja */
|
||||
|
||||
VGMSTREAM * init_vgmstream_thp(STREAMFILE *streamFile) {
|
||||
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
|
||||
char filename[PATH_LIMIT];
|
||||
off_t start_offset;
|
||||
|
||||
uint32_t maxAudioSize=0;
|
||||
|
||||
uint32_t numComponents;
|
||||
off_t componentTypeOffset;
|
||||
off_t componentDataOffset;
|
||||
|
||||
char thpVersion;
|
||||
|
||||
int loop_flag;
|
||||
int channel_count=-1;
|
||||
int i;
|
||||
|
||||
/* check extension, case insensitive */
|
||||
streamFile->get_name(streamFile,filename,sizeof(filename));
|
||||
if (strcasecmp("thp",filename_extension(filename)) &&
|
||||
strcasecmp("dsp",filename_extension(filename))) goto fail;
|
||||
|
||||
/* check header */
|
||||
if (read_32bitBE(0x00,streamFile) != 0x54485000)
|
||||
goto fail;
|
||||
|
||||
maxAudioSize = read_32bitBE(0x0C,streamFile);
|
||||
thpVersion = read_8bit(0x06,streamFile);
|
||||
|
||||
if(maxAudioSize==0) // no sound
|
||||
goto fail;
|
||||
|
||||
loop_flag = 0; // allways unloop
|
||||
|
||||
/* fill in the vital statistics */
|
||||
if(thpVersion==0x10) {
|
||||
start_offset = read_32bitBE(0x24,streamFile);
|
||||
/* No idea what's up with this */
|
||||
if (start_offset == 0)
|
||||
start_offset = read_32bitBE(0x28,streamFile);
|
||||
} else
|
||||
start_offset = read_32bitBE(0x28,streamFile);
|
||||
|
||||
// Get info from the first block
|
||||
componentTypeOffset = read_32bitBE(0x20,streamFile);
|
||||
numComponents = read_32bitBE(componentTypeOffset ,streamFile);
|
||||
componentDataOffset=componentTypeOffset+0x14;
|
||||
componentTypeOffset+=4;
|
||||
|
||||
for(i=0;i<numComponents;i++) {
|
||||
if(read_8bit(componentTypeOffset+i,streamFile)==1) {
|
||||
channel_count=read_32bitBE(componentDataOffset,streamFile);
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->channels=channel_count;
|
||||
vgmstream->sample_rate=read_32bitBE(componentDataOffset+4,streamFile);
|
||||
vgmstream->num_samples=read_32bitBE(componentDataOffset+8,streamFile);
|
||||
break;
|
||||
} else {
|
||||
if(thpVersion==0x10)
|
||||
componentDataOffset+=0x0c;
|
||||
else
|
||||
componentDataOffset+=0x08;
|
||||
}
|
||||
}
|
||||
|
||||
/* open the file for reading */
|
||||
{
|
||||
int i;
|
||||
STREAMFILE * file;
|
||||
file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
if (!file) goto fail;
|
||||
for (i=0;i<channel_count;i++) {
|
||||
vgmstream->ch[i].streamfile = file;
|
||||
}
|
||||
}
|
||||
|
||||
vgmstream->full_block_size = read_32bitBE(0x18,streamFile); /* block size of current block, changes every time */
|
||||
block_update_thp(start_offset,vgmstream);
|
||||
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
vgmstream->layout_type = layout_blocked_thp;
|
||||
vgmstream->meta_type = meta_THP;
|
||||
|
||||
return vgmstream;
|
||||
|
||||
/* clean up anything we may have opened */
|
||||
fail:
|
||||
if (vgmstream) close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "../layout/layout.h"
|
||||
|
||||
/* THP - Nintendo movie format found in GC/Wii games */
|
||||
VGMSTREAM* init_vgmstream_thp(STREAMFILE *streamFile) {
|
||||
VGMSTREAM *vgmstream = NULL;
|
||||
off_t start_offset, component_type_offset, component_data_offset;
|
||||
uint32_t version, max_audio_size;
|
||||
int num_components;
|
||||
int loop_flag, channel_count;
|
||||
int i;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .thp: actual extension
|
||||
* .dsp: fake extension?
|
||||
* (extensionless): Fragile (Wii) */
|
||||
if (!check_extensions(streamFile, "thp,dsp,"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,streamFile) != 0x54485000) /* "THP\0" */
|
||||
goto fail;
|
||||
|
||||
version = read_32bitBE(0x04,streamFile); /* 16b+16b major/minor */
|
||||
/* 0x08: max buffer size */
|
||||
max_audio_size = read_32bitBE(0x0C,streamFile);
|
||||
/* 0x10: fps in float */
|
||||
/* 0x14: block count */
|
||||
/* 0x18: first block size */
|
||||
/* 0x1c: data size */
|
||||
|
||||
if (version != 0x00010000 && version != 0x00011000) /* v1.0 (~2002) or v1.1 (rest) */
|
||||
goto fail;
|
||||
if (max_audio_size == 0) /* no sound */
|
||||
goto fail;
|
||||
|
||||
component_type_offset = read_32bitBE(0x20,streamFile);
|
||||
/* 0x24: block offsets table offset (optional, for seeking) */
|
||||
start_offset = read_32bitBE(0x28,streamFile);
|
||||
/* 0x2c: last block offset */
|
||||
|
||||
/* first component "type" x16 then component headers */
|
||||
num_components = read_32bitBE(component_type_offset,streamFile);
|
||||
component_type_offset += 0x04;
|
||||
component_data_offset = component_type_offset + 0x10;
|
||||
|
||||
/* parse "component" (data that goes into blocks) */
|
||||
for (i = 0; i < num_components; i++) {
|
||||
int type = read_8bit(component_type_offset + i,streamFile);
|
||||
|
||||
if (type == 0x00) { /* video */
|
||||
if (version == 0x00010000)
|
||||
component_data_offset += 0x08; /* width + height */
|
||||
else
|
||||
component_data_offset += 0x0c; /* width + height + format? */
|
||||
}
|
||||
else if (type == 0x01) { /* audio */
|
||||
/* parse below */
|
||||
#if 0
|
||||
if (version == 0x00010000)
|
||||
component_data_offset += 0x0c; /* channels + sample rate + samples */
|
||||
else
|
||||
component_data_offset += 0x10; /* channels + sample rate + samples + format? */
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
else { /* 0xFF / no data (reserved as THP is meant to be extensible) */
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* official docs remark original's audio is adjusted to match GC's hardware rate
|
||||
* (48000 > 48043 / 32000 > 32028), not sure if that means ouput sample rate should
|
||||
* adjusted, but we can't detect Wii (non adjusted) .thp tho */
|
||||
|
||||
loop_flag = 0;
|
||||
channel_count = read_32bitBE(component_data_offset + 0x00,streamFile);
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = read_32bitBE(component_data_offset + 0x04,streamFile);
|
||||
vgmstream->num_samples = read_32bitBE(component_data_offset + 0x08,streamFile);
|
||||
|
||||
vgmstream->meta_type = meta_THP;
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
vgmstream->layout_type = layout_blocked_thp;
|
||||
/* coefs are in every block */
|
||||
|
||||
vgmstream->full_block_size = read_32bitBE(0x18,streamFile); /* next block size */
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -199,13 +199,15 @@ VGMSTREAM * init_vgmstream_vag(STREAMFILE *streamFile) {
|
|||
interleave = 0x800;
|
||||
loop_flag = 0;
|
||||
}
|
||||
else if (read_32bitBE(0x24, streamFile) == 0x56414778) { /* VAGx" */
|
||||
else if (read_32bitBE(0x24, streamFile) == 0x56414778) { /* "VAGx" */
|
||||
/* Need for Speed: Hot Pursuit 2 (PS2) */
|
||||
start_offset = 0x30;
|
||||
channel_count = read_32bitBE(0x2c, streamFile);
|
||||
channel_size = channel_size / channel_count;
|
||||
loop_flag = 0;
|
||||
|
||||
if (file_size % 0x10 != 0) goto fail;
|
||||
|
||||
/* detect interleave using end markers */
|
||||
interleave = 0;
|
||||
|
||||
|
@ -214,7 +216,7 @@ VGMSTREAM * init_vgmstream_vag(STREAMFILE *streamFile) {
|
|||
off_t end_off = 0;
|
||||
uint8_t flag;
|
||||
|
||||
while (1) {
|
||||
while (offset > start_offset) {
|
||||
offset -= 0x10;
|
||||
flag = read_8bit(offset + 0x01, streamFile);
|
||||
if (flag == 0x01) {
|
||||
|
@ -225,9 +227,9 @@ VGMSTREAM * init_vgmstream_vag(STREAMFILE *streamFile) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (offset == start_offset) goto fail;
|
||||
}
|
||||
|
||||
if (!interleave) goto fail;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -1,99 +1,99 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* .WAVE - WayForward "EngineBlack" games [Mighty Switch Force! (3DS), Adventure Time: Hey Ice King! Why'd You Steal Our Garbage?! (3DS)] */
|
||||
VGMSTREAM * init_vgmstream_wave(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset, extradata_offset;
|
||||
int loop_flag = 0, channel_count, sample_rate, codec;
|
||||
int32_t num_samples, loop_start = 0, loop_end = 0;
|
||||
size_t interleave;
|
||||
int big_endian;
|
||||
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
|
||||
float (*read_f32)(off_t,STREAMFILE*) = NULL;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "wave"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitLE(0x00,streamFile) != 0xE5B7ECFE && /* header id */
|
||||
read_32bitBE(0x00,streamFile) != 0xE5B7ECFE)
|
||||
goto fail;
|
||||
if (read_32bitBE(0x04,streamFile) != 0x00) /* version? */
|
||||
goto fail;
|
||||
|
||||
/* assumed */
|
||||
big_endian = read_32bitBE(0x00,streamFile) == 0xE5B7ECFE;
|
||||
if (big_endian) {
|
||||
read_32bit = read_32bitBE;
|
||||
read_f32 = read_f32be;
|
||||
} else {
|
||||
read_32bit = read_32bitLE;
|
||||
read_f32 = read_f32le;
|
||||
}
|
||||
|
||||
channel_count = read_8bit(0x05,streamFile);
|
||||
|
||||
if (read_32bit(0x08,streamFile) != get_streamfile_size(streamFile))
|
||||
goto fail;
|
||||
if (read_8bit(0x0c,streamFile) != 0x00) /* ? */
|
||||
goto fail;
|
||||
|
||||
sample_rate = (int)read_f32(0x0c, streamFile); /* sample rate in 32b float (WHY?) */
|
||||
num_samples = read_32bit(0x10, streamFile);
|
||||
loop_start = read_32bit(0x14, streamFile);
|
||||
loop_end = read_32bit(0x18, streamFile);
|
||||
|
||||
codec = read_8bit(0x1c, streamFile);
|
||||
channel_count = read_8bit(0x1d, streamFile);
|
||||
if (read_8bit(0x1e, streamFile) != 0x00) goto fail; /* unknown */
|
||||
if (read_8bit(0x1f, streamFile) != 0x00) goto fail; /* unknown */
|
||||
|
||||
start_offset = read_32bit(0x20, streamFile);
|
||||
interleave = read_32bit(0x24, streamFile); /* typically half data_size */
|
||||
extradata_offset = read_32bit(0x28, streamFile); /* OR: extradata size (0x2c) */
|
||||
|
||||
loop_flag = (loop_start > 0);
|
||||
/* some songs (ex. Adventure Time's m_candykingdom_overworld.wave) do full loops, but there is no way
|
||||
* to tell them apart from sfx/voices, so we try to detect if it's long enough. */
|
||||
if(!loop_flag
|
||||
&& loop_start == 0 && loop_end == num_samples /* full loop */
|
||||
&& channel_count > 1
|
||||
&& num_samples > 20*sample_rate) { /* in seconds */
|
||||
loop_flag = 1;
|
||||
}
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = num_samples;
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
|
||||
vgmstream->meta_type = meta_WAVE;
|
||||
/* not sure if there are other codecs but anyway */
|
||||
switch(codec) {
|
||||
case 0x02:
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = interleave;
|
||||
|
||||
/* ADPCM setup: 0x20 coefs + 0x06 initial ps/hist1/hist2 + 0x06 loop ps/hist1/hist2, per channel */
|
||||
dsp_read_coefs(vgmstream, streamFile, extradata_offset+0x00, 0x2c, big_endian);
|
||||
dsp_read_hist(vgmstream, streamFile, extradata_offset+0x22, 0x2c, big_endian);
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* .WAVE - WayForward "EngineBlack" games [Mighty Switch Force! (3DS), Adventure Time: Hey Ice King! Why'd You Steal Our Garbage?! (3DS)] */
|
||||
VGMSTREAM * init_vgmstream_wave(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset, extradata_offset;
|
||||
int loop_flag = 0, channel_count, sample_rate, codec;
|
||||
int32_t num_samples, loop_start = 0, loop_end = 0;
|
||||
size_t interleave;
|
||||
int big_endian;
|
||||
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
|
||||
float (*read_f32)(off_t,STREAMFILE*) = NULL;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "wave"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitLE(0x00,streamFile) != 0xE5B7ECFE && /* header id */
|
||||
read_32bitBE(0x00,streamFile) != 0xE5B7ECFE)
|
||||
goto fail;
|
||||
if (read_32bitBE(0x04,streamFile) != 0x00) /* version? */
|
||||
goto fail;
|
||||
|
||||
/* assumed */
|
||||
big_endian = read_32bitBE(0x00,streamFile) == 0xE5B7ECFE;
|
||||
if (big_endian) {
|
||||
read_32bit = read_32bitBE;
|
||||
read_f32 = read_f32be;
|
||||
} else {
|
||||
read_32bit = read_32bitLE;
|
||||
read_f32 = read_f32le;
|
||||
}
|
||||
|
||||
channel_count = read_8bit(0x05,streamFile);
|
||||
|
||||
if (read_32bit(0x08,streamFile) != get_streamfile_size(streamFile))
|
||||
goto fail;
|
||||
if (read_8bit(0x0c,streamFile) != 0x00) /* ? */
|
||||
goto fail;
|
||||
|
||||
sample_rate = (int)read_f32(0x0c, streamFile); /* sample rate in 32b float (WHY?) */
|
||||
num_samples = read_32bit(0x10, streamFile);
|
||||
loop_start = read_32bit(0x14, streamFile);
|
||||
loop_end = read_32bit(0x18, streamFile);
|
||||
|
||||
codec = read_8bit(0x1c, streamFile);
|
||||
channel_count = read_8bit(0x1d, streamFile);
|
||||
if (read_8bit(0x1e, streamFile) != 0x00) goto fail; /* unknown */
|
||||
if (read_8bit(0x1f, streamFile) != 0x00) goto fail; /* unknown */
|
||||
|
||||
start_offset = read_32bit(0x20, streamFile);
|
||||
interleave = read_32bit(0x24, streamFile); /* typically half data_size */
|
||||
extradata_offset = read_32bit(0x28, streamFile); /* OR: extradata size (0x2c) */
|
||||
|
||||
loop_flag = (loop_start > 0);
|
||||
/* some songs (ex. Adventure Time's m_candykingdom_overworld.wave) do full loops, but there is no way
|
||||
* to tell them apart from sfx/voices, so we try to detect if it's long enough. */
|
||||
if(!loop_flag
|
||||
&& loop_start == 0 && loop_end == num_samples /* full loop */
|
||||
&& channel_count > 1
|
||||
&& num_samples > 20*sample_rate) { /* in seconds */
|
||||
loop_flag = 1;
|
||||
}
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = num_samples;
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
|
||||
vgmstream->meta_type = meta_WAVE;
|
||||
/* not sure if there are other codecs but anyway */
|
||||
switch(codec) {
|
||||
case 0x02:
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = interleave;
|
||||
|
||||
/* ADPCM setup: 0x20 coefs + 0x06 initial ps/hist1/hist2 + 0x06 loop ps/hist1/hist2, per channel */
|
||||
dsp_read_coefs(vgmstream, streamFile, extradata_offset+0x00, 0x2c, big_endian);
|
||||
dsp_read_hist(vgmstream, streamFile, extradata_offset+0x22, 0x2c, big_endian);
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,42 +1,42 @@
|
|||
#ifndef _MIXING_H_
|
||||
#define _MIXING_H_
|
||||
|
||||
#include "vgmstream.h"
|
||||
|
||||
/* Applies mixing commands to the sample buffer. Mixing must be externally enabled and
|
||||
* outbuf must big enough to hold output_channels*samples_to_do */
|
||||
void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream);
|
||||
|
||||
/* internal mixing pre-setup for vgmstream (doesn't imply usage).
|
||||
* If init somehow fails next calls are ignored. */
|
||||
void mixing_init(VGMSTREAM* vgmstream);
|
||||
void mixing_close(VGMSTREAM* vgmstream);
|
||||
void mixing_update_channel(VGMSTREAM* vgmstream);
|
||||
|
||||
/* Call to let vgmstream apply mixing, which must handle input/output_channels.
|
||||
* Once mixing is active any new mixes are ignored (to avoid the possibility
|
||||
* of down/upmixing without querying input/output_channels). */
|
||||
void mixing_setup(VGMSTREAM * vgmstream, int32_t max_sample_count);
|
||||
|
||||
/* gets current mixing info */
|
||||
void mixing_info(VGMSTREAM * vgmstream, int *input_channels, int *output_channels);
|
||||
|
||||
/* adds mixes filtering and optimizing if needed */
|
||||
void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src);
|
||||
void mixing_push_add(VGMSTREAM* vgmstream, int ch_dst, int ch_src, double volume);
|
||||
void mixing_push_volume(VGMSTREAM* vgmstream, int ch_dst, double volume);
|
||||
void mixing_push_limit(VGMSTREAM* vgmstream, int ch_dst, double volume);
|
||||
void mixing_push_upmix(VGMSTREAM* vgmstream, int ch_dst);
|
||||
void mixing_push_downmix(VGMSTREAM* vgmstream, int ch_dst);
|
||||
void mixing_push_killmix(VGMSTREAM* vgmstream, int ch_dst);
|
||||
void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double vol_end, char shape, int32_t time_pre, int32_t time_start, int32_t time_end, int32_t time_post);
|
||||
|
||||
void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask);
|
||||
void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask);
|
||||
void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode);
|
||||
void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max);
|
||||
void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode);
|
||||
void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/);
|
||||
|
||||
|
||||
#endif /* _MIXING_H_ */
|
||||
#ifndef _MIXING_H_
|
||||
#define _MIXING_H_
|
||||
|
||||
#include "vgmstream.h"
|
||||
|
||||
/* Applies mixing commands to the sample buffer. Mixing must be externally enabled and
|
||||
* outbuf must big enough to hold output_channels*samples_to_do */
|
||||
void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream);
|
||||
|
||||
/* internal mixing pre-setup for vgmstream (doesn't imply usage).
|
||||
* If init somehow fails next calls are ignored. */
|
||||
void mixing_init(VGMSTREAM* vgmstream);
|
||||
void mixing_close(VGMSTREAM* vgmstream);
|
||||
void mixing_update_channel(VGMSTREAM* vgmstream);
|
||||
|
||||
/* Call to let vgmstream apply mixing, which must handle input/output_channels.
|
||||
* Once mixing is active any new mixes are ignored (to avoid the possibility
|
||||
* of down/upmixing without querying input/output_channels). */
|
||||
void mixing_setup(VGMSTREAM * vgmstream, int32_t max_sample_count);
|
||||
|
||||
/* gets current mixing info */
|
||||
void mixing_info(VGMSTREAM * vgmstream, int *input_channels, int *output_channels);
|
||||
|
||||
/* adds mixes filtering and optimizing if needed */
|
||||
void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src);
|
||||
void mixing_push_add(VGMSTREAM* vgmstream, int ch_dst, int ch_src, double volume);
|
||||
void mixing_push_volume(VGMSTREAM* vgmstream, int ch_dst, double volume);
|
||||
void mixing_push_limit(VGMSTREAM* vgmstream, int ch_dst, double volume);
|
||||
void mixing_push_upmix(VGMSTREAM* vgmstream, int ch_dst);
|
||||
void mixing_push_downmix(VGMSTREAM* vgmstream, int ch_dst);
|
||||
void mixing_push_killmix(VGMSTREAM* vgmstream, int ch_dst);
|
||||
void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double vol_end, char shape, int32_t time_pre, int32_t time_start, int32_t time_end, int32_t time_post);
|
||||
|
||||
void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask);
|
||||
void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask);
|
||||
void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode);
|
||||
void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max);
|
||||
void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode);
|
||||
void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/);
|
||||
|
||||
|
||||
#endif /* _MIXING_H_ */
|
||||
|
|
|
@ -1,344 +1,348 @@
|
|||
#include "vgmstream.h"
|
||||
#include "plugins.h"
|
||||
#include "mixing.h"
|
||||
|
||||
|
||||
/* ****************************************** */
|
||||
/* CONTEXT: simplifies plugin code */
|
||||
/* ****************************************** */
|
||||
|
||||
int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) {
|
||||
const char ** extension_list;
|
||||
size_t extension_list_len;
|
||||
const char *extension;
|
||||
int i;
|
||||
|
||||
|
||||
if (cfg->is_extension) {
|
||||
extension = filename;
|
||||
} else {
|
||||
extension = filename_extension(filename);
|
||||
}
|
||||
|
||||
/* some metas accept extensionless files */
|
||||
if (strlen(extension) <= 0) {
|
||||
return !cfg->reject_extensionless;
|
||||
}
|
||||
|
||||
/* try in default list */
|
||||
if (!cfg->skip_standard) {
|
||||
extension_list = vgmstream_get_formats(&extension_list_len);
|
||||
for (i = 0; i < extension_list_len; i++) {
|
||||
if (strcasecmp(extension, extension_list[i]) == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* try in common extensions */
|
||||
if (cfg->accept_common) {
|
||||
extension_list = vgmstream_get_common_formats(&extension_list_len);
|
||||
for (i = 0; i < extension_list_len; i++) {
|
||||
if (strcasecmp(extension, extension_list[i]) == 0)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* allow anything not in the normal list but not in common extensions */
|
||||
if (cfg->accept_unknown) {
|
||||
int is_common = 0;
|
||||
|
||||
extension_list = vgmstream_get_common_formats(&extension_list_len);
|
||||
for (i = 0; i < extension_list_len; i++) {
|
||||
if (strcasecmp(extension, extension_list[i]) == 0) {
|
||||
is_common = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_common)
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ****************************************** */
|
||||
/* TAGS: loads key=val tags from a file */
|
||||
/* ****************************************** */
|
||||
|
||||
#define VGMSTREAM_TAGS_LINE_MAX 2048
|
||||
|
||||
/* opaque tag state */
|
||||
struct VGMSTREAM_TAGS {
|
||||
/* extracted output */
|
||||
char key[VGMSTREAM_TAGS_LINE_MAX];
|
||||
char val[VGMSTREAM_TAGS_LINE_MAX];
|
||||
|
||||
/* file to find tags for */
|
||||
int targetname_len;
|
||||
char targetname[VGMSTREAM_TAGS_LINE_MAX];
|
||||
/* path of targetname */
|
||||
char targetpath[VGMSTREAM_TAGS_LINE_MAX];
|
||||
|
||||
/* tag section for filename (see comments below) */
|
||||
int section_found;
|
||||
off_t section_start;
|
||||
off_t section_end;
|
||||
off_t offset;
|
||||
|
||||
/* commands */
|
||||
int autotrack_on;
|
||||
int autotrack_written;
|
||||
int track_count;
|
||||
|
||||
int autoalbum_on;
|
||||
int autoalbum_written;
|
||||
};
|
||||
|
||||
|
||||
static void tags_clean(VGMSTREAM_TAGS* tag) {
|
||||
int i;
|
||||
int val_len = strlen(tag->val);
|
||||
|
||||
/* remove trailing spaces */
|
||||
for (i = val_len - 1; i > 0; i--) {
|
||||
if (tag->val[i] != ' ')
|
||||
break;
|
||||
tag->val[i] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val) {
|
||||
VGMSTREAM_TAGS* tags = malloc(sizeof(VGMSTREAM_TAGS));
|
||||
if (!tags) goto fail;
|
||||
|
||||
*tag_key = tags->key;
|
||||
*tag_val = tags->val;
|
||||
|
||||
return tags;
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void vgmstream_tags_close(VGMSTREAM_TAGS *tags) {
|
||||
free(tags);
|
||||
}
|
||||
|
||||
/* Find next tag and return 1 if found.
|
||||
*
|
||||
* Tags can be "global" @TAGS, "command" $TAGS, and "file" %TAGS for a target filename.
|
||||
* To extract tags we must find either global tags, or the filename's tag "section"
|
||||
* where tags apply: (# @TAGS ) .. (other_filename) ..(# %TAGS section).. (target_filename).
|
||||
* When a new "other_filename" is found that offset is marked as section_start, and when
|
||||
* target_filename is found it's marked as section_end. Then we can begin extracting tags
|
||||
* within that section, until all tags are exhausted. Global tags are extracted as found,
|
||||
* so they always go first, also meaning any tags after file's section are ignored.
|
||||
* Command tags have special meanings and are output after all section tags. */
|
||||
int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) {
|
||||
off_t file_size = get_streamfile_size(tagfile);
|
||||
char currentname[VGMSTREAM_TAGS_LINE_MAX] = {0};
|
||||
char line[VGMSTREAM_TAGS_LINE_MAX];
|
||||
int ok, bytes_read, line_ok, n1,n2;
|
||||
|
||||
if (!tags)
|
||||
return 0;
|
||||
|
||||
/* prepare file start and skip BOM if needed */
|
||||
if (tags->offset == 0) {
|
||||
if ((uint16_t)read_16bitLE(0x00, tagfile) == 0xFFFE ||
|
||||
(uint16_t)read_16bitLE(0x00, tagfile) == 0xFEFF) {
|
||||
tags->offset = 0x02;
|
||||
if (tags->section_start == 0)
|
||||
tags->section_start = 0x02;
|
||||
}
|
||||
else if (((uint32_t)read_32bitBE(0x00, tagfile) & 0xFFFFFF00) == 0xEFBBBF00) {
|
||||
tags->offset = 0x03;
|
||||
if (tags->section_start == 0)
|
||||
tags->section_start = 0x03;
|
||||
}
|
||||
}
|
||||
|
||||
/* read lines */
|
||||
while (tags->offset <= file_size) {
|
||||
|
||||
/* after section: no more tags to extract */
|
||||
if (tags->section_found && tags->offset >= tags->section_end) {
|
||||
|
||||
/* write extra tags after all regular tags */
|
||||
if (tags->autotrack_on && !tags->autotrack_written) {
|
||||
sprintf(tags->key, "%s", "TRACK");
|
||||
sprintf(tags->val, "%i", tags->track_count);
|
||||
tags->autotrack_written = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (tags->autoalbum_on && !tags->autoalbum_written && tags->targetpath[0] != '\0') {
|
||||
const char* path;
|
||||
|
||||
path = strrchr(tags->targetpath,'\\');
|
||||
if (!path) {
|
||||
path = strrchr(tags->targetpath,'/');
|
||||
}
|
||||
if (!path) {
|
||||
path = tags->targetpath;
|
||||
}
|
||||
|
||||
sprintf(tags->key, "%s", "ALBUM");
|
||||
sprintf(tags->val, "%s", path+1);
|
||||
tags->autoalbum_written = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
goto fail;
|
||||
}
|
||||
|
||||
bytes_read = read_line(line, sizeof(line), tags->offset, tagfile, &line_ok);
|
||||
if (!line_ok || bytes_read == 0) goto fail;
|
||||
|
||||
tags->offset += bytes_read;
|
||||
|
||||
|
||||
if (tags->section_found) {
|
||||
/* find possible file tag */
|
||||
ok = sscanf(line, "# %%%[^ \t] %[^\r\n] ", tags->key,tags->val);
|
||||
if (ok == 2) {
|
||||
tags_clean(tags);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
if (line[0] == '#') {
|
||||
/* find possible global command */
|
||||
ok = sscanf(line, "# $%[^ \t] %[^\r\n]", tags->key,tags->val);
|
||||
if (ok == 1 || ok == 2) {
|
||||
if (strcasecmp(tags->key,"AUTOTRACK") == 0) {
|
||||
tags->autotrack_on = 1;
|
||||
}
|
||||
else if (strcasecmp(tags->key,"AUTOALBUM") == 0) {
|
||||
tags->autoalbum_on = 1;
|
||||
}
|
||||
|
||||
continue; /* not an actual tag */
|
||||
}
|
||||
|
||||
/* find possible global tag */
|
||||
ok = sscanf(line, "# @%[^ \t] %[^\r\n]", tags->key,tags->val);
|
||||
if (ok == 2) {
|
||||
tags_clean(tags);
|
||||
return 1;
|
||||
}
|
||||
|
||||
continue; /* next line */
|
||||
}
|
||||
|
||||
/* find possible filename and section start/end
|
||||
* (.m3u seem to allow filenames with whitespaces before, make sure to trim) */
|
||||
ok = sscanf(line, " %n%[^\r\n]%n ", &n1, currentname, &n2);
|
||||
if (ok == 1) {
|
||||
int currentname_len = n2 - n1;
|
||||
int filename_found = 0;
|
||||
|
||||
/* we want to find file with the same name (case insensitive), OR a virtual .txtp with
|
||||
* the filename inside (so 'file.adx' gets tags from 'file.adx#i.txtp', reading
|
||||
* tags even if we don't open !tags.m3u with virtual .txtp directly) */
|
||||
|
||||
/* strcasecmp works ok even for UTF-8 */
|
||||
if (currentname_len >= tags->targetname_len && /* starts with targetname */
|
||||
strncasecmp(currentname, tags->targetname, tags->targetname_len) == 0) {
|
||||
|
||||
if (currentname_len == tags->targetname_len) { /* exact match */
|
||||
filename_found = 1;
|
||||
}
|
||||
else if (vgmstream_is_virtual_filename(currentname)) { /* ends with .txth */
|
||||
char c = currentname[tags->targetname_len];
|
||||
/* tell apart the unlikely case of having both 'bgm01.ad.txtp' and 'bgm01.adp.txtp' */
|
||||
filename_found = (c==' ' || c == '.' || c == '#');
|
||||
}
|
||||
}
|
||||
|
||||
if (filename_found) {
|
||||
/* section ok, start would be set before this (or be 0) */
|
||||
tags->section_end = tags->offset;
|
||||
tags->section_found = 1;
|
||||
tags->offset = tags->section_start;
|
||||
}
|
||||
else {
|
||||
/* mark new possible section */
|
||||
tags->section_start = tags->offset;
|
||||
}
|
||||
|
||||
tags->track_count++; /* new track found (target filename or not) */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* empty/bad line, probably */
|
||||
}
|
||||
}
|
||||
|
||||
/* may reach here if read up to file_size but no section was found */
|
||||
|
||||
fail:
|
||||
tags->key[0] = '\0';
|
||||
tags->val[0] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename) {
|
||||
char *path;
|
||||
|
||||
if (!tags)
|
||||
return;
|
||||
|
||||
memset(tags, 0, sizeof(VGMSTREAM_TAGS));
|
||||
|
||||
//todo validate sizes and copy sensible max
|
||||
|
||||
/* get base name */
|
||||
strcpy(tags->targetpath, target_filename);
|
||||
|
||||
/* Windows CMD accepts both \\ and /, and maybe plugin uses either */
|
||||
path = strrchr(tags->targetpath,'\\');
|
||||
if (!path) {
|
||||
path = strrchr(tags->targetpath,'/');
|
||||
}
|
||||
if (path != NULL) {
|
||||
path[0] = '\0'; /* leave targetpath with path only */
|
||||
path = path+1;
|
||||
}
|
||||
|
||||
if (path) {
|
||||
strcpy(tags->targetname, path);
|
||||
} else {
|
||||
tags->targetpath[0] = '\0';
|
||||
strcpy(tags->targetname, target_filename);
|
||||
}
|
||||
tags->targetname_len = strlen(tags->targetname);
|
||||
}
|
||||
|
||||
/* ****************************************** */
|
||||
/* MIXING: modifies vgmstream output */
|
||||
/* ****************************************** */
|
||||
|
||||
void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels) {
|
||||
mixing_setup(vgmstream, max_sample_count);
|
||||
mixing_info(vgmstream, input_channels, output_channels);
|
||||
}
|
||||
|
||||
void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels) {
|
||||
if (max_channels <= 0)
|
||||
return;
|
||||
|
||||
/* guess mixing the best we can, using standard downmixing if possible
|
||||
* (without mapping we can't be sure if format is using a standard layout) */
|
||||
if (vgmstream->channel_layout && max_channels <= 2) {
|
||||
mixing_macro_downmix(vgmstream, max_channels);
|
||||
}
|
||||
else {
|
||||
mixing_macro_layer(vgmstream, max_channels, 0, 'e');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
#include "vgmstream.h"
|
||||
#include "plugins.h"
|
||||
#include "mixing.h"
|
||||
|
||||
|
||||
/* ****************************************** */
|
||||
/* CONTEXT: simplifies plugin code */
|
||||
/* ****************************************** */
|
||||
|
||||
int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) {
|
||||
const char ** extension_list;
|
||||
size_t extension_list_len;
|
||||
const char *extension;
|
||||
int i;
|
||||
|
||||
|
||||
if (cfg->is_extension) {
|
||||
extension = filename;
|
||||
} else {
|
||||
extension = filename_extension(filename);
|
||||
}
|
||||
|
||||
/* some metas accept extensionless files */
|
||||
if (strlen(extension) <= 0) {
|
||||
return !cfg->reject_extensionless;
|
||||
}
|
||||
|
||||
/* try in default list */
|
||||
if (!cfg->skip_standard) {
|
||||
extension_list = vgmstream_get_formats(&extension_list_len);
|
||||
for (i = 0; i < extension_list_len; i++) {
|
||||
if (strcasecmp(extension, extension_list[i]) == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* try in common extensions */
|
||||
if (cfg->accept_common) {
|
||||
extension_list = vgmstream_get_common_formats(&extension_list_len);
|
||||
for (i = 0; i < extension_list_len; i++) {
|
||||
if (strcasecmp(extension, extension_list[i]) == 0)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* allow anything not in the normal list but not in common extensions */
|
||||
if (cfg->accept_unknown) {
|
||||
int is_common = 0;
|
||||
|
||||
extension_list = vgmstream_get_common_formats(&extension_list_len);
|
||||
for (i = 0; i < extension_list_len; i++) {
|
||||
if (strcasecmp(extension, extension_list[i]) == 0) {
|
||||
is_common = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_common)
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ****************************************** */
|
||||
/* TAGS: loads key=val tags from a file */
|
||||
/* ****************************************** */
|
||||
|
||||
#define VGMSTREAM_TAGS_LINE_MAX 2048
|
||||
|
||||
/* opaque tag state */
|
||||
struct VGMSTREAM_TAGS {
|
||||
/* extracted output */
|
||||
char key[VGMSTREAM_TAGS_LINE_MAX];
|
||||
char val[VGMSTREAM_TAGS_LINE_MAX];
|
||||
|
||||
/* file to find tags for */
|
||||
int targetname_len;
|
||||
char targetname[VGMSTREAM_TAGS_LINE_MAX];
|
||||
/* path of targetname */
|
||||
char targetpath[VGMSTREAM_TAGS_LINE_MAX];
|
||||
|
||||
/* tag section for filename (see comments below) */
|
||||
int section_found;
|
||||
off_t section_start;
|
||||
off_t section_end;
|
||||
off_t offset;
|
||||
|
||||
/* commands */
|
||||
int autotrack_on;
|
||||
int autotrack_written;
|
||||
int track_count;
|
||||
|
||||
int autoalbum_on;
|
||||
int autoalbum_written;
|
||||
};
|
||||
|
||||
|
||||
static void tags_clean(VGMSTREAM_TAGS* tag) {
|
||||
int i;
|
||||
int val_len = strlen(tag->val);
|
||||
|
||||
/* remove trailing spaces */
|
||||
for (i = val_len - 1; i > 0; i--) {
|
||||
if (tag->val[i] != ' ')
|
||||
break;
|
||||
tag->val[i] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val) {
|
||||
VGMSTREAM_TAGS* tags = malloc(sizeof(VGMSTREAM_TAGS));
|
||||
if (!tags) goto fail;
|
||||
|
||||
*tag_key = tags->key;
|
||||
*tag_val = tags->val;
|
||||
|
||||
return tags;
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void vgmstream_tags_close(VGMSTREAM_TAGS *tags) {
|
||||
free(tags);
|
||||
}
|
||||
|
||||
/* Find next tag and return 1 if found.
|
||||
*
|
||||
* Tags can be "global" @TAGS, "command" $TAGS, and "file" %TAGS for a target filename.
|
||||
* To extract tags we must find either global tags, or the filename's tag "section"
|
||||
* where tags apply: (# @TAGS ) .. (other_filename) ..(# %TAGS section).. (target_filename).
|
||||
* When a new "other_filename" is found that offset is marked as section_start, and when
|
||||
* target_filename is found it's marked as section_end. Then we can begin extracting tags
|
||||
* within that section, until all tags are exhausted. Global tags are extracted as found,
|
||||
* so they always go first, also meaning any tags after file's section are ignored.
|
||||
* Command tags have special meanings and are output after all section tags. */
|
||||
int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) {
|
||||
off_t file_size = get_streamfile_size(tagfile);
|
||||
char currentname[VGMSTREAM_TAGS_LINE_MAX] = {0};
|
||||
char line[VGMSTREAM_TAGS_LINE_MAX];
|
||||
int ok, bytes_read, line_ok, n1,n2;
|
||||
|
||||
if (!tags)
|
||||
return 0;
|
||||
|
||||
/* prepare file start and skip BOM if needed */
|
||||
if (tags->offset == 0) {
|
||||
if ((uint16_t)read_16bitLE(0x00, tagfile) == 0xFFFE ||
|
||||
(uint16_t)read_16bitLE(0x00, tagfile) == 0xFEFF) {
|
||||
tags->offset = 0x02;
|
||||
if (tags->section_start == 0)
|
||||
tags->section_start = 0x02;
|
||||
}
|
||||
else if (((uint32_t)read_32bitBE(0x00, tagfile) & 0xFFFFFF00) == 0xEFBBBF00) {
|
||||
tags->offset = 0x03;
|
||||
if (tags->section_start == 0)
|
||||
tags->section_start = 0x03;
|
||||
}
|
||||
}
|
||||
|
||||
/* read lines */
|
||||
while (tags->offset <= file_size) {
|
||||
|
||||
/* after section: no more tags to extract */
|
||||
if (tags->section_found && tags->offset >= tags->section_end) {
|
||||
|
||||
/* write extra tags after all regular tags */
|
||||
if (tags->autotrack_on && !tags->autotrack_written) {
|
||||
sprintf(tags->key, "%s", "TRACK");
|
||||
sprintf(tags->val, "%i", tags->track_count);
|
||||
tags->autotrack_written = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (tags->autoalbum_on && !tags->autoalbum_written && tags->targetpath[0] != '\0') {
|
||||
const char* path;
|
||||
|
||||
path = strrchr(tags->targetpath,'\\');
|
||||
if (!path) {
|
||||
path = strrchr(tags->targetpath,'/');
|
||||
}
|
||||
if (!path) {
|
||||
path = tags->targetpath;
|
||||
}
|
||||
|
||||
sprintf(tags->key, "%s", "ALBUM");
|
||||
sprintf(tags->val, "%s", path+1);
|
||||
tags->autoalbum_written = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
goto fail;
|
||||
}
|
||||
|
||||
bytes_read = read_line(line, sizeof(line), tags->offset, tagfile, &line_ok);
|
||||
if (!line_ok || bytes_read == 0) goto fail;
|
||||
|
||||
tags->offset += bytes_read;
|
||||
|
||||
|
||||
if (tags->section_found) {
|
||||
/* find possible file tag */
|
||||
ok = sscanf(line, "# %%%[^%%]%% %[^\r\n] ", tags->key,tags->val); /* key with spaces */
|
||||
if (ok != 2)
|
||||
ok = sscanf(line, "# %%%[^ \t] %[^\r\n] ", tags->key,tags->val); /* key without */
|
||||
if (ok == 2) {
|
||||
tags_clean(tags);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
if (line[0] == '#') {
|
||||
/* find possible global command */
|
||||
ok = sscanf(line, "# $%[^ \t] %[^\r\n]", tags->key,tags->val);
|
||||
if (ok == 1 || ok == 2) {
|
||||
if (strcasecmp(tags->key,"AUTOTRACK") == 0) {
|
||||
tags->autotrack_on = 1;
|
||||
}
|
||||
else if (strcasecmp(tags->key,"AUTOALBUM") == 0) {
|
||||
tags->autoalbum_on = 1;
|
||||
}
|
||||
|
||||
continue; /* not an actual tag */
|
||||
}
|
||||
|
||||
/* find possible global tag */
|
||||
ok = sscanf(line, "# @%[^@]@ %[^\r\n]", tags->key,tags->val); /* key with spaces */
|
||||
if (ok != 2)
|
||||
ok = sscanf(line, "# @%[^ \t] %[^\r\n]", tags->key,tags->val); /* key without */
|
||||
if (ok == 2) {
|
||||
tags_clean(tags);
|
||||
return 1;
|
||||
}
|
||||
|
||||
continue; /* next line */
|
||||
}
|
||||
|
||||
/* find possible filename and section start/end
|
||||
* (.m3u seem to allow filenames with whitespaces before, make sure to trim) */
|
||||
ok = sscanf(line, " %n%[^\r\n]%n ", &n1, currentname, &n2);
|
||||
if (ok == 1) {
|
||||
int currentname_len = n2 - n1;
|
||||
int filename_found = 0;
|
||||
|
||||
/* we want to find file with the same name (case insensitive), OR a virtual .txtp with
|
||||
* the filename inside (so 'file.adx' gets tags from 'file.adx#i.txtp', reading
|
||||
* tags even if we don't open !tags.m3u with virtual .txtp directly) */
|
||||
|
||||
/* strcasecmp works ok even for UTF-8 */
|
||||
if (currentname_len >= tags->targetname_len && /* starts with targetname */
|
||||
strncasecmp(currentname, tags->targetname, tags->targetname_len) == 0) {
|
||||
|
||||
if (currentname_len == tags->targetname_len) { /* exact match */
|
||||
filename_found = 1;
|
||||
}
|
||||
else if (vgmstream_is_virtual_filename(currentname)) { /* ends with .txth */
|
||||
char c = currentname[tags->targetname_len];
|
||||
/* tell apart the unlikely case of having both 'bgm01.ad.txtp' and 'bgm01.adp.txtp' */
|
||||
filename_found = (c==' ' || c == '.' || c == '#');
|
||||
}
|
||||
}
|
||||
|
||||
if (filename_found) {
|
||||
/* section ok, start would be set before this (or be 0) */
|
||||
tags->section_end = tags->offset;
|
||||
tags->section_found = 1;
|
||||
tags->offset = tags->section_start;
|
||||
}
|
||||
else {
|
||||
/* mark new possible section */
|
||||
tags->section_start = tags->offset;
|
||||
}
|
||||
|
||||
tags->track_count++; /* new track found (target filename or not) */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* empty/bad line, probably */
|
||||
}
|
||||
}
|
||||
|
||||
/* may reach here if read up to file_size but no section was found */
|
||||
|
||||
fail:
|
||||
tags->key[0] = '\0';
|
||||
tags->val[0] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename) {
|
||||
char *path;
|
||||
|
||||
if (!tags)
|
||||
return;
|
||||
|
||||
memset(tags, 0, sizeof(VGMSTREAM_TAGS));
|
||||
|
||||
//todo validate sizes and copy sensible max
|
||||
|
||||
/* get base name */
|
||||
strcpy(tags->targetpath, target_filename);
|
||||
|
||||
/* Windows CMD accepts both \\ and /, and maybe plugin uses either */
|
||||
path = strrchr(tags->targetpath,'\\');
|
||||
if (!path) {
|
||||
path = strrchr(tags->targetpath,'/');
|
||||
}
|
||||
if (path != NULL) {
|
||||
path[0] = '\0'; /* leave targetpath with path only */
|
||||
path = path+1;
|
||||
}
|
||||
|
||||
if (path) {
|
||||
strcpy(tags->targetname, path);
|
||||
} else {
|
||||
tags->targetpath[0] = '\0';
|
||||
strcpy(tags->targetname, target_filename);
|
||||
}
|
||||
tags->targetname_len = strlen(tags->targetname);
|
||||
}
|
||||
|
||||
/* ****************************************** */
|
||||
/* MIXING: modifies vgmstream output */
|
||||
/* ****************************************** */
|
||||
|
||||
void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels) {
|
||||
mixing_setup(vgmstream, max_sample_count);
|
||||
mixing_info(vgmstream, input_channels, output_channels);
|
||||
}
|
||||
|
||||
void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels) {
|
||||
if (max_channels <= 0)
|
||||
return;
|
||||
|
||||
/* guess mixing the best we can, using standard downmixing if possible
|
||||
* (without mapping we can't be sure if format is using a standard layout) */
|
||||
if (vgmstream->channel_layout && max_channels <= 2) {
|
||||
mixing_macro_downmix(vgmstream, max_channels);
|
||||
}
|
||||
else {
|
||||
mixing_macro_layer(vgmstream, max_channels, 0, 'e');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,106 +1,106 @@
|
|||
/*
|
||||
* plugins.h - helper for plugins
|
||||
*/
|
||||
#ifndef _PLUGINS_H_
|
||||
#define _PLUGINS_H_
|
||||
|
||||
#include "streamfile.h"
|
||||
|
||||
/* ****************************************** */
|
||||
/* CONTEXT: simplifies plugin code */
|
||||
/* ****************************************** */
|
||||
|
||||
typedef struct {
|
||||
int is_extension; /* set if filename is already an extension */
|
||||
int skip_standard; /* set if shouldn't check standard formats */
|
||||
int reject_extensionless; /* set if player can't play extensionless files */
|
||||
int accept_unknown; /* set to allow any extension (for txth) */
|
||||
int accept_common; /* set to allow known-but-common extension (when player has plugin priority) */
|
||||
} vgmstream_ctx_valid_cfg;
|
||||
|
||||
/* returns if vgmstream can parse file by extension */
|
||||
int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg);
|
||||
|
||||
#if 0
|
||||
|
||||
/* opaque player state */
|
||||
typedef struct VGMSTREAM_CTX VGMSTREAM_CTX;
|
||||
|
||||
typedef struct {
|
||||
//...
|
||||
} VGMSTREAM_CTX_INFO;
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_init(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_format_check(...);
|
||||
VGMSTREAM_CTX* vgmstream_ctx_set_format_whilelist(...);
|
||||
VGMSTREAM_CTX* vgmstream_ctx_set_format_blacklist(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_set_file(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_get_config(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_set_config(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_get_buffer(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_get_info(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_describe(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_get_title(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_get_tagfile(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_play(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_seek(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_close(...);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/* ****************************************** */
|
||||
/* TAGS: loads key=val tags from a file */
|
||||
/* ****************************************** */
|
||||
|
||||
/* opaque tag state */
|
||||
typedef struct VGMSTREAM_TAGS VGMSTREAM_TAGS;
|
||||
|
||||
/* Initializes TAGS and returns pointers to extracted strings (always valid but change
|
||||
* on every vgmstream_tags_next_tag call). Next functions are safe to call even if this fails (validate NULL).
|
||||
* ex.: const char *tag_key, *tag_val; tags=vgmstream_tags_init(&tag_key, &tag_val); */
|
||||
VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val);
|
||||
|
||||
/* Resets tagfile to restart reading from the beginning for a new filename.
|
||||
* Must be called first before extracting tags. */
|
||||
void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename);
|
||||
|
||||
|
||||
/* Extracts next valid tag in tagfile to *tag. Returns 0 if no more tags are found (meant to be
|
||||
* called repeatedly until 0). Key/values are trimmed and values can be in UTF-8. */
|
||||
int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile);
|
||||
|
||||
/* Closes tag file */
|
||||
void vgmstream_tags_close(VGMSTREAM_TAGS* tags);
|
||||
|
||||
|
||||
/* ****************************************** */
|
||||
/* MIXING: modifies vgmstream output */
|
||||
/* ****************************************** */
|
||||
|
||||
/* Enables mixing effects, with max outbuf samples as a hint. Once active, plugin
|
||||
* must use returned input_channels to create outbuf and output_channels to output audio.
|
||||
* max_sample_count may be 0 if you only need to query values and not actually enable it.
|
||||
* Needs to be enabled last after adding effects. */
|
||||
void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels);
|
||||
|
||||
/* sets automatic downmixing if vgmstream's channels are higher than max_channels */
|
||||
void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels);
|
||||
|
||||
/* sets a fadeout */
|
||||
//void vgmstream_mixing_fadeout(VGMSTREAM *vgmstream, float start_second, float duration_seconds);
|
||||
|
||||
#endif /* _PLUGINS_H_ */
|
||||
/*
|
||||
* plugins.h - helper for plugins
|
||||
*/
|
||||
#ifndef _PLUGINS_H_
|
||||
#define _PLUGINS_H_
|
||||
|
||||
#include "streamfile.h"
|
||||
|
||||
/* ****************************************** */
|
||||
/* CONTEXT: simplifies plugin code */
|
||||
/* ****************************************** */
|
||||
|
||||
typedef struct {
|
||||
int is_extension; /* set if filename is already an extension */
|
||||
int skip_standard; /* set if shouldn't check standard formats */
|
||||
int reject_extensionless; /* set if player can't play extensionless files */
|
||||
int accept_unknown; /* set to allow any extension (for txth) */
|
||||
int accept_common; /* set to allow known-but-common extension (when player has plugin priority) */
|
||||
} vgmstream_ctx_valid_cfg;
|
||||
|
||||
/* returns if vgmstream can parse file by extension */
|
||||
int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg);
|
||||
|
||||
#if 0
|
||||
|
||||
/* opaque player state */
|
||||
typedef struct VGMSTREAM_CTX VGMSTREAM_CTX;
|
||||
|
||||
typedef struct {
|
||||
//...
|
||||
} VGMSTREAM_CTX_INFO;
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_init(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_format_check(...);
|
||||
VGMSTREAM_CTX* vgmstream_ctx_set_format_whilelist(...);
|
||||
VGMSTREAM_CTX* vgmstream_ctx_set_format_blacklist(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_set_file(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_get_config(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_set_config(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_get_buffer(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_get_info(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_describe(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_get_title(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_get_tagfile(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_play(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_seek(...);
|
||||
|
||||
VGMSTREAM_CTX* vgmstream_ctx_close(...);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/* ****************************************** */
|
||||
/* TAGS: loads key=val tags from a file */
|
||||
/* ****************************************** */
|
||||
|
||||
/* opaque tag state */
|
||||
typedef struct VGMSTREAM_TAGS VGMSTREAM_TAGS;
|
||||
|
||||
/* Initializes TAGS and returns pointers to extracted strings (always valid but change
|
||||
* on every vgmstream_tags_next_tag call). Next functions are safe to call even if this fails (validate NULL).
|
||||
* ex.: const char *tag_key, *tag_val; tags=vgmstream_tags_init(&tag_key, &tag_val); */
|
||||
VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val);
|
||||
|
||||
/* Resets tagfile to restart reading from the beginning for a new filename.
|
||||
* Must be called first before extracting tags. */
|
||||
void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename);
|
||||
|
||||
|
||||
/* Extracts next valid tag in tagfile to *tag. Returns 0 if no more tags are found (meant to be
|
||||
* called repeatedly until 0). Key/values are trimmed and values can be in UTF-8. */
|
||||
int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile);
|
||||
|
||||
/* Closes tag file */
|
||||
void vgmstream_tags_close(VGMSTREAM_TAGS* tags);
|
||||
|
||||
|
||||
/* ****************************************** */
|
||||
/* MIXING: modifies vgmstream output */
|
||||
/* ****************************************** */
|
||||
|
||||
/* Enables mixing effects, with max outbuf samples as a hint. Once active, plugin
|
||||
* must use returned input_channels to create outbuf and output_channels to output audio.
|
||||
* max_sample_count may be 0 if you only need to query values and not actually enable it.
|
||||
* Needs to be enabled last after adding effects. */
|
||||
void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels);
|
||||
|
||||
/* sets automatic downmixing if vgmstream's channels are higher than max_channels */
|
||||
void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels);
|
||||
|
||||
/* sets a fadeout */
|
||||
//void vgmstream_mixing_fadeout(VGMSTREAM *vgmstream, float start_second, float duration_seconds);
|
||||
|
||||
#endif /* _PLUGINS_H_ */
|
||||
|
|
|
@ -59,6 +59,14 @@ void put_16bitBE(uint8_t * buf, int16_t i);
|
|||
|
||||
void put_32bitBE(uint8_t * buf, int32_t i);
|
||||
|
||||
/* alias of the above */ //TODO: improve
|
||||
#define put_u8 put_8bit
|
||||
#define put_u16le put_16bitLE
|
||||
#define put_u32le put_32bitLE
|
||||
#define put_u16be put_16bitBE
|
||||
#define put_u32be put_32bitBE
|
||||
|
||||
|
||||
/* signed nibbles come up a lot */
|
||||
static int nibble_to_int[16] = {0,1,2,3,4,5,6,7,-8,-7,-6,-5,-4,-3,-2,-1};
|
||||
|
||||
|
|
|
@ -285,14 +285,14 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
|
|||
init_vgmstream_mn_str,
|
||||
init_vgmstream_mss,
|
||||
init_vgmstream_ps2_hsf,
|
||||
init_vgmstream_ps3_ivag,
|
||||
init_vgmstream_ivag,
|
||||
init_vgmstream_ps2_2pfs,
|
||||
init_vgmstream_xnb,
|
||||
init_vgmstream_ubi_ckd,
|
||||
init_vgmstream_ps2_vbk,
|
||||
init_vgmstream_otm,
|
||||
init_vgmstream_bcstm,
|
||||
init_vgmstream_idsp_nus3,
|
||||
init_vgmstream_idsp_namco,
|
||||
init_vgmstream_kt_g1l,
|
||||
init_vgmstream_kt_wiibgm,
|
||||
init_vgmstream_ktss,
|
||||
|
@ -405,6 +405,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
|
|||
init_vgmstream_hd3_bd3,
|
||||
init_vgmstream_bnk_sony,
|
||||
init_vgmstream_nus3bank,
|
||||
init_vgmstream_nus3bank_encrypted,
|
||||
init_vgmstream_scd_sscf,
|
||||
init_vgmstream_dsp_sps_n1,
|
||||
init_vgmstream_dsp_itl_ch,
|
||||
|
@ -513,8 +514,8 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) {
|
|||
if (!vgmstream)
|
||||
continue;
|
||||
|
||||
/* fail if there is nothing to play (without this check vgmstream can generate empty files) */
|
||||
if (vgmstream->num_samples <= 0) {
|
||||
/* fail if there is nothing/too much to play (<=0 generates empty files, >N writes GBs of garbage) */
|
||||
if (vgmstream->num_samples <= 0 || vgmstream->num_samples > VGMSTREAM_MAX_NUM_SAMPLES) {
|
||||
VGM_LOG("VGMSTREAM: wrong num_samples %i\n", vgmstream->num_samples);
|
||||
close_vgmstream(vgmstream);
|
||||
continue;
|
||||
|
|
|
@ -12,6 +12,7 @@ enum { VGMSTREAM_MAX_CHANNELS = 64 };
|
|||
enum { VGMSTREAM_MIN_SAMPLE_RATE = 300 }; /* 300 is Wwise min */
|
||||
enum { VGMSTREAM_MAX_SAMPLE_RATE = 192000 }; /* found in some FSB5 */
|
||||
enum { VGMSTREAM_MAX_SUBSONGS = 65535 };
|
||||
enum { VGMSTREAM_MAX_NUM_SAMPLES = 1000000000 }; /* no ~5h vgm hopefully */
|
||||
|
||||
#include "streamfile.h"
|
||||
|
||||
|
@ -582,13 +583,13 @@ typedef enum {
|
|||
meta_MN_STR, /* Mini Ninjas (PC/PS3/WII) */
|
||||
meta_MSS, /* Guerilla: ShellShock Nam '67 (PS2/Xbox), Killzone (PS2) */
|
||||
meta_PS2_HSF, /* Lowrider (PS2) */
|
||||
meta_PS3_IVAG, /* Interleaved VAG files (PS3) */
|
||||
meta_IVAG,
|
||||
meta_PS2_2PFS, /* Konami: Mahoromatic: Moetto - KiraKira Maid-San, GANTZ (PS2) */
|
||||
meta_PS2_VBK, /* Disney's Stitch - Experiment 626 */
|
||||
meta_OTM, /* Otomedius (Arcade) */
|
||||
meta_CSTM, /* Nintendo 3DS CSTM (Century Stream) */
|
||||
meta_FSTM, /* Nintendo Wii U FSTM (caFe? Stream) */
|
||||
meta_IDSP_NUS3, /* Namco 3DS/Wii U IDSP */
|
||||
meta_IDSP_NAMCO,
|
||||
meta_KT_WIIBGM, /* Koei Tecmo WiiBGM */
|
||||
meta_KTSS, /* Koei Tecmo Nintendo Stream (KNS) */
|
||||
meta_MCA, /* Capcom MCA "MADP" */
|
||||
|
|
Loading…
Reference in New Issue