cleanup: split ea-eaac formats

This commit is contained in:
bnnm 2023-12-31 01:23:13 +01:00
parent 5f98d88d1c
commit e9ea2af59b
12 changed files with 1273 additions and 1087 deletions

View File

@ -1084,22 +1084,13 @@ different internally (encrypted, different versions, etc) and not always can be
- Angel Studios/Rockstar San Diego STMA header [*STMA*]
- *stma*: `.stm .lstm`
- Codecs: NGC_DSP DVI_IMA_int PCM16BE PCM16LE
- **ea_eaac.c**
- **ea_eaac_standard.c**
- Electronic Arts SNR+SNS header [*EA_SNR_SNS*]
- Electronic Arts SPS header [*EA_SPS*]
- Electronic Arts SNU header [*EA_SNU*]
- *ea_snr_sns*: `.snr`
- *ea_sps*: `.sps`
- *ea_snu*: `.snu`
- *ea_abk_eaac*: `.abk + .ast`
- *ea_sbr*: `.sbr + .sbs`
- *ea_hdr_sth_dat*: `.hdr + .sth .dat .mus .(external)`
- *ea_mpf_mus_eaac*: `.mpf + .(external)`
- *ea_tmx*: `.tmx`
- Subfiles: *gin*
- *ea_sbr_harmony*: `.sbr + .sbs`
- *eaaudiocore_header*: `(base) + .sns`
- Codecs: PCM16_int EA_XAS_V1 MPEG NGC_DSP SPEEX ATRAC9 Opus XMA1 XMA2
- **awc.c**
- Rockstar AWC header [*AWC*]
- *awc*: `.awc`
@ -1152,6 +1143,26 @@ different internally (encrypted, different versions, etc) and not always can be
- Gameloft VXN header [*VXN*]
- *vxn*: `.vxn`
- Codecs: PCM16LE MSADPCM MS_IMA FFmpeg(various)
- **ea_eaac_abk.c**
- Electronic Arts SNR+SNS header [*EA_SNR_SNS*]
- *ea_abk_eaac*: `.abk + .ast`
- **ea_eaac_hdr_sth_dat.c**
- Electronic Arts SNR+SNS header [*EA_SNR_SNS*]
- *ea_hdr_sth_dat*: `.hdr + .sth .dat`
- **ea_eaac_mpf_mus.c**
- Electronic Arts SNR+SNS header [*EA_SNR_SNS*]
- *ea_mpf_mus_eaac*: `.mpf + .(external) .mus`
- **ea_eaac_tmx.c**
- Electronic Arts SNR+SNS header [*EA_SNR_SNS*]
- *ea_tmx*: `.tmx`
- Subfiles: *gin*
- **ea_eaac_sbr.c**
- Electronic Arts SPS header [*EA_SPS*]
- Electronic Arts SNR+SNS header [*EA_SNR_SNS*]
- *ea_sbr*: `.sbr + .sbs`
- **ea_eaac_sbr_harmony.c**
- Electronic Arts SPS header [*EA_SPS*]
- *ea_sbr_harmony*: `.sbr + .sbs`
- **vid1.c**
- Factor 5 VID1 header [*VID1*]
- *vid1*: `.vid .ogg .logg`
@ -1919,6 +1930,10 @@ different internally (encrypted, different versions, etc) and not always can be
- FFmpeg supported format [*FFMPEG*]
- *ffmpeg*: `.(any) .at3`
- Codecs: FFmpeg(various)
- **ea_eaac.c**
- Electronic Arts SPS header [*EA_SPS*]
- *eaaudiocore_header*: `(base) + .sns`
- Codecs: PCM16_int EA_XAS_V1 MPEG NGC_DSP SPEEX ATRAC9 Opus XMA1 XMA2
## Supported extras
Reminder of some extra formats and helper files vgmstream supports. They are described

View File

@ -414,6 +414,13 @@
<ClCompile Include="meta\dsf.c" />
<ClCompile Include="meta\ea_1snh.c" />
<ClCompile Include="meta\ea_eaac.c" />
<ClCompile Include="meta\ea_eaac_abk.c" />
<ClCompile Include="meta\ea_eaac_hdr_sth_dat.c" />
<ClCompile Include="meta\ea_eaac_mpf_mus.c" />
<ClCompile Include="meta\ea_eaac_sbr.c" />
<ClCompile Include="meta\ea_eaac_sbr_harmony.c" />
<ClCompile Include="meta\ea_eaac_standard.c" />
<ClCompile Include="meta\ea_eaac_tmx.c" />
<ClCompile Include="meta\ea_schl.c" />
<ClCompile Include="meta\ea_schl_fixed.c" />
<ClCompile Include="meta\ea_swvr.c" />

View File

@ -1063,6 +1063,27 @@
<ClCompile Include="meta\ea_eaac.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ea_eaac_abk.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ea_eaac_hdr_sth_dat.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ea_eaac_mpf_mus.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ea_eaac_sbr.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ea_eaac_sbr_harmony.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ea_eaac_standard.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ea_eaac_tmx.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\ea_schl.c">
<Filter>meta\Source Files</Filter>
</ClCompile>

File diff suppressed because it is too large Load Diff

197
src/meta/ea_eaac_abk.c Normal file
View File

