vgmstream/src/meta/ea_schl_standard.c
2024-07-03 01:38:43 +03:00

186 lines
7.3 KiB
C

#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 */
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" */
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;
read_u32_t read_u32;
/* 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 */
read_u32 = guess_endian32(0x04, sf) ? read_u32be : read_u32le;
/* find starting valid header for the parser */
while (offset < get_streamfile_size(sf)) {
uint32_t block_id = read_u32be(offset + 0x00, sf);
uint32_t block_size = read_u32(offset + 0x04, sf);
/* 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)) {
uint32_t block_id = read_u32be(offset + 0x00, sf);
uint32_t block_size = read_u32(offset + 0x04, sf);
/* 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;
}