mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-12-19 09:55:53 +01:00
98 lines
3.5 KiB
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;
|
||
|
}
|