@ -0,0 +1,197 @@
#include "meta.h"
#include "../util/endianness.h"
static VGMSTREAM* parse_s10a_header(STREAMFILE* sf, off_t offset, uint16_t target_index, off_t ast_offset);
/* EA ABK - ABK header seems to be same as in the old games but the sound table is different and it contains SNR/SNS sounds instead */
VGMSTREAM* init_vgmstream_ea_abk_eaac(STREAMFILE* sf) {
VGMSTREAM* vgmstream;
int is_dupe, total_sounds = 0, target_stream = sf->stream_index;
off_t bnk_offset, modules_table, module_data, player_offset, samples_table, entry_offset, ast_offset;
off_t cfg_num_players_off, cfg_module_data_off, cfg_module_entry_size, cfg_samples_table_off;
uint32_t num_sounds, num_sample_tables;
uint16_t num_modules, bnk_index, bnk_target_index;
uint8_t num_players;
off_t sample_tables[0x400];
int32_t(*read_32bit)(off_t, STREAMFILE*);
int16_t(*read_16bit)(off_t, STREAMFILE*);
/* checks */
if (!is_id32be(0x00, sf, "ABKC"))
return NULL;
if (!check_extensions(sf, "abk"))
return NULL;
/* use table offset to check endianness */
if (guess_endian32(0x1C, sf)) {
read_32bit = read_32bitBE;
read_16bit = read_16bitBE;
} else {
read_32bit = read_32bitLE;
read_16bit = read_16bitLE;
}
if (target_stream == 0) target_stream = 1;
if (target_stream < 0)
goto fail;
num_modules = read_16bit(0x0A, sf);
modules_table = read_32bit(0x1C, sf);
bnk_offset = read_32bit(0x20, sf);
num_sample_tables = 0;
bnk_target_index = 0xFFFF;
ast_offset = 0;
if (!bnk_offset || !is_id32be(bnk_offset, sf, "S10A"))
goto fail;
/* set up some common values */
if (modules_table == 0x5C) {
/* the usual variant */
cfg_num_players_off = 0x24;
cfg_module_data_off = 0x2C;
cfg_module_entry_size = 0x3C;
cfg_samples_table_off = 0x04;
}
else if (modules_table == 0x78) {
/* FIFA 08 has a bunch of extra zeroes all over the place, don't know what's up with that */
cfg_num_players_off = 0x40;
cfg_module_data_off = 0x54;
cfg_module_entry_size = 0x68;
cfg_samples_table_off = 0x0C;
}
else {
goto fail;
}
for (uint32_t i = 0; i < num_modules; i++) {
num_players = read_8bit(modules_table + cfg_num_players_off, sf);
module_data = read_32bit(modules_table + cfg_module_data_off, sf);
if (num_players == 0xff) goto fail; /* EOF read */
for (uint32_t j = 0; j < num_players; j++) {
player_offset = read_32bit(modules_table + cfg_module_entry_size + 0x04 * j, sf);
samples_table = read_32bit(module_data + player_offset + cfg_samples_table_off, sf);
/* multiple players may point at the same sound table */
is_dupe = 0;
for (uint32_t k = 0; k < num_sample_tables; k++) {
if (samples_table == sample_tables[k]) {
is_dupe = 1;
break;
}
}
if (is_dupe)
continue;
sample_tables[num_sample_tables++] = samples_table;
num_sounds = read_32bit(samples_table, sf);
if (num_sounds == 0xffffffff) goto fail; /* EOF read */
for (uint32_t k = 0; k < num_sounds; k++) {
/* 0x00: sound index */
/* 0x02: priority */
/* 0x03: azimuth */
/* 0x08: streamed data offset */
entry_offset = samples_table + 0x04 + 0x0C * k;
bnk_index = read_16bit(entry_offset + 0x00, sf);
/* some of these are dummies */
if (bnk_index == 0xFFFF)
continue;
total_sounds++;
if (target_stream == total_sounds) {
bnk_target_index = bnk_index;
ast_offset = read_32bit(entry_offset + 0x08, sf);
}
}
}
/* skip class controllers */
num_players += read_8bit(modules_table + cfg_num_players_off + 0x03, sf);
modules_table += cfg_module_entry_size + num_players * 0x04;
}
if (bnk_target_index == 0xFFFF || ast_offset == 0)
goto fail;
vgmstream = parse_s10a_header(sf, bnk_offset, bnk_target_index, ast_offset);
if (!vgmstream) goto fail;
vgmstream->num_streams = total_sounds;
return vgmstream;
fail:
return NULL;
}
/* EA S10A header - seen inside new ABK files. Putting it here in case it's encountered stand-alone. */
static VGMSTREAM* parse_s10a_header(STREAMFILE* sf, off_t offset, uint16_t target_index, off_t sns_offset) {
VGMSTREAM* vgmstream;
STREAMFILE *sf_ast = NULL;
uint32_t num_sounds;
off_t snr_offset;
eaac_meta_t info = {0};
/* header is always big endian */
/* 0x00: header magic */
/* 0x04: version */
/* 0x05: padding */
/* 0x06: serial number */
/* 0x08: number of files */
/* 0x0C: offsets table */
if (!is_id32be(offset + 0x00, sf, "S10A"))
return NULL;
num_sounds = read_32bitBE(offset + 0x08, sf);
if (num_sounds == 0 || target_index >= num_sounds)
return NULL;
snr_offset = offset + read_32bitBE(offset + 0x0C + 0x04 * target_index, sf);
if (sns_offset == 0xFFFFFFFF) {
/* RAM asset */
//;VGM_LOG("EA S10A: RAM at snr=%lx", snr_offset);
info.sf_head = sf;
info.head_offset = snr_offset;
info.body_offset = 0x00;
info.type = meta_EA_SNR_SNS;
vgmstream = load_vgmstream_ea_eaac(&info);
if (!vgmstream) goto fail;
}
else {
/* streamed asset */
sf_ast = open_streamfile_by_ext(sf, "ast");
if (!sf_ast) {
vgm_logi("EA ABK: .ast file not found (find and put together)\n");
goto fail;
}
if (!is_id32be(0x00, sf_ast, "S10S"))
goto fail;
//;VGM_LOG("EA S10A: stream at snr=%lx, sns=%lx\n", snr_offset, sns_offset);
info.sf_head = sf;
info.sf_body = sf_ast;
info.head_offset = snr_offset;
info.body_offset = sns_offset;
info.type = meta_EA_SNR_SNS;
vgmstream = load_vgmstream_ea_eaac(&info);
if (!vgmstream) goto fail;
close_streamfile(sf_ast);
}
return vgmstream;
fail:
close_streamfile(sf_ast);
return NULL;
}

