2024-07-02 23:33:38 +02:00
|
|
|
#include "meta.h"
|
|
|
|
#include "../coding/coding.h"
|
|
|
|
#include "../util/endianness.h"
|
|
|
|
|
|
|
|
#define EA_BLOCKID_HEADER 0x5343486C /* "SCHl" */
|
|
|
|
|
|
|
|
#define EA_BLOCKID_LOC_HEADER 0x53480000 /* "SH" */
|
|
|
|
|
|
|
|
#define EA_BLOCKID_LOC_EN 0x0000454E /* English */
|
|
|
|
#define EA_BLOCKID_LOC_FR 0x00004652 /* French */
|
|
|
|
#define EA_BLOCKID_LOC_GE 0x00004745 /* German, older */
|
|
|
|
#define EA_BLOCKID_LOC_DE 0x00004445 /* German, newer */
|
|
|
|
#define EA_BLOCKID_LOC_IT 0x00004954 /* Italian */
|
|
|
|
#define EA_BLOCKID_LOC_SP 0x00005350 /* Castilian Spanish, older */
|
|
|
|
#define EA_BLOCKID_LOC_ES 0x00004553 /* Castilian Spanish, newer */
|
|
|
|
#define EA_BLOCKID_LOC_MX 0x00004D58 /* Mexican Spanish */
|
|
|
|
#define EA_BLOCKID_LOC_RU 0x00005255 /* Russian */
|
|
|
|
#define EA_BLOCKID_LOC_JA 0x00004A41 /* Japanese, older */
|
|
|
|
#define EA_BLOCKID_LOC_JP 0x00004A50 /* Japanese, newer */
|
|
|
|
#define EA_BLOCKID_LOC_PL 0x0000504C /* Polish */
|
|
|
|
#define EA_BLOCKID_LOC_BR 0x00004252 /* Brazilian Portuguese */
|
|
|
|
|
|
|
|
/* EA SCHl with variable header - from EA games (roughly 1997~2010); generated by EA Canada's sx.exe/Sound eXchange */
|
|
|
|
VGMSTREAM* init_vgmstream_ea_schl(STREAMFILE* sf) {
|
|
|
|
|
|
|
|
/* check extension */
|
|
|
|
/* they don't seem enforced by EA's tools but usually:
|
|
|
|
* .asf: ~early (audio stream file?) [ex. Need for Speed II (PC)]
|
|
|
|
* .lasf: fake for plugins
|
|
|
|
* .str: ~early [ex. FIFA 98 (PS1), FIFA 2002 (PS1)]
|
|
|
|
* .chk: ~early [ex. NBA Live 98 (PS1)]
|
|
|
|
* .eam: ~mid?
|
|
|
|
* .exa: ~mid [ex. 007 - From Russia with Love]
|
|
|
|
* .sng: ~late (FIFA games)
|
|
|
|
* .aud: ~late [ex. FIFA 14 (3DS)]
|
|
|
|
* .strm: MySims Kingdom (Wii)
|
|
|
|
* .stm: FIFA 12 (3DS)
|
|
|
|
* .sx: FIFA 98 (SAT)
|
|
|
|
* .xa: ?
|
|
|
|
* .hab: GoldenEye - Rogue Agent (inside .big)
|
|
|
|
* .xsf: 007 - Agent Under Fire (Xbox)
|
|
|
|
* .gsf: 007 - Everything or Nothing (GC)
|
|
|
|
* (extensionless): SSX (PS2) (inside .big)
|
|
|
|
* .r: The Sims 2: Pets (PSP) (not l/r, shorter "res") */
|
|
|
|
if (!check_extensions(sf, "asf,lasf,str,chk,eam,exa,sng,aud,sx,xa,strm,stm,hab,xsf,gsf,,r"))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* check header */
|
2024-07-03 00:38:43 +02:00
|
|
|
if (read_u32be(0x00, sf) != EA_BLOCKID_HEADER && /* "SCHl" */
|
|
|
|
read_u32be(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_EN) && /* "SHEN" */
|
|
|
|
read_u32be(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_FR) && /* "SHFR" */
|
|
|
|
read_u32be(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_GE) && /* "SHGE" */
|
|
|
|
read_u32be(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_DE) && /* "SHDE" */
|
|
|
|
read_u32be(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_IT) && /* "SHIT" */
|
|
|
|
read_u32be(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_SP) && /* "SHSP" */
|
|
|
|
read_u32be(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_ES) && /* "SHES" */
|
|
|
|
read_u32be(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_MX) && /* "SHMX" */
|
|
|
|
read_u32be(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_RU) && /* "SHRU" */
|
|
|
|
read_u32be(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_JA) && /* "SHJA" */
|
|
|
|
read_u32be(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_JP) && /* "SHJP" */
|
|
|
|
read_u32be(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_PL) && /* "SHPL" */
|
|
|
|
read_u32be(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_BR)) /* "SHBR" */
|
2024-07-02 23:33:38 +02:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* Stream is divided into blocks/chunks: SCHl=audio header, SCCl=count of SCDl, SCDl=data xN, SCLl=loop end, SCEl=end.
|
|
|
|
* Video uses picture blocks (MVhd/MV0K/etc) and sometimes multiaudio blocks (SHxx/SCxx/SDxx/SExx where xx=language).
|
|
|
|
* The number/size is affected by: block rate setting, sample rate, channels, CPU location (SPU/main/DSP/others), etc */
|
|
|
|
return load_vgmstream_ea_schl(sf, 0x00);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* EA BNK with variable header - from EA games SFXs; also created by sx.exe */
|
|
|
|
VGMSTREAM* init_vgmstream_ea_bnk(STREAMFILE* sf) {
|
|
|
|
int target_stream = sf->stream_index;
|
|
|
|
|
|
|
|
/* check extension */
|
|
|
|
/* .bnk: common
|
|
|
|
* .sdt: Harry Potter games, Burnout games (PSP)
|
|
|
|
* .hdt/ldt: Burnout games (PSP)
|
|
|
|
* .abk: GoldenEye - Rogue Agent
|
|
|
|
* .ast: FIFA 2004 (inside .big)
|
|
|
|
* .cat: FIFA 2000 (PC, chant.cat)
|
|
|
|
* (extensionless): The Sims 2 spinoffs (PSP) */
|
|
|
|
if (!check_extensions(sf, "bnk,sdt,hdt,ldt,abk,ast,cat,"))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (target_stream == 0) target_stream = 1;
|
|
|
|
return load_vgmstream_ea_bnk(sf, 0x00, target_stream - 1, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* EA SCHl inside non-demuxed videos, used in current gen games too */
|
|
|
|
VGMSTREAM* init_vgmstream_ea_schl_video(STREAMFILE* sf) {
|
|
|
|
VGMSTREAM* vgmstream = NULL;
|
|
|
|
off_t offset = 0, start_offset = 0;
|
|
|
|
int blocks_done = 0;
|
|
|
|
int total_subsongs, target_subsong = sf->stream_index;
|
2024-07-03 00:38:43 +02:00
|
|
|
read_u32_t read_u32;
|
2024-07-02 23:33:38 +02:00
|
|
|
|
|
|
|
|
|
|
|
/* checks */
|
|
|
|
/* .uv: early */
|
|
|
|
/* .dct: early-mid [ex. Need for Speed II SE (PC), FIFA 98 (PC)] */
|
|
|
|
/* .wve: early-mid [Madden NFL 99 (PC)] */
|
|
|
|
/* .mad: mid */
|
|
|
|
/* .vp6: late */
|
|
|
|
/* .mpc: SSX Tricky (PS2) */
|
|
|
|
if (is_id32be(0x00, sf, "SCHl")) {
|
|
|
|
if (!check_extensions(sf, "uv,dct,mpc,lmpc,vp6"))
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
else if (is_id32be(0x00, sf, "MADk")) {
|
|
|
|
if (!check_extensions(sf, "mad,wve"))
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
else if (is_id32be(0x00, sf, "MVhd")) {
|
|
|
|
if (!check_extensions(sf, "vp6"))
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
else if (is_id32be(0x00, sf, "MPCh")) {
|
|
|
|
if (!check_extensions(sf, "mpc,lmpc"))
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* use block size to check endianness */
|
2024-07-03 00:38:43 +02:00
|
|
|
read_u32 = guess_endian32(0x04, sf) ? read_u32be : read_u32le;
|
2024-07-02 23:33:38 +02:00
|
|
|
|
|
|
|
/* find starting valid header for the parser */
|
|
|
|
while (offset < get_streamfile_size(sf)) {
|
2024-07-03 00:38:43 +02:00
|
|
|
uint32_t block_id = read_u32be(offset + 0x00, sf);
|
|
|
|
uint32_t block_size = read_u32(offset + 0x04, sf);
|
2024-07-02 23:33:38 +02:00
|
|
|
|
|
|
|
/* find "SCHl" or "SHxx" blocks */
|
|
|
|
if ((block_id == EA_BLOCKID_HEADER) || ((block_id & 0xFFFF0000) == EA_BLOCKID_LOC_HEADER)) {
|
|
|
|
start_offset = offset;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (block_size == 0xFFFFFFFF)
|
|
|
|
goto fail;
|
|
|
|
if (blocks_done > 10)
|
|
|
|
goto fail; /* unlikely to contain music */
|
|
|
|
|
|
|
|
blocks_done++;
|
|
|
|
offset += block_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (offset >= get_streamfile_size(sf))
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
/* find target subsong (one per each SHxx multilang block) */
|
|
|
|
total_subsongs = 1;
|
|
|
|
if (target_subsong == 0) target_subsong = 1;
|
|
|
|
offset = start_offset;
|
|
|
|
while (offset < get_streamfile_size(sf)) {
|
2024-07-03 00:38:43 +02:00
|
|
|
uint32_t block_id = read_u32be(offset + 0x00, sf);
|
|
|
|
uint32_t block_size = read_u32(offset + 0x04, sf);
|
2024-07-02 23:33:38 +02:00
|
|
|
|
|
|
|
/* no more subsongs (assumes all SHxx headers go together) */
|
|
|
|
if (((block_id & 0xFFFF0000) != EA_BLOCKID_LOC_HEADER)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (target_subsong == total_subsongs) {
|
|
|
|
start_offset = offset;
|
|
|
|
/* keep counting subsongs */
|
|
|
|
}
|
|
|
|
|
|
|
|
total_subsongs++;
|
|
|
|
offset += block_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
|
|
|
|
|
|
|
vgmstream = load_vgmstream_ea_schl(sf, start_offset);
|
|
|
|
if (!vgmstream) goto fail;
|
|
|
|
|
|
|
|
vgmstream->num_streams = total_subsongs;
|
|
|
|
return vgmstream;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
close_vgmstream(vgmstream);
|
|
|
|
return NULL;
|
|
|
|
}
|