Add RAGE aud XMA [GTA IV (X360)]

This commit is contained in:
bnnm 2024-04-28 23:23:13 +02:00
parent c12bdb004c
commit e7b1b98860
4 changed files with 333 additions and 39 deletions

View File

@ -152,6 +152,7 @@
<ClInclude Include="meta\ogg_vorbis_streamfile.h" />
<ClInclude Include="meta\opus_interleave_streamfile.h" />
<ClInclude Include="meta\ppst_streamfile.h" />
<ClInclude Include="meta\rage_aud_streamfile.h" />
<ClInclude Include="meta\riff_ogg_streamfile.h" />
<ClInclude Include="meta\sab_streamfile.h" />
<ClInclude Include="meta\sfh_streamfile.h" />

View File

@ -281,6 +281,9 @@
<ClInclude Include="meta\ppst_streamfile.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
<ClInclude Include="meta\rage_aud_streamfile.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
<ClInclude Include="meta\riff_ogg_streamfile.h">
<Filter>meta\Header Files</Filter>
</ClInclude>

View File

@ -1,39 +1,42 @@
#include "meta.h"
#include "../layout/layout.h"
#include "../coding/coding.h"
#include "../layout/layout.h"
#include "../util/endianness.h"
#include "rage_aud_streamfile.h"
typedef struct {
int is_music;
int big_endian;
int is_streamed; /* implicit: streams=music, sfx=memory */
int total_subsongs;
int channel_count;
int channels;
int sample_rate;
int codec;
int num_samples;
int codec;
size_t block_count;
size_t block_size;
int block_count;
int block_chunk;
off_t stream_offset;
size_t stream_size;
int big_endian;
uint32_t stream_offset;
uint32_t stream_size;
} aud_header;
static int parse_aud_header(STREAMFILE* sf, aud_header* aud);
static layered_layout_data* build_layered_rage_aud(STREAMFILE* sf, aud_header* aud);
/* RAGE AUD - MC:LA, GTA IV (PC/PS3/X360) */
/* RAGE AUD - from older RAGE engine games [Midnight Club: Los Angeles (PS3/X360), GTA IV (PC/PS3/X360)] */
VGMSTREAM* init_vgmstream_rage_aud(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
aud_header aud = {0};
int loop_flag;
/* checks */
/* extensionless (.ivaud is fake/added by tools) */
if (!check_extensions(sf, "ivaud,"))
/* (extensionless): original names before hashing
* .ivaud: fake/added by tools */
if (!check_extensions(sf, ",ivaud"))
return NULL;
/* check header */
@ -45,7 +48,7 @@ VGMSTREAM* init_vgmstream_rage_aud(STREAMFILE* sf) {
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(aud.channel_count, loop_flag);
vgmstream = allocate_vgmstream(aud.channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = aud.sample_rate;
@ -58,18 +61,21 @@ VGMSTREAM* init_vgmstream_rage_aud(STREAMFILE* sf) {
switch (aud.codec) {
case 0x0001: /* common in sfx, uncommon in music (ex. EP2_SFX/MENU_MUSIC) */
vgmstream->coding_type = aud.big_endian ? coding_PCM16BE : coding_PCM16LE;
vgmstream->layout_type = aud.is_music ? layout_blocked_rage_aud : layout_none;
vgmstream->full_block_size = aud.block_size;
vgmstream->layout_type = aud.is_streamed ? layout_blocked_rage_aud : layout_none;
vgmstream->full_block_size = aud.block_chunk;
break;
#ifdef VGM_USE_FFMPEG
case 0x0000: { /* XMA2 (X360) */
if (aud.is_music) {
goto fail;
case 0x0000: { /* XMA1 (X360) */
if (aud.is_streamed) {
vgmstream->layout_data = build_layered_rage_aud(sf, &aud);
if (!vgmstream->layout_data) goto fail;
vgmstream->layout_type = layout_layered;
vgmstream->coding_type = coding_FFmpeg;
}
else {
/* regular XMA for sfx */
vgmstream->codec_data = init_ffmpeg_xma1_raw(sf, aud.stream_offset, aud.stream_size, aud.channel_count, aud.sample_rate, 0);
vgmstream->codec_data = init_ffmpeg_xma1_raw(sf, aud.stream_offset, aud.stream_size, aud.channels, aud.sample_rate, 0);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
@ -83,12 +89,11 @@ VGMSTREAM* init_vgmstream_rage_aud(STREAMFILE* sf) {
#ifdef VGM_USE_MPEG
case 0x0100: { /* MPEG (PS3) */
mpeg_custom_config cfg = {0};
if (aud.is_music) {
if (aud.is_streamed) {
goto fail;
}
else {
cfg.chunk_size = aud.block_size;
cfg.chunk_size = aud.block_chunk;
cfg.big_endian = aud.big_endian;
vgmstream->codec_data = init_mpeg_custom(sf, aud.stream_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_STANDARD, &cfg);
@ -101,8 +106,8 @@ VGMSTREAM* init_vgmstream_rage_aud(STREAMFILE* sf) {
case 0x0400: /* PC */
vgmstream->coding_type = coding_IMA_int;
vgmstream->layout_type = aud.is_music ? layout_blocked_rage_aud : layout_none;
vgmstream->full_block_size = aud.block_size;
vgmstream->layout_type = aud.is_streamed ? layout_blocked_rage_aud : layout_none;
vgmstream->full_block_size = aud.block_chunk;
break;
default:
@ -134,35 +139,33 @@ static int parse_aud_header(STREAMFILE* sf, aud_header* aud) {
read_u16 = aud->big_endian ? read_u16be : read_u16le;
uint64_t table_offset = read_u64(0x00, sf);
if (table_offset > 0x10000) /* arbitrary max, typically 0x1c~0x1000 */
if (table_offset > 0x18000) /* typically 0x1c~0x1000, seen 0x17BE8 */
return 0;
/* use bank's stream count to detect */
aud->is_music = (read_u32(0x10, sf) == 0);
aud->is_streamed = (read_u32(0x10, sf) == 0);
if (aud->is_music) {
if (aud->is_streamed) {
off_t block_table_offset, channel_table_offset, channel_info_offset;
/* music header */
block_table_offset = table_offset;
aud->block_count = read_u32(0x08, sf);
aud->block_size = read_u32(0x0c, sf); /* uses padded blocks */
aud->block_chunk = read_u32(0x0c, sf); /* uses padded blocks */
/* 0x10(4): stream count */
channel_table_offset = read_u64(0x14, sf);
/* 0x1c(8): block_table_offset again? */
aud->channel_count = read_u32(0x24, sf);
aud->channels = read_u32(0x24, sf);
/* 0x28(4): unknown entries? */
aud->stream_offset = read_u32(0x2c, sf);
channel_info_offset = channel_table_offset + aud->channel_count * 0x10;
channel_info_offset = channel_table_offset + aud->channels * 0x10;
/* TODO: While not exactly relevant currently without it being supported yet,
* there are two Xbox 360 (XMA) streams, where the block count is off by one.
*
* This size check will fail on the following two files:
* GTA4 - Header says 2 blocks, actually has 3 - EP1_SFX/RP03_ML
* MCLA - Header says 3 blocks, actually has 4 - AUDIO/X360/SFX/AMBIENCE_STREAM/AMB_QUADSHOT_MALL_ADVERT_09
/* block count is off in rare XMA streams:
* GTA4 - Header says 2 blocks, actually has 3 - EP1_SFX/RP03_ML
* MCLA - Header says 3 blocks, actually has 4 - AUDIO/X360/SFX/AMBIENCE_STREAM/AMB_QUADSHOT_MALL_ADVERT_09
*/
if ((aud->block_count * aud->block_size) + aud->stream_offset != get_streamfile_size(sf)) {
uint32_t expected_size = aud->block_count * aud->block_chunk + aud->stream_offset;
if (expected_size != get_streamfile_size(sf) && expected_size + aud->block_chunk != get_streamfile_size(sf)) {
VGM_LOG("RAGE AUD: bad file size\n");
goto fail;
}
@ -231,7 +234,7 @@ static int parse_aud_header(STREAMFILE* sf, aud_header* aud) {
/* 0x20(8): adpcm states offset, 0x38: num states? (reference for seeks?) */
/* rest: unknown data */
aud->channel_count = 1;
aud->channels = 1;
}
return 1;
@ -239,3 +242,86 @@ static int parse_aud_header(STREAMFILE* sf, aud_header* aud) {
fail:
return 0;
}
/* ************************************************************************* */
static VGMSTREAM* build_blocks_vgmstream(STREAMFILE* sf, aud_header* aud, int channel) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* temp_sf = NULL;
int block_channels = 1;
uint32_t substream_size, substream_offset;
/* setup custom IO streamfile that removes RAGE's odd blocks (not perfect but serviceable) */
temp_sf = setup_rage_aud_streamfile(sf, aud->stream_offset, aud->stream_size, aud->block_chunk, aud->channels, channel, aud->codec, aud->big_endian);
if (!temp_sf) goto fail;
substream_offset = 0x00;
substream_size = get_streamfile_size(temp_sf);
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(block_channels, 0);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_RAGE_AUD;
vgmstream->sample_rate = aud->sample_rate;
vgmstream->num_samples = aud->num_samples;
vgmstream->stream_size = aud->stream_size;
vgmstream->stream_size = substream_size;
switch(aud->codec) {
#ifdef VGM_USE_FFMPEG
case 0x0000: { /* XMA1 (X360) */
vgmstream->codec_data = init_ffmpeg_xma1_raw(temp_sf, substream_offset, substream_size, block_channels, aud->sample_rate, 0);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
//TODO
//xma_fix_raw_samples(vgmstream, temp_sf, substream_offset, substream_size, 0, 0,0); /* samples are ok? */
break;
}
#endif
default:
goto fail;
}
if (!vgmstream_open_stream(vgmstream, temp_sf, substream_offset))
goto fail;
close_streamfile(temp_sf);
return vgmstream;
fail:
;VGM_LOG("AUD: can't open decoder\n");
close_vgmstream(vgmstream);
close_streamfile(temp_sf);
return NULL;
}
/* ************************************************************************* */
/* blah blah, see awc.c */
static layered_layout_data* build_layered_rage_aud(STREAMFILE* sf, aud_header* aud) {
int i;
layered_layout_data* data = NULL;
/* init layout */
data = init_layout_layered(aud->channels);
if (!data) goto fail;
/* open each layer subfile */
for (i = 0; i < aud->channels; i++) {
data->layers[i] = build_blocks_vgmstream(sf, aud, i);
if (!data->layers[i]) goto fail;
}
/* setup layered VGMSTREAMs */
if (!setup_layout_layered(data))
goto fail;
return data;
fail:
free_layout_layered(data);
return NULL;
}

View File

@ -0,0 +1,204 @@
#ifndef _RAGE_AUD_STREAMFILE_H_
#define _RAGE_AUD_STREAMFILE_H_
#include "deblock_streamfile.h"
#include "../util/endianness.h"
#include "../util/log.h"
#define RAGE_AUD_MAX_MUSIC_CHANNELS 7 /* max known */
/* ************************************************************************* */
typedef struct {
int start_entry; /* innacurate! */
int entries;
int32_t channel_skip;
int32_t channel_samples;
uint32_t frame_size;
/* derived */
uint32_t chunk_start; /* relative to block offset */
uint32_t chunk_size; /* size of this channel's data (not including padding) */
} rage_aud_block_t;
typedef struct {
int big_endian;
uint8_t codec;
int channels;
uint32_t block_offset;
uint32_t header_size;
rage_aud_block_t blk[RAGE_AUD_MAX_MUSIC_CHANNELS];
} rage_aud_block_info_t;
/* Block format:
* - block header for all channels (needed to find frame start)
* - frames from channel 1
* - ...
* - frames from channel N
* - usually there is padding between channels or blocks
*
* Header format:
* - base header (for all channels)
* 0x00: seek info offset (within block)
* 0x08: seek table offset
* 0x10: seek table offset
* - channel info (per channel)
* 0x00: start entry for that channel?
* may be off by +1/+2?
* 0x04: frames in this channel (may be different between channels)
* 0x08: samples to discard in the beginning of this block (MPEG/XMA2)
* this seems to repeat XMA frames, which decoders don't like, so maybe they just skip whole frames
* 0x0c: samples in channel without discard? (for MPEG/XMA2 can vary between channels)
* (next fields only exists for MPEG)
* 0x10: close to number of frames but varies a bit?
* 0x14: channel chunk size (not counting padding)
* - seek table (entries for all channels)
* 0x00: start?
* 0x04: end?
* - padding until this block's end
*/
static bool read_rage_aud_block(STREAMFILE* sf, rage_aud_block_info_t* bi) {
read_s32_t read_s32 = bi->big_endian ? read_s32be : read_s32le;
uint32_t channel_entry_size, seek_entry_size;
uint32_t offset = bi->block_offset;
int channels = bi->channels;
/* read stupid block crap + derived info at once so hopefully it's a bit easier to understand */
switch(bi->codec) {
case 0x0000: /* XMA1 */
channel_entry_size = 0x10;
seek_entry_size = 0x08;
break;
default:
goto fail;
}
/* base header */
{
offset += 0x18;
}
/* channel info table */
for (int ch = 0; ch < bi->channels; ch++) {
bi->blk[ch].start_entry = read_s32(offset + 0x00, sf);
bi->blk[ch].entries = read_s32(offset + 0x04, sf);
bi->blk[ch].channel_skip = read_s32(offset + 0x08, sf);
bi->blk[ch].channel_samples = read_s32(offset + 0x0c, sf);
/* others: optional */
offset += channel_entry_size;
}
/* seek table */
for (int ch = 0; ch < channels; ch++) {
offset += bi->blk[ch].entries * seek_entry_size;
}
/* derived info */
for (int ch = 0; ch < channels; ch++) {
switch(bi->codec) {
case 0x0000: /* XMA1 */
bi->blk[ch].frame_size = 0x800;
bi->blk[ch].chunk_size = bi->blk[ch].entries * bi->blk[ch].frame_size;
break;
/* in MPEG frames seem to be VBR, so would need channel chunk size */
default:
goto fail;
}
}
/* detect block header size (aligned to 0x800) and adjust offset */
{
/* seek table size seems consistent for all blocks; last one defines less entries yet still writes
* a table as big as prev blocks, repeating old values for unused entries, so final header size is consistent */
if (!bi->header_size)
bi->header_size = offset - bi->block_offset;
offset = bi->block_offset + align_size_to_block(bi->header_size, 0x800);
}
/* set frame starts per channel */
for (int ch = 0; ch < channels; ch++) {
bi->blk[ch].chunk_start = offset - bi->block_offset;
offset += bi->blk[ch].chunk_size;
}
/* beyond this is padding until chunk_start */
return true;
fail:
return false;
}
/* Find data that repeats in the beginning of a new block at the end of last block.
* When a new block starts there is some repeated data + channel_skip (for seeking + encoder delay?).
* Detect it so decoder may ignore it. */
static uint32_t get_block_repeated_size(STREAMFILE* sf, rage_aud_block_info_t* bi, int channel) {
if (bi->blk[channel].channel_skip == 0)
return 0;
if (bi->block_offset >= get_streamfile_size(sf))
return 0;
switch(bi->codec) {
case 0x0000: { /* XMA1 */
/* when data repeats seems to clone the last (super-)frame */
return bi->blk[channel].frame_size;
}
default:
;VGM_LOG("RAGE_AUD: found channel skip in codec %x\n", bi->codec); /* not seen */
return 0;
}
}
/* ************************************************************************* */
static void block_callback(STREAMFILE *sf, deblock_io_data* data) {
int channel = data->cfg.track_number;
rage_aud_block_info_t bi = {0};
bi.big_endian = data->cfg.big_endian;
bi.block_offset = data->physical_offset;
bi.channels = data->cfg.track_count;
bi.codec = data->cfg.track_type;
bi.header_size = data->cfg.config;
if (!read_rage_aud_block(sf, &bi))
return; //???
data->cfg.config = bi.header_size; /* fixed for all blocks but calc'd on first one */
uint32_t repeat_size = get_block_repeated_size(sf, &bi, channel);
data->block_size = data->cfg.chunk_size;
data->skip_size = bi.blk[channel].chunk_start + repeat_size;
data->data_size = bi.blk[channel].chunk_size - repeat_size;
}
/* deblocks RAGE_AUD blocks */
static STREAMFILE* setup_rage_aud_streamfile(STREAMFILE* sf, uint32_t stream_offset, uint32_t stream_size, uint32_t block_size, int channels, int channel, uint8_t codec, int big_endian) {
STREAMFILE* new_sf = NULL;
deblock_config_t cfg = {0};
if (channels > RAGE_AUD_MAX_MUSIC_CHANNELS || channel >= channels)
return NULL;
cfg.track_number = channel;
cfg.track_count = channels;
cfg.stream_start = stream_offset;
cfg.stream_size = stream_size;
cfg.chunk_size = block_size;
cfg.track_type = codec;
cfg.big_endian = big_endian;
//cfg.physical_offset = stream_offset;
//cfg.logical_size = rage_aud_io_size(sf, &cfg); /* force init */
cfg.block_callback = block_callback;
new_sf = open_wrap_streamfile(sf);
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg);
//new_sf = open_buffer_streamfile_f(new_sf, 0);
return new_sf;
}
#endif