View File

@ -0,0 +1,160 @@
#include "meta.h"
#include "../util/endianness.h"
#define EAAC_BLOCKID0_DATA 0x00
#define EAAC_BLOCKID0_END 0x80 /* maybe meant to be a bitflag? */
/* EA HDR/STH/DAT - seen in older 7th gen games, used for storing speech */
VGMSTREAM* init_vgmstream_ea_hdr_sth_dat(STREAMFILE* sf) {
VGMSTREAM* vgmstream;
STREAMFILE *sf_dat = NULL, *sf_sth = NULL;
int target_stream = sf->stream_index;
uint32_t snr_offset, sns_offset, block_size;
uint16_t sth_offset, sth_offset2;
uint8_t num_params, num_sounds, block_id;
size_t dat_size;
uint32_t(*read_u32)(off_t, STREAMFILE*);
eaac_meta_t info = {0};
/* 0x00: ID */
/* 0x02: number of parameters */
/* 0x03: number of samples */
/* 0x04: speaker ID (used for different police voices in NFS games) */
/* 0x08: sample repeat (alt number of samples?) */
/* 0x09: block size (always zero?) */
/* 0x0a: number of blocks (related to size?) */
/* 0x0c: number of sub-banks (always zero?) */
/* 0x0e: padding */
/* 0x10: table start */
if (!check_extensions(sf, "hdr"))
return NULL;
if (read_u8(0x09, sf) != 0)
return NULL;
if (read_u32be(0x0c, sf) != 0)
return NULL;
/* first offset is always zero */
if (read_u16be(0x10, sf) != 0)
return NULL;
sf_sth = open_streamfile_by_ext(sf, "sth");
if (!sf_sth) goto fail;
sf_dat = open_streamfile_by_ext(sf, "dat");
if (!sf_dat) goto fail;
/* STH always starts with the first offset of zero */
sns_offset = read_u32be(0x00, sf_sth);
if (sns_offset != 0)
goto fail;
/* check if DAT starts with a correct SNS block */
block_id = read_u8(0x00, sf_dat);
if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END)
goto fail;
num_params = read_u8(0x02, sf) & 0x7F;
num_sounds = read_u8(0x03, sf);
if (read_u8(0x08, sf) > num_sounds)
goto fail;
if (target_stream == 0) target_stream = 1;
if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds)
goto fail;
/* offsets in HDR are always big endian */
sth_offset = read_u16be(0x10 + (0x02 + num_params) * (target_stream - 1), sf);
#if 0
snr_offset = sth_offset + 0x04;
sns_offset = read_u32(sth_offset + 0x00, sf_sth);
#else
/* overly intricate way to detect byte endianness because of the simplicity of HDR format */
dat_size = get_streamfile_size(sf_dat);
snr_offset = 0;
sns_offset = 0;
if (num_sounds == 1) {
/* always 0 */
snr_offset = sth_offset + 0x04;
sns_offset = 0x00;
}
else {
/* find the first sound size and match it up with the second sound offset to detect endianness */
while (1) {
if (sns_offset >= dat_size)
goto fail;
block_id = read_u8(sns_offset, sf_dat);
block_size = read_u32be(sns_offset, sf_dat) & 0x00FFFFFF;
if (block_size == 0)
goto fail;
if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END)
goto fail;
sns_offset += block_size;
if (block_id == EAAC_BLOCKID0_END)
break;
}
sns_offset = align_size_to_block(sns_offset, 0x40);
sth_offset2 = read_u16be(0x10 + (0x02 + num_params) * 1, sf);
if (sns_offset == read_u32be(sth_offset2, sf_sth)) {
read_u32 = read_u32be;
}
else if (sns_offset == read_u32le(sth_offset2, sf_sth)) {
read_u32 = read_u32le;
}
else {
goto fail;
}
snr_offset = sth_offset + 0x04;
sns_offset = read_u32(sth_offset + 0x00, sf_sth);
}
#endif
block_id = read_u8(sns_offset, sf_dat);
if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END)
goto fail;
info.sf_head = sf_sth;
info.sf_body = sf_dat;
info.head_offset = snr_offset;
info.body_offset = sns_offset;
info.type = meta_EA_SNR_SNS;
vgmstream = load_vgmstream_ea_eaac(&info);
if (!vgmstream) goto fail;
if (num_params != 0) {
uint8_t val;
char buf[8];
int i;
for (i = 0; i < num_params; i++) {
val = read_u8(0x10 + (0x02 + num_params) * (target_stream - 1) + 0x02 + i, sf);
snprintf(buf, sizeof(buf), "%u", val);
concatn(STREAM_NAME_SIZE, vgmstream->stream_name, buf);
if (i != num_params - 1)
concatn(STREAM_NAME_SIZE, vgmstream->stream_name, ", ");
}
}
vgmstream->num_streams = num_sounds;
close_streamfile(sf_sth);
close_streamfile(sf_dat);
return vgmstream;
fail:
close_streamfile(sf_sth);
close_streamfile(sf_dat);
return NULL;
}

