#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_32bitBE(0x00, sf) != EA_BLOCKID_HEADER && /* "SCHl" */ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_EN) && /* "SHEN" */ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_FR) && /* "SHFR" */ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_GE) && /* "SHGE" */ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_DE) && /* "SHDE" */ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_IT) && /* "SHIT" */ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_SP) && /* "SHSP" */ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_ES) && /* "SHES" */ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_MX) && /* "SHMX" */ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_RU) && /* "SHRU" */ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_JA) && /* "SHJA" */ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_JP) && /* "SHJP" */ read_32bitBE(0x00, sf) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_PL) && /* "SHPL" */ read_32bitBE(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; int32_t(*read_32bit)(off_t, STREAMFILE*); /* 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 */ if (guess_endian32(0x04, sf)) { read_32bit = read_32bitBE; } else { read_32bit = read_32bitLE; } /* find starting valid header for the parser */ while (offset < get_streamfile_size(sf)) { uint32_t block_id = read_32bitBE(offset + 0x00, sf); uint32_t block_size = read_32bit(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_32bitBE(offset + 0x00, sf); uint32_t block_size = read_32bit(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; }