Add EA-XMA for EA SNU [Dante's Inferno (X360)]

only 1/2ch works correctly as multichannel layout need to be
investigated
This commit is contained in:
bnnm 2017-09-24 18:52:09 +02:00
parent 4bb1103e3d
commit a8370b4892
3 changed files with 229 additions and 18 deletions

View File

@ -202,9 +202,8 @@ void free_at3plus(maiatrac3plus_codec_data *data);
#ifdef VGM_USE_FFMPEG
/* ffmpeg_decoder */
ffmpeg_codec_data * init_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size);
ffmpeg_codec_data * init_ffmpeg_offset_index(STREAMFILE *streamFile, uint64_t start, uint64_t size, int stream_index);
ffmpeg_codec_data * init_ffmpeg_header_offset(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size);
ffmpeg_codec_data * init_ffmpeg_header_offset_index(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size, int stream_index);
ffmpeg_codec_data * init_ffmpeg_config(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size, ffmpeg_custom_config * config);
void decode_ffmpeg(VGMSTREAM *stream, sample * outbuf, int32_t samples_to_do, int channels);
void reset_ffmpeg(VGMSTREAM *vgmstream);
@ -212,6 +211,10 @@ void seek_ffmpeg(VGMSTREAM *vgmstream, int32_t num_sample);
void free_ffmpeg(ffmpeg_codec_data *data);
void ffmpeg_set_skip_samples(ffmpeg_codec_data * data, int skip_samples);
size_t ffmpeg_make_opus_header(uint8_t * buf, int buf_size, int channels, int skip, int sample_rate);
size_t ffmpeg_get_eaxma_virtual_size(off_t real_offset, size_t real_size, STREAMFILE *streamFile);
#endif
/* coding_utils */
@ -256,5 +259,4 @@ void xma2_parse_xma2_chunk(STREAMFILE *streamFile, off_t chunk_offset, int * cha
size_t atrac3_bytes_to_samples(size_t bytes, int full_block_align);
size_t atrac3plus_bytes_to_samples(size_t bytes, int full_block_align);
#endif /*_CODING_H*/

View File

@ -0,0 +1,153 @@
#include "coding.h"
#include "ffmpeg_decoder_utils.h"
#ifdef VGM_USE_FFMPEG
#define EAXMA_XMA_BLOCK_SIZE 0x800
/**
* EA-XMA is XMA with padding removed (so a real 0x450 block would be padded to a virtual 0x800 block).
* //todo missing multichannel (packet multistream) support, unknown layout
*/
int ffmpeg_custom_read_eaxma(ffmpeg_codec_data *data, uint8_t *buf, int buf_size) {
uint8_t v_buf[0x8000]; /* intermediate buffer, could be simplified */
int buf_done = 0;
uint64_t real_offset = data->real_offset;
uint64_t virtual_offset = data->virtual_offset - data->header_size;
uint64_t virtual_base = data->virtual_base;
/* read and transform SNS/EA-XMA block into XMA block by adding padding */
while (buf_done < buf_size) {
int bytes_to_copy;
size_t data_size, extra_size = 0, gap_size = 0;
size_t block_size = read_32bitBE(real_offset, data->streamfile);
/* 0x04(4): some kind of size? 0x08(4): decoded samples */
/* setup */
data_size = (block_size & 0x00FFFFFF) - 0x0c; //todo last block size may be slightly off?
if (data_size % EAXMA_XMA_BLOCK_SIZE) /* aligned padding */
extra_size = EAXMA_XMA_BLOCK_SIZE - (data_size % EAXMA_XMA_BLOCK_SIZE);
if (buf_done == 0) /* first read */
gap_size = virtual_offset - virtual_base; /* might start a few bytes into the block */
if (data_size + extra_size > 0x8000) {
VGM_LOG("EA-XMA: total size bigger than buffer at %lx\n", (off_t)real_offset);
return 0;
}
bytes_to_copy = data_size + extra_size - gap_size;
if (bytes_to_copy > buf_size - buf_done)
bytes_to_copy = buf_size - buf_done;
/* transform */
read_streamfile(v_buf, real_offset + 0x0c, data_size, data->streamfile);
memset(v_buf + data_size, 0xFF, extra_size); /* padding can be any value, typically 0xFF */
memcpy(buf + buf_done, v_buf + gap_size, bytes_to_copy);
/* move when block is fully done */
if (data_size + extra_size == bytes_to_copy + gap_size) {
real_offset += (block_size & 0x00FFFFFF);
virtual_base += data_size + extra_size;
}
buf_done += bytes_to_copy;
/* exit on last block just in case, though should reach file size */
if (block_size & 0x80000000)
break;
}
data->real_offset = real_offset;
data->virtual_base = virtual_base;
return buf_size;
}
int64_t ffmpeg_custom_seek_eaxma(ffmpeg_codec_data *data, int64_t virtual_offset) {
int64_t real_offset, virtual_base;
int64_t current_virtual_offset = data->virtual_offset;
/* Find SNS block start closest to offset. ie. virtual_offset 0x1A10 could mean SNS blocks
* of 0x456+0x820 padded to 0x800+0x1000 (base) + 0x210 (extra for reads), thus real_offset = 0xC76 */
if (virtual_offset > current_virtual_offset) { /* seek after current: start from current block */
real_offset = data->real_offset;
virtual_base = data->virtual_base;
}
else { /* seek before current: start from the beginning */
real_offset = data->real_start;
virtual_base = 0;
}
/* find target block */
while (virtual_base < virtual_offset) {
size_t data_size, extra_size = 0;
size_t block_size = read_32bitBE(real_offset, data->streamfile);
data_size = (block_size & 0x00FFFFFF) - 0x0c;
if (data_size % EAXMA_XMA_BLOCK_SIZE)
extra_size = EAXMA_XMA_BLOCK_SIZE - (data_size % EAXMA_XMA_BLOCK_SIZE);
/* stop if virtual_offset lands inside current block */
if (data_size + extra_size > virtual_offset)
break;
real_offset += (block_size & 0x00FFFFFF);
virtual_base += data_size + extra_size;
}
/* closest we can use for reads */
data->real_offset = real_offset;
data->virtual_base = virtual_base;
return virtual_offset;
}
int64_t ffmpeg_custom_size_eaxma(ffmpeg_codec_data *data) {
uint64_t virtual_size = data->config.virtual_size;
if (!virtual_size)
return 0;
return virtual_size + data->header_size;
}
/* needed to know in meta for fake RIFF */
size_t ffmpeg_get_eaxma_virtual_size(off_t real_offset, size_t real_size, STREAMFILE *streamFile) {
size_t virtual_size = 0;
/* count all SNS/EAXMA blocks size + padding size */
while (real_offset < real_size) {
size_t data_size;
size_t block_size = read_32bitBE(real_offset, streamFile);
data_size = (block_size & 0x00FFFFFF) - 0x0c;
if ((block_size & 0xFF000000) && !(block_size & 0x80000000)) {
VGM_LOG("EA-XMA: unknown flag found at %lx\n", (off_t)real_offset);
goto fail;
}
real_offset += (block_size & 0x00FFFFFF);
virtual_size += data_size;
if (data_size % EAXMA_XMA_BLOCK_SIZE) /* XMA block padding */
virtual_size += EAXMA_XMA_BLOCK_SIZE - (data_size % EAXMA_XMA_BLOCK_SIZE);
/* exit on last block just in case, though should reach real_size */
if (block_size & 0x80000000)
break;
}
return virtual_size;
fail:
return 0;
}
#endif

View File

@ -1,27 +1,38 @@
#include "meta.h"
#include "../layout/layout.h"
#include "../coding/coding.h"
/* .SNU - EA new-ish header (Dead Space, The Godfather 2) */
/* .SNU - from EA Redwood Shores/Visceral games (Dead Space, Dante's Inferno, The Godfather 2) */
VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
int channel_count, loop_flag = 0, channel_config, codec, sample_rate, flags;
uint32_t num_samples, loop_start = 0, loop_end = 0;
off_t start_offset;
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
/* check extension, case insensitive */
if (!check_extensions(streamFile,"snu"))
goto fail;
/* check header */
//if ((read_32bitBE(0x00,streamFile) & 0x00FFFF00 != 0x00000000) && (read_32bitBE(0x0c,streamFile) != 0x00000000))
// goto fail;
/* 0x00: related to sample rate?, 0x02: always 0?, 0x03: related to channels? (usually match but may be 0) */
/* 0x04: some size, maybe >>2 ~= number of 0x4c frames (BE/LE depending on platform) */
/* 0x08: always 0x20? (also BE/LE), 0x0c: always 0? */
/* check header (the first 0x10 are BE/LE depending on platform) */
/* 0x00(1): related to sample rate? (03=48000)
* 0x01(1): flags? (when set seems to be a bank and has extra data before start_offset) //todo
* 0x02(1): always 0?
* 0x03(1): channels? (usually matches but rarely may be 0)
* 0x04(4): some size, maybe >>2 ~= number of frames
* 0x08(4): start offset
* 0x0c(4): some sub-offset? (0x20, found when 0x01 is set) */
/* use start offset as endianness flag */
if ((uint32_t)read_32bitLE(0x08,streamFile) > 0x00F00000) {
read_32bit = read_32bitBE;
} else {
read_32bit = read_32bitLE;
}
start_offset = 0x20; /* first block */
start_offset = read_32bit(0x08,streamFile);
codec = read_8bit(0x10,streamFile);
channel_config = read_8bit(0x11,streamFile);
@ -36,7 +47,7 @@ VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE *streamFile) {
}
#if 0
//todo not working ok with blocks
//todo not working ok with blocks in XAS
if (flags & 0x60) { /* full loop, seen in ambient tracks */
loop_flag = 1;
loop_start = 0;
@ -45,7 +56,7 @@ VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE *streamFile) {
#endif
//channel_count = (channel_config >> 2) + 1; //todo test
/* 01/02/03 = 1 ch?, 05/06/07 = 2/3 ch?, 0d/0e/0f = 4/5 ch?, 14/15/16/17 = 6/7 ch?, 1d/1e/1f = 8 ch? */
/* 01/02/03 = 1 ch?, 05/06/07 = 2/3 ch?, 0d/0e/0f = 4/5 ch?, 15/16/17 = 6/7 ch?, 1d/1e/1f = 8 ch? */
switch(channel_config) {
case 0x00: channel_count = 1; break;
case 0x04: channel_count = 2; break;
@ -68,20 +79,64 @@ VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE *streamFile) {
vgmstream->loop_end_sample = loop_end;
vgmstream->meta_type = meta_EA_SNU;
vgmstream->layout_type = layout_ea_sns_blocked;
switch(codec) {
case 0x04: /* "Xas1": EA-XAS (Dead Space) */
vgmstream->coding_type = coding_EA_XAS;
vgmstream->layout_type = layout_ea_sns_blocked;
break;
#if 0
#ifdef VGM_USE_MPEG
case 0x07: { /* "EL32S": EALayer3 v2 "S" (Dante's Inferno PS3) */
mpeg_custom_config cfg;
off_t mpeg_start_offset = start_offset + 0x08;
memset(&cfg, 0, sizeof(mpeg_custom_config));
/* layout is still blocks, but should work fine with the custom mpeg decoder */
vgmstream->codec_data = init_mpeg_custom_codec_data(streamFile, mpeg_start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_EAL32S, &cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->layout_type = layout_ea_sns_blocked;
break;
}
#endif
#endif
#ifdef VGM_USE_FFMPEG
case 0x03: { /* "EXm0": EA-XMA (Dante's Inferno X360) */
uint8_t buf[0x100];
int bytes, block_size, block_count;
size_t stream_size, virtual_size;
ffmpeg_custom_config cfg;
stream_size = get_streamfile_size(streamFile) - start_offset;
virtual_size = ffmpeg_get_eaxma_virtual_size(start_offset,stream_size, streamFile);
block_size = 0x8000; /* ? */
block_count = stream_size / block_size + (stream_size % block_size ? 1 : 0);
bytes = ffmpeg_make_riff_xma2(buf, 0x100, vgmstream->num_samples, virtual_size, vgmstream->channels, vgmstream->sample_rate, block_count, block_size);
if (bytes <= 0) goto fail;
memset(&cfg, 0, sizeof(ffmpeg_custom_config));
cfg.type = FFMPEG_EA_XMA;
cfg.virtual_size = virtual_size;
vgmstream->codec_data = init_ffmpeg_config(streamFile, buf,bytes, start_offset,stream_size, &cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
break;
}
#endif
case 0x00: /* "NONE" */
case 0x01: /* not used? */
case 0x02: /* "P6B0": PCM16BE */
case 0x03: /* "EXm0": EA-XMA */
case 0x05: /* "EL31": EALayer3 v1 b (with PCM blocks in normal EA-frames?) */
case 0x06: /* "EL32P": EALayer3 v2 "P" */
case 0x07: /* "EL32S": EALayer3 v2 "S" */
case 0x09: /* EASpeex? */
case 0x0c: /* EAOpus? */
case 0x0e: /* XAS variant? */
@ -97,6 +152,7 @@ VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE *streamFile) {
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
goto fail;
if (vgmstream->layout_type == layout_ea_sns_blocked)
ea_sns_block_update(start_offset, vgmstream);
return vgmstream;