343
src/meta/ea_eaac_mpf_mus.c Normal file
View File

@ -0,0 +1,343 @@
#include "meta.h"
#include "../util/endianness.h"
#include "../layout/layout.h"
#include "../util/companion_files.h"
static STREAMFILE *open_mapfile_pair(STREAMFILE* sf, int track /*, int num_tracks*/);
/* EA MPF/MUS combo - used in older 7th gen games for storing interactive music */
VGMSTREAM* init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE *sf_mus = NULL;
uint32_t num_tracks, track_start, track_checksum = 0, mus_sounds, mus_stream = 0, bnk_index = 0, bnk_sound_index = 0,
tracks_table, samples_table, eof_offset, table_offset, entry_offset = 0, snr_offset, sns_offset;
uint16_t num_subbanks, index, sub_index;
uint8_t version, sub_version;
segmented_layout_data* data_s = NULL;
int i;
int target_stream = sf->stream_index, total_streams, is_ram = 0;
uint32_t(*read_u32)(off_t, STREAMFILE *);
uint16_t(*read_u16)(off_t, STREAMFILE *);
/* checks */
if (is_id32be(0x00, sf, "PFDx")) {
read_u32 = read_u32be;
read_u16 = read_u16be;
}
else if (is_id32le(0x00, sf, "PFDx")) {
read_u32 = read_u32le;
read_u16 = read_u16le;
}
else {
return NULL;
}
if (!check_extensions(sf, "mpf"))
return NULL;
version = read_u8(0x04, sf);
sub_version = read_u8(0x05, sf);
if (version != 5 || sub_version < 2 || sub_version > 3) goto fail;
num_tracks = read_u8(0x0d, sf);
tracks_table = read_u32(0x2c, sf);
samples_table = read_u32(0x34, sf);
eof_offset = read_u32(0x38, sf);
total_streams = (eof_offset - samples_table) / 0x08;
if (target_stream == 0) target_stream = 1;
if (target_stream < 0 || total_streams == 0 || target_stream > total_streams)
goto fail;
for (i = num_tracks - 1; i >= 0; i--) {
entry_offset = read_u32(tracks_table + i * 0x04, sf) * 0x04;
track_start = read_u32(entry_offset + 0x00, sf);
if (track_start == 0 && i != 0)
continue; /* empty track */
if (track_start <= target_stream - 1) {
num_subbanks = read_u16(entry_offset + 0x04, sf);
track_checksum = read_u32be(entry_offset + 0x08, sf);
is_ram = (num_subbanks != 0);
mus_stream = target_stream - 1 - track_start;
break;
}
}
/* open MUS file that matches this track */
sf_mus = open_mapfile_pair(sf, i);//, num_tracks
if (!sf_mus) goto fail;
/* sample offsets table is still there but it just holds SNS offsets, we only need it for RAM sound indexes */
/* 0x00 - offset/index, 0x04 - duration (in milliseconds) */
sns_offset = read_u32(samples_table + (target_stream - 1) * 0x08 + 0x00, sf);
if (is_ram) {
bnk_sound_index = (sns_offset & 0x0000FFFF);
bnk_index = (sns_offset & 0xFFFF0000) >> 16;
if (bnk_index != 0) {
/* HACK: open proper .mus now since open_mapfile_pair doesn't let us adjust the name */
char filename[PATH_LIMIT], basename[PATH_LIMIT], ext[32];
int basename_len;
STREAMFILE* sf_temp;
get_streamfile_basename(sf_mus, basename, PATH_LIMIT);
basename_len = strlen(basename);
get_streamfile_ext(sf_mus, ext, sizeof(ext));
/* strip off 0 at the end */
basename[basename_len - 1] = '\0';
/* append bank index to the name */
snprintf(filename, PATH_LIMIT, "%s%u.%s", basename, bnk_index, ext);
sf_temp = open_streamfile_by_filename(sf_mus, filename);
if (!sf_temp) goto fail;
close_streamfile(sf_mus);
sf_mus = sf_temp;
}
track_checksum = read_u32be(entry_offset + 0x14 + bnk_index * 0x10, sf);
if (track_checksum && read_u32be(0x00, sf_mus) != track_checksum)
goto fail;
}
else {
if (track_checksum && read_u32be(0x00, sf_mus) != track_checksum)
goto fail;
}
/* MUS file has a header, however */
if (sub_version == 2) {
if (read_u32(0x04, sf_mus) != 0x00)
goto fail;
/*
* 0x00: index
* 0x02: sub-index
* 0x04: SNR offset
* 0x08: SNS offset (contains garbage for RAM sounds)
*/
table_offset = 0x08;
if (is_ram) {
int ram_segments;
/* find number of parts for this node */
for (i = 0; ; i++) {
entry_offset = table_offset + (bnk_sound_index + i) * 0x0c;
index = read_u16(entry_offset + 0x00, sf_mus);
sub_index = read_u16(entry_offset + 0x02, sf_mus);
if (index == 0xffff) /* EOF check */
goto fail;
entry_offset += 0x0c;
if (read_u16(entry_offset + 0x00, sf_mus) != index ||
read_u16(entry_offset + 0x02, sf_mus) != sub_index + 1)
break;
}
ram_segments = i + 1;
/* init layout */
data_s = init_layout_segmented(ram_segments);
if (!data_s) goto fail;
for (i = 0; i < ram_segments; i++) {
eaac_meta_t info = {0};
entry_offset = table_offset + (bnk_sound_index + i) * 0x0c;
snr_offset = read_u32(entry_offset + 0x04, sf_mus);
info.sf_head = sf_mus;
info.head_offset = snr_offset;
info.body_offset = 0x00;
info.type = meta_EA_SNR_SNS;
data_s->segments[i] = load_vgmstream_ea_eaac(&info);
if (!data_s->segments[i]) goto fail;
}
/* setup segmented VGMSTREAMs */
if (!setup_layout_segmented(data_s))
goto fail;
vgmstream = allocate_segmented_vgmstream(data_s, 0, 0, 0);
}
else {
eaac_meta_t info = {0};
entry_offset = table_offset + mus_stream * 0x0c;
snr_offset = read_u32(entry_offset + 0x04, sf_mus);
sns_offset = read_u32(entry_offset + 0x08, sf_mus);
info.sf_head = sf_mus;
info.sf_body = sf_mus;
info.head_offset = snr_offset;
info.body_offset = sns_offset;
info.type = meta_EA_SNR_SNS;
vgmstream = load_vgmstream_ea_eaac(&info);
}
}
else if (sub_version == 3) {
eaac_meta_t info = {0};
/* number of samples is always little endian */
mus_sounds = read_u32le(0x04, sf_mus);
if (mus_stream >= mus_sounds)
goto fail;
if (is_ram) {
/* not seen so far */
VGM_LOG("EA EAAC MUS: found RAM track in MPF v5.3.\n");
goto fail;
}
/*
* 0x00: checksum
* 0x04: index
* 0x06: sub-index
* 0x08: SNR offset
* 0x0c: SNS offset
* 0x10: SNR size
* 0x14: SNS size
* 0x18: zero
*/
table_offset = 0x28;
entry_offset = table_offset + mus_stream * 0x1c;
snr_offset = read_u32(entry_offset + 0x08, sf_mus) * 0x10;
sns_offset = read_u32(entry_offset + 0x0c, sf_mus) * 0x80;
info.sf_head = sf_mus;
info.sf_body = sf_mus;
info.head_offset = snr_offset;
info.body_offset = sns_offset;
info.type = meta_EA_SNR_SNS;
vgmstream = load_vgmstream_ea_eaac(&info);
}
else {
goto fail;
}
if (!vgmstream)
goto fail;
vgmstream->num_streams = total_streams;
get_streamfile_filename(sf_mus, vgmstream->stream_name, STREAM_NAME_SIZE);
close_streamfile(sf_mus);
return vgmstream;
fail:
close_streamfile(sf_mus);
free_layout_segmented(data_s);
return NULL;
}
//TODO remove a few lesser ones + use .txtm
/* open map/mpf+mus pairs that aren't exact pairs, since EA's games can load any combo */
static STREAMFILE *open_mapfile_pair(STREAMFILE* sf, int track /*, int num_tracks*/) {
static const char *const mapfile_pairs[][2] = {
/* standard cases, replace map part with mus part (from the end to preserve prefixes) */
{"game.mpf", "Game_Stream.mus"}, /* Skate 1/2/3 */
{"ipod.mpf", "Ipod_Stream.mus"},
{"world.mpf", "World_Stream.mus"},
{"FreSkate.mpf", "track.mus,ram.mus"}, /* Skate It */
{"nsf_sing.mpf", "track_main.mus"}, /* Need for Speed: Nitro */
{"nsf_wii.mpf", "Track.mus"},
{"ssx_fe.mpf", "stream_1.mus,stream_2.mus"}, /* SSX 2012 */
{"ssxdd.mpf", "main_trk.mus,"
"trick_alaska0.mus,"
"trick_rockies0.mus,"
"trick_pata0.mus,"
"trick_ant0.mus,"
"trick_killi0.mus,"
"trick_cyb0.mus,"
"trick_hima0.mus,"
"trick_nz0.mus,"
"trick_alps0.mus,"
"trick_lhotse0.mus"}
};
STREAMFILE *sf_mus = NULL;
char file_name[PATH_LIMIT];
int pair_count = (sizeof(mapfile_pairs) / sizeof(mapfile_pairs[0]));
int i, j;
size_t file_len, map_len;
/* try parsing TXTM if present */
sf_mus = read_filemap_file(sf, track);
if (sf_mus) return sf_mus;
/* if loading the first track, try opening MUS with the same name first (most common scenario) */
if (track == 0) {
sf_mus = open_streamfile_by_ext(sf, "mus");
if (sf_mus) return sf_mus;
}
get_streamfile_filename(sf, file_name, PATH_LIMIT);
file_len = strlen(file_name);
for (i = 0; i < pair_count; i++) {
const char *map_name = mapfile_pairs[i][0];
const char *mus_name = mapfile_pairs[i][1];
char buf[PATH_LIMIT] = { 0 };
char *pch;
int use_mask = 0;
map_len = strlen(map_name);
/* replace map_name with expected mus_name */
if (file_len < map_len)
continue;
if (map_name[0] == '*') {
use_mask = 1;
map_name++;
map_len--;
if (strncmp(file_name + (file_len - map_len), map_name, map_len) != 0)
continue;
} else {
if (strcmp(file_name, map_name) != 0)
continue;
}
strncpy(buf, mus_name, PATH_LIMIT - 1);
pch = strtok(buf, ","); //TODO: not thread safe in std C
for (j = 0; j < track && pch; j++) {
pch = strtok(NULL, ",");
}
if (!pch) continue; /* invalid track */
if (use_mask) {
file_name[file_len - map_len] = '\0';
strncat(file_name, pch + 1, PATH_LIMIT - 1);
} else {
strncpy(file_name, pch, PATH_LIMIT - 1);
}
sf_mus = open_streamfile_by_filename(sf, file_name);
if (sf_mus) return sf_mus;
get_streamfile_filename(sf, file_name, PATH_LIMIT); /* reset for next loop */
}
/* hack when when multiple maps point to the same mus, uses name before "+"
* ex. ZZZTR00A.TRJ+ZTR00PGR.MAP or ZZZTR00A.TRJ+ZTR00R0A.MAP both point to ZZZTR00A.TRJ */
{
char *mod_name = strchr(file_name, '+');
if (mod_name) {
mod_name[0] = '\0';
sf_mus = open_streamfile_by_filename(sf, file_name);
if (sf_mus) return sf_mus;
}
}
vgm_logi("EA MPF: .mus file not found (find and put together)\n");
return NULL;
}

