vgmstream/src/meta/xma_ue3.c

98 lines
3.5 KiB
C

#include "meta.h"
#include "../coding/coding.h"
/* XMA from Unreal Engine games */
VGMSTREAM* init_vgmstream_xma_ue3(STREAMFILE *sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset, chunk_offset;
int loop_flag, channel_count, sample_rate, is_xma2_old = 0;
int num_samples, loop_start_sample, loop_end_sample;
size_t file_size, fmt_size, seek_size, data_size;
/* checks */
/* .xma: assumed */
if (!check_extensions(sf, "xma,"))
goto fail;
/* UE3 uses class-like chunks called "SoundNodeWave" to store info and (rarely multi) raw audio data. Other
* platforms use standard formats (PC=Ogg, PS3=MSF), while X360 has mutant XMA. Extractors transmogrify
* UE3 XMA into RIFF XMA (discarding seek table and changing endianness) but we'll support actual raw
* data for completeness. UE4 has .uexp which are very similar so XBone may use the same XMA. */
file_size = get_streamfile_size(sf);
fmt_size = read_u32be(0x00, sf);
seek_size = read_u32be(0x04, sf);
data_size = read_u32be(0x08, sf);
if (0x0c + fmt_size + seek_size + data_size != file_size)
goto fail;
chunk_offset = 0x0c;
/* parse sample data (always BE unlike real XMA) */
if (fmt_size != 0x34) { /* old XMA2 [The Last Remnant (X360)] */
is_xma2_old = 1;
xma2_parse_xma2_chunk(sf, chunk_offset, &channel_count,&sample_rate, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample);
}
else { /* new XMA2 [Shadows of the Damned (X360)] */
channel_count = read_s16be(chunk_offset + 0x02, sf);
sample_rate = read_s32be(chunk_offset + 0x04, sf);
xma2_parse_fmt_chunk_extra(sf, chunk_offset, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample, 1);
}
start_offset = 0x0c + fmt_size + seek_size;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_XMA_UE3;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = num_samples;
vgmstream->loop_start_sample = loop_start_sample;
vgmstream->loop_end_sample = loop_end_sample;
#ifdef VGM_USE_FFMPEG
{
uint8_t buf[0x100];
size_t bytes;
if (is_xma2_old) {
bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf,0x100, chunk_offset,fmt_size, data_size, sf);
} else {
bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf,0x100, chunk_offset,fmt_size, data_size, sf, 1);
}
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, start_offset,data_size);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
xma_fix_raw_samples(vgmstream, sf, start_offset, data_size, chunk_offset, 1,1);
}
#else
goto fail;
#endif
/* UE3 seems to set full loops for non-looping tracks (real loops do exist though), try to detect */
{
int full_loop, is_small;
/* *must* be after xma_fix_raw_samples */
full_loop = vgmstream->loop_start_sample == 0 && vgmstream->loop_end_sample == vgmstream->num_samples;
is_small = 1; //vgmstream->num_samples < 20 * vgmstream->sample_rate; /* all files */
if (full_loop && is_small) {
VGM_LOG("XMA UE3a: disabled unwanted loop\n");
vgmstream->loop_flag = 0;
}
}
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}