2019-12-15 18:21:31 +01:00
|
|
|
#include "meta.h"
|
|
|
|
#include "../coding/coding.h"
|
|
|
|
|
|
|
|
/* XMA from Unreal Engine games */
|
2023-01-20 01:02:23 +01:00
|
|
|
VGMSTREAM* init_vgmstream_xma_ue3(STREAMFILE* sf) {
|
2019-12-15 18:21:31 +01:00
|
|
|
VGMSTREAM* vgmstream = NULL;
|
2023-01-20 01:02:23 +01:00
|
|
|
uint32_t start_offset, chunk_offset;
|
|
|
|
int loop_flag, channel_count, sample_rate;
|
2019-12-15 18:21:31 +01:00
|
|
|
int num_samples, loop_start_sample, loop_end_sample;
|
2023-01-20 01:02:23 +01:00
|
|
|
uint32_t file_size, chunk_size, seek_size, data_size;
|
2019-12-15 18:21:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
/* 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. */
|
|
|
|
|
2023-01-20 01:02:23 +01:00
|
|
|
/* checks */
|
2019-12-15 18:21:31 +01:00
|
|
|
file_size = get_streamfile_size(sf);
|
2023-01-20 01:02:23 +01:00
|
|
|
chunk_size = read_u32be(0x00, sf);
|
2019-12-15 18:21:31 +01:00
|
|
|
seek_size = read_u32be(0x04, sf);
|
|
|
|
data_size = read_u32be(0x08, sf);
|
2023-01-20 01:02:23 +01:00
|
|
|
if (0x0c + chunk_size + seek_size + data_size != file_size)
|
2019-12-15 18:21:31 +01:00
|
|
|
goto fail;
|
2023-01-20 01:02:23 +01:00
|
|
|
|
|
|
|
/* .xma: assumed */
|
|
|
|
/* .x360audio: fake produced by UE Viewer */
|
|
|
|
if (!check_extensions(sf, "xma,x360audio,"))
|
|
|
|
goto fail;
|
|
|
|
|
2019-12-15 18:21:31 +01:00
|
|
|
chunk_offset = 0x0c;
|
|
|
|
|
|
|
|
/* parse sample data (always BE unlike real XMA) */
|
2023-01-20 01:02:23 +01:00
|
|
|
if (chunk_size != 0x34) { /* old XMA2 [The Last Remnant (X360)] */
|
2019-12-15 18:21:31 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2023-01-20 01:02:23 +01:00
|
|
|
start_offset = 0x0c + chunk_size + seek_size;
|
2019-12-15 18:21:31 +01:00
|
|
|
|
|
|
|
/* 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
|
|
|
|
{
|
2023-01-20 01:02:23 +01:00
|
|
|
vgmstream->codec_data = init_ffmpeg_xma_chunk(sf, start_offset, data_size, chunk_offset, chunk_size);
|
2019-12-15 18:21:31 +01:00
|
|
|
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;
|
|
|
|
}
|