115
src/meta/ea_eaac_sbr.c Normal file
View File

@ -0,0 +1,115 @@
#include "meta.h"
#include "../util/endianness.h"
#define EAAC_BLOCKID1_HEADER 0x48 /* 'H' */
/* EA SBR/SBS - used in older 7th gen games for storing SFX */
VGMSTREAM* init_vgmstream_ea_sbr(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE *sf_sbs = NULL;
uint32_t num_sounds, sound_id, type_desc, num_items, item_type,
table_offset, types_offset, entry_offset, items_offset, data_offset, snr_offset, sns_offset;
int target_stream = sf->stream_index;
eaac_meta_t info = {0};
/* checks */
if (!is_id32be(0x00, sf, "SBKR"))
return NULL;
if (!check_extensions(sf, "sbr"))
return NULL;
/* SBR files are always big endian */
num_sounds = read_u32be(0x1c, sf);
table_offset = read_u32be(0x24, sf);
types_offset = read_u32be(0x28, sf);
if (target_stream == 0) target_stream = 1;
if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds)
goto fail;
entry_offset = table_offset + 0x0a * (target_stream - 1);
sound_id = read_u32be(entry_offset + 0x00, sf);
num_items = read_u16be(entry_offset + 0x04, sf);
items_offset = read_u32be(entry_offset + 0x06, sf);
snr_offset = 0;
sns_offset = 0;
for (uint32_t i = 0; i < num_items; i++) {
entry_offset = items_offset + 0x06 * i;
item_type = read_u16be(entry_offset + 0x00, sf);
data_offset = read_u32be(entry_offset + 0x02, sf);
type_desc = read_u32be(types_offset + 0x06 * item_type, sf);
switch (type_desc) {
case 0x534E5231: /* "SNR1" */
snr_offset = data_offset;
break;
case 0x534E5331: /* "SNS1" */
sns_offset = read_u32be(data_offset, sf);
break;
default:
break;
}
}
if (snr_offset == 0 && sns_offset == 0)
goto fail;
if (sns_offset == 0) {
/* RAM asset */
info.sf_head = sf;
info.head_offset = snr_offset;
info.body_offset = 0x00;
info.type = (read_u8(snr_offset, sf) == EAAC_BLOCKID1_HEADER) ? meta_EA_SPS : meta_EA_SNR_SNS;;
vgmstream = load_vgmstream_ea_eaac(&info);
if (!vgmstream) goto fail;
}
else {
/* streamed asset */
sf_sbs = open_streamfile_by_ext(sf, "sbs");
if (!sf_sbs) goto fail;
if (!is_id32be(0x00, sf_sbs, "SBKS"))
goto fail;
if (read_u8(sns_offset, sf_sbs) == EAAC_BLOCKID1_HEADER) {
/* SPS */
info.sf_head = sf_sbs;
info.head_offset = sns_offset;
info.body_offset = 0x00;
info.type = meta_EA_SPS;
vgmstream = load_vgmstream_ea_eaac(&info);
if (!vgmstream) goto fail;
}
else {
/* SNR/SNS */
if (snr_offset == 0)
goto fail;
info.sf_head = sf;
info.sf_body = sf_sbs;
info.head_offset = snr_offset;
info.body_offset = sns_offset;
info.type = meta_EA_SNR_SNS;
vgmstream = load_vgmstream_ea_eaac(&info);
if (!vgmstream) goto fail;
}
}
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%08x", sound_id);
vgmstream->num_streams = num_sounds;
close_streamfile(sf_sbs);
return vgmstream;
fail:
close_streamfile(sf_sbs);
return NULL;
}

View File

@ -0,0 +1,243 @@
#include "meta.h"
#include "../layout/layout.h"
#include "../coding/coding.h"
#include "../util/endianness.h"
/* EA Harmony Sample Bank - used in 8th gen EA Sports games */
VGMSTREAM* init_vgmstream_ea_sbr_harmony(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE *sf_sbs = NULL, *sf_data = NULL;
uint64_t base_offset, sound_offset, offset, prev_offset;
uint32_t dset_id, dset_offset, num_values, num_fields, field_id,
data_offset, table_offset, set_sounds, sound_table_offset;
int16_t flag;
uint16_t num_dsets;
uint8_t set_type, offset_size;
char sound_name[STREAM_NAME_SIZE];
int target_stream = sf->stream_index, total_sounds, local_target, is_streamed = 0;
int i, j;
uint64_t(*read_u64)(off_t, STREAMFILE *);
uint32_t(*read_u32)(off_t, STREAMFILE*);
uint16_t(*read_u16)(off_t, STREAMFILE*);
eaac_meta_t info = {0};
/* checks */
if (is_id32be(0x00, sf, "SBle")) {
read_u64 = read_u64le;
read_u32 = read_u32le;
read_u16 = read_u16le;
}
else if (is_id32be(0x00, sf, "SBbe")) {
read_u64 = read_u64be;
read_u32 = read_u32be;
read_u16 = read_u16be;
}
else {
return NULL;
}
if (!check_extensions(sf, "sbr"))
return NULL;
num_dsets = read_u16(0x0a, sf);
table_offset = read_u32(0x18, sf);
data_offset = read_u32(0x20, sf);
if (target_stream == 0) target_stream = 1;
if (target_stream < 0)
goto fail;
total_sounds = 0;
sound_offset = 0;
/* the bank is split into DSET sections each of which references one or multiple sounds */
/* each set can contain RAM sounds (stored in SBR in data section) or streamed sounds (stored separately in SBS file) */
for (i = 0; i < num_dsets; i++) {
dset_offset = read_u32(table_offset + 0x08 * i, sf);
if (read_u32(dset_offset, sf) != get_id32be("DSET"))
goto fail;
dset_id = read_u32(dset_offset + 0x08, sf);
num_values = read_u32(dset_offset + 0x38, sf);
num_fields = read_u32(dset_offset + 0x3c, sf);
local_target = target_stream - total_sounds - 1;
dset_offset += 0x48;
/* find RAM or OFF field */
for (j = 0; j < num_fields; j++) {
field_id = read_u32(dset_offset, sf);
if (field_id == get_id32be(".RAM") ||
field_id == get_id32be(".OFF")) {
break;
}
dset_offset += 0x18;
}
if (j == num_fields)
goto fail;
/* different set types store offsets differently */
set_type = read_u8(dset_offset + 0x05, sf);
/* data sets often contain duplicate offets, need to filter them out however we can */
/* offsets are stored in ascending order which makes things easier */
if (set_type == 0x00) {
set_sounds = 1;
total_sounds += set_sounds;
if (local_target < 0 || local_target > 0)
continue;
sound_offset = read_u64(dset_offset + 0x08, sf);
}
else if (set_type == 0x01) {
flag = (int16_t)read_u16(dset_offset + 0x06, sf);
base_offset = read_u64(dset_offset + 0x08, sf);
set_sounds = num_values;
total_sounds += set_sounds;
if (local_target < 0 || local_target >= set_sounds)
continue;
sound_offset = base_offset + flag * local_target;
}
else if (set_type == 0x02) {
flag = (read_u16(dset_offset + 0x06, sf) >> 0) & 0xFF;
offset_size = (read_u16(dset_offset + 0x06, sf) >> 8) & 0xFF;
base_offset = read_u64(dset_offset + 0x08, sf);
sound_table_offset = read_u32(dset_offset + 0x10, sf);
set_sounds = 0;
prev_offset = UINT64_MAX;
for (j = 0; j < num_values; j++) {
if (offset_size == 0x01) {
offset = read_u8(sound_table_offset + 0x01 * j, sf);
}
else if (offset_size == 0x02) {
offset = read_u16(sound_table_offset + 0x02 * j, sf);
}
else if (offset_size == 0x04) {
offset = read_u32(sound_table_offset + 0x04 * j, sf);
}
else {
goto fail;
}
offset <<= flag;
offset += base_offset;
if (offset != prev_offset) {
if (set_sounds == local_target)
sound_offset = offset;
set_sounds++;
}
prev_offset = offset;
}
total_sounds += set_sounds;
if (local_target < 0 || local_target >= set_sounds)
continue;
}
else if (set_type == 0x03) {
offset_size = (read_u16(dset_offset + 0x06, sf) >> 8) & 0xFF;
set_sounds = read_u64(dset_offset + 0x08, sf);
sound_table_offset = read_u32(dset_offset + 0x10, sf);
total_sounds += set_sounds;
if (local_target < 0 || local_target >= set_sounds)
continue;
if (offset_size == 0x01) {
sound_offset = read_u8(sound_table_offset + 0x01 * local_target, sf);
}
else if (offset_size == 0x02) {
sound_offset = read_u16(sound_table_offset + 0x02 * local_target, sf);
}
else if (offset_size == 0x04) {
sound_offset = read_u32(sound_table_offset + 0x04 * local_target, sf);
}
else {
goto fail;
}
}
else if (set_type == 0x04) {
sound_table_offset = read_u32(dset_offset + 0x10, sf);
set_sounds = 0;
prev_offset = UINT64_MAX;
for (j = 0; j < num_values; j++) {
offset = read_u64(sound_table_offset + 0x08 * j, sf);
if (sound_offset != prev_offset) {
if (set_sounds == local_target)
sound_offset = offset;
set_sounds++;
}
prev_offset = offset;
}
total_sounds += set_sounds;
if (local_target < 0 || local_target >= set_sounds)
continue;
}
else {
goto fail;
}
snprintf(sound_name, STREAM_NAME_SIZE, "DSET %08x/%04d", dset_id, local_target);
if (field_id == get_id32be(".RAM")) {
is_streamed = 0;
}
else if (field_id == get_id32be(".OFF")) {
is_streamed = 1;
}
}
if (sound_offset == 0)
goto fail;
if (!is_streamed) {
/* RAM asset */
if (!is_id32be(data_offset, sf, "data") &&
!is_id32be(data_offset, sf, "DATA"))
goto fail;
sf_data = sf;
sound_offset += data_offset;
}
else {
/* streamed asset */
sf_sbs = open_streamfile_by_ext(sf, "sbs");
if (!sf_sbs) goto fail;
if (!is_id32be(0x00, sf_sbs, "data") &&
!is_id32be(0x00, sf_sbs, "DATA"))
goto fail;
sf_data = sf_sbs;
if (is_id32be(sound_offset, sf_data, "slot")) {
/* skip "slot" section */
sound_offset += 0x30;
}
}
info.sf_head = sf_data;
info.head_offset = sound_offset;
info.body_offset = 0x00;
info.type = meta_EA_SPS;
vgmstream = load_vgmstream_ea_eaac(&info);
if (!vgmstream) goto fail;
vgmstream->num_streams = total_sounds;
strncpy(vgmstream->stream_name, sound_name, STREAM_NAME_SIZE);
close_streamfile(sf_sbs);
return vgmstream;
fail:
close_streamfile(sf_sbs);
return NULL;
}

View File

@ -0,0 +1,63 @@
#include "meta.h"
#include "../util/endianness.h"
/* .SNR+SNS - from EA latest games (~2005-2010), v0 header */
VGMSTREAM* init_vgmstream_ea_snr_sns(STREAMFILE* sf) {
eaac_meta_t info = {0};
/* checks */
if (!check_extensions(sf,"snr"))
return NULL;
info.sf_head = sf;
info.head_offset = 0x00;
info.body_offset = 0x00;
info.type = meta_EA_SNR_SNS;
info.standalone = true;
return load_vgmstream_ea_eaac(&info);
}
/* .SPS - from EA latest games (~2010~present), v1 header */
VGMSTREAM* init_vgmstream_ea_sps(STREAMFILE* sf) {
eaac_meta_t info = {0};
/* checks */
if (!check_extensions(sf,"sps"))
return NULL;
info.sf_head = sf;
info.head_offset = 0x00;
info.type = meta_EA_SPS;
info.standalone = true;
return load_vgmstream_ea_eaac(&info);
}
/* .SNU - from EA Redwood Shores/Visceral games (Dead Space, Dante's Inferno, The Godfather 2), v0 header */
VGMSTREAM* init_vgmstream_ea_snu(STREAMFILE* sf) {
eaac_meta_t info = {0};
read_u32_t read_u32 = NULL;
/* checks */
if (!check_extensions(sf,"snu"))
return NULL;
/* EA SNU header (BE/LE depending on platform) */
/* 0x00(1): related to sample rate? (03=48000)
* 0x01(1): flags/count? (when set has extra block data before start_offset)
* 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 */
read_u32 = guess_read_u32(0x08,sf);
info.sf_head = sf;
info.sf_body = sf;
info.head_offset = 0x10; /* SNR header */
info.body_offset = read_u32(0x08,sf); /* SNS blocks */
info.type = meta_EA_SNU;
return load_vgmstream_ea_eaac(&info);
}

71
src/meta/ea_eaac_tmx.c Normal file
View File

@ -0,0 +1,71 @@
#include "meta.h"
#include "../util/endianness.h"
#include "../coding/coding.h"
/* EA TMX - used for engine sounds in NFS games (2007-2011) */
VGMSTREAM* init_vgmstream_ea_tmx(STREAMFILE* sf) {
uint32_t num_sounds, sound_type, table_offset, data_offset, entry_offset, sound_offset;
VGMSTREAM* vgmstream = NULL;
STREAMFILE* temp_sf = NULL;
int target_stream = sf->stream_index;
uint32_t(*read_u32)(off_t, STREAMFILE *);
/* checks */
if (is_id32be(0x0c, sf, "0001")) {
read_u32 = read_u32be;
}
else if (is_id32le(0x0c, sf, "1000")) {
read_u32 = read_u32le;
}
else {
return NULL;
}
if (!check_extensions(sf, "tmx"))
return NULL;
num_sounds = read_u32(0x20, sf);
table_offset = read_u32(0x58, sf);
data_offset = read_u32(0x5c, sf);
if (target_stream == 0) target_stream = 1;
if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds)
goto fail;
entry_offset = table_offset + (target_stream - 1) * 0x24;
sound_type = read_u32(entry_offset + 0x00, sf);
sound_offset = read_u32(entry_offset + 0x08, sf) + data_offset;
switch (sound_type) {
case 0x47494E20: /* "GIN " */
temp_sf = setup_subfile_streamfile(sf, sound_offset, get_streamfile_size(sf) - sound_offset, "gin");
if (!temp_sf) goto fail;
vgmstream = init_vgmstream_gin(temp_sf);
if (!vgmstream) goto fail;
close_streamfile(temp_sf);
break;
case 0x534E5220: { /* "SNR " */
eaac_meta_t info = {0};
info.sf_head = sf;
info.head_offset = sound_offset;
info.body_offset = 0x00;
info.type = meta_EA_SNR_SNS;
vgmstream = load_vgmstream_ea_eaac(&info);
if (!vgmstream) goto fail;
break;
}
default:
goto fail;
}
vgmstream->num_streams = num_sounds;
return vgmstream;
fail:
close_streamfile(temp_sf);
return NULL;
}

View File

@ -650,6 +650,17 @@ VGMSTREAM * init_vgmstream_ea_tmx(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_ea_sbr(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_ea_sbr_harmony(STREAMFILE * streamFile);
typedef struct {
STREAMFILE* sf_head;
STREAMFILE* sf_body;
uint32_t head_offset;
uint32_t body_offset;
meta_t type;
bool standalone;
} eaac_meta_t;
VGMSTREAM* load_vgmstream_ea_eaac(eaac_meta_t* info);
VGMSTREAM* init_vgmstream_vid1(STREAMFILE* sf);
VGMSTREAM * init_vgmstream_flx(STREAMFILE * streamFile);