mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-02-17 19:19:16 +01:00
Merge pull request #1466 from bnnm/xa-vag-eaac-etc
- Improve XA subsong detection performance - Add .svg VAG [ModernGroove: Ministry of Sound Edition (PS2)] - Fix some encrypted FSB .bank - Add FSB keys - Add .wav/lwav MPEG [The Seventh Seal (PC)] - txtp tools: tweaks - Add .gwb+gwd [Monster Truck 4x4 World World Circuit (Wii)] - Fix some .vab with empty sounds - Fix some .snu [Dead Space 3 (PC)]
This commit is contained in:
commit
0ad2096ce3
@ -74,11 +74,12 @@ class Cli(object):
|
||||
p.add_argument('-fne', dest='exclude_regex', help="Filter by REGEX excluding matches of subsong name")
|
||||
p.add_argument('-nsc',dest='no_semicolon', help="Remove semicolon names (for songs with multinames)", action='store_true')
|
||||
p.add_argument("-cmd","--command", help="sets any command (free text)")
|
||||
p.add_argument("-cmdi","--command-inline", help="sets any inline command (free text)")
|
||||
p.add_argument('-v', dest='log_level', help="Verbose log level (off|debug|info, default: info)", default='info')
|
||||
args = p.parse_args()
|
||||
|
||||
# defauls to rename (easier to use with drag-and-drop)
|
||||
if not all([args.overwrite, args.overwrite_ignore, args.overwrite_rename]):
|
||||
if not any([args.overwrite, args.overwrite_ignore, args.overwrite_rename]):
|
||||
args.overwrite_rename = True
|
||||
return args
|
||||
|
||||
@ -282,7 +283,7 @@ class TxtpMaker(object):
|
||||
for badchar in badchars:
|
||||
txt = txt.replace(badchar, '_')
|
||||
|
||||
if not self.cfg.no_internal_ext:
|
||||
if self.cfg.no_internal_ext:
|
||||
pos = txt.rfind(".")
|
||||
if pos >= 0:
|
||||
txt = txt[:pos]
|
||||
@ -328,6 +329,8 @@ class TxtpMaker(object):
|
||||
with open(outname,"w+", encoding='utf-8') as ftxtp:
|
||||
if line:
|
||||
ftxtp.write(line)
|
||||
if cfg.command_inline:
|
||||
ftxtp.write(cfg.command_inline)
|
||||
if cfg.command:
|
||||
cmd = cfg.command.replace("\\n", "\n") + "\n"
|
||||
if not line.endswith('\n'):
|
||||
@ -376,6 +379,7 @@ class TxtpMaker(object):
|
||||
|
||||
outname = ''
|
||||
if cfg.base_name:
|
||||
|
||||
stream_name = self._clean_stream_name()
|
||||
internal_filename = stream_name
|
||||
if not internal_filename:
|
||||
|
@ -34,6 +34,7 @@ def parse():
|
||||
parser.add_argument("-fe","--filter-exclude", help="exclude files matched with regex and keep rest")
|
||||
parser.add_argument("-s","--single", help="generate single files per list match", action='store_true')
|
||||
parser.add_argument("-l","--list", help="list only results and don't write .txtp", action='store_true')
|
||||
parser.add_argument("-ml","--mode-layers", help="sets layers", action='store_true')
|
||||
parser.add_argument("-cla","--command-loop-auto", help="sets auto-loop (last segment)", action='store_true')
|
||||
parser.add_argument("-clf","--command-loop-force", help="sets auto-loop (last segment) even with 1 segment", action='store_true')
|
||||
parser.add_argument("-cls","--command-loop-start", help="sets loop start segment")
|
||||
@ -154,6 +155,8 @@ def main():
|
||||
txtp_line = "%s%s\n" % (segment, command_inline)
|
||||
ftxtp.write(txtp_line)
|
||||
|
||||
if args.mode_layers:
|
||||
ftxtp.write("mode = layers\n")
|
||||
if args.command_loop_auto or args.command_loop_force and len_segments > 1:
|
||||
ftxtp.write("loop_mode = auto\n")
|
||||
if args.command_loop_start:
|
||||
|
@ -185,7 +185,7 @@ different internally (encrypted, different versions, etc) and not always can be
|
||||
- Sony VAG header (custom) [*VAG_custom*]
|
||||
- Sony VAG header [*VAG*]
|
||||
- Acclaim Austin AAAp header [*AAAP*]
|
||||
- *vag*: `.vag .swag .str .vig .l .r .vas .xa2 .snd`
|
||||
- *vag*: `.vag .swag .str .vig .l .r .vas .xa2 .snd .svg`
|
||||
- *vag_aaap*: `.vag`
|
||||
- Codecs: PSX HEVAG
|
||||
- **ild.c**
|
||||
@ -201,7 +201,7 @@ different internally (encrypted, different versions, etc) and not always can be
|
||||
- Electronic Arts SCHl header [*EA_SCHL*]
|
||||
- *ea_schl*: `.asf .lasf .str .chk .eam .exa .sng .aud .sx .xa .strm .stm .hab .xsf .gsf .(extensionless)`
|
||||
- *ea_schl_video*: `.uv .dct .mad .wve .vp6`
|
||||
- *ea_bnk*: `.bnk .sdt .hdt .ldt .abk .ast`
|
||||
- *ea_bnk*: `.bnk .sdt .hdt .ldt .abk .ast .cat`
|
||||
- *ea_abk*: `.abk + .ast`
|
||||
- *ea_hdr_dat*: `.hdr + .dat`
|
||||
- Subfiles: *vag*
|
||||
@ -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`
|
||||
@ -1299,7 +1310,7 @@ different internally (encrypted, different versions, etc) and not always can be
|
||||
- Sony BNK header [*BNK_SONY*]
|
||||
- *bnk_sony*: `.bnk`
|
||||
- Subfiles: *riff*
|
||||
- Codecs: ATRAC9 PCM16BE PCM16LE PSX HEVAG
|
||||
- Codecs: ATRAC9 MPEG PCM16BE PCM16LE PSX HEVAG
|
||||
- **nus3bank.c**
|
||||
- (container)
|
||||
- *nus3bank*: `.nub2 .nus3bank`
|
||||
@ -1798,9 +1809,13 @@ different internally (encrypted, different versions, etc) and not always can be
|
||||
- Sony SNDS header [*SNDS*]
|
||||
- Codecs: ATRAC9
|
||||
- **nxof.c**
|
||||
- Nihon Falcom FDK Opus Header [*NXOF*]
|
||||
- Nihon Falcom FDK header [*NXOF*]
|
||||
- *nxof*: `.nxopus`
|
||||
- Codecs: Opus
|
||||
- **gwb_gwd.c**
|
||||
- Ubisoft GWB+GWD header [*GWB_GWD*]
|
||||
- *gwb_gwd*: `.gwb + .gwd`
|
||||
- Codecs: NGC_DSP
|
||||
- **scd_pcm.c**
|
||||
- Lunar: Eternal Blue .PCM header [*SCD_PCM*]
|
||||
- *scd_pcm*: `.pcm`
|
||||
@ -1851,7 +1866,7 @@ different internally (encrypted, different versions, etc) and not always can be
|
||||
- Codecs: NGC_DTK
|
||||
- **mpeg.c**
|
||||
- MPEG header [*MPEG*]
|
||||
- *mpeg*: `.mp3 .mp2 .lmp3 .lmp2 .mus .imf .aix .(extensionless)`
|
||||
- *mpeg*: `.mp3 .mp2 .lmp3 .lmp2 .mus .imf .aix .wav .lwav .(extensionless)`
|
||||
- Codecs: MPEG
|
||||
- **btsnd.c**
|
||||
- Nintendo Wii U Menu Boot Sound header [*BTSND*]
|
||||
@ -1915,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
|
||||
|
@ -391,7 +391,7 @@ static int ealayer3_parse_frame_v2(ealayer3_buffer_t* ib, ealayer3_frame_t* eaf)
|
||||
}
|
||||
|
||||
VGM_ASSERT(eaf->v2_extended_flag && eaf->v2_common_size == 0, "EA EAL3: v2 empty frame\n"); /* seen in V2S */
|
||||
VGM_ASSERT(eaf->v2_extended_flag && eaf->v2_offset_samples > 0, "EA EAL3: v2_offset_mode=%x with value=0x%x\n", eaf->v2_offset_mode, eaf->v2_offset_samples);
|
||||
VGM_ASSERT(eaf->v2_extended_flag && eaf->v2_offset_samples > 0, "EA EAL3: v2_offset_mode=%x with value=0x%x\n", eaf->v2_offset_mode, eaf->v2_offset_samples); /* Dead Space 2 (PC) */
|
||||
//VGM_ASSERT(eaf->v2_pcm_samples > 0, "EA EAL3: v2_pcm_samples 0x%x\n", eaf->v2_pcm_samples);
|
||||
|
||||
eaf->pcm_size = (eaf->v2_pcm_samples * sizeof(int16_t) * eaf->channels);
|
||||
|
@ -212,6 +212,7 @@ static const char* extension_list[] = {
|
||||
"gsf",
|
||||
"gsp",
|
||||
"gtd",
|
||||
"gwb",
|
||||
"gwm",
|
||||
|
||||
"h4m",
|
||||
@ -1422,7 +1423,8 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_SQUEAKSTREAM, "Torus SqueakStream header"},
|
||||
{meta_SQUEAKSAMPLE, "Torus SqueakSample header"},
|
||||
{meta_SNDS, "Sony SNDS header"},
|
||||
{meta_NXOF, "Nihon Falcom FDK Opus Header"},
|
||||
{meta_NXOF, "Nihon Falcom FDK header"},
|
||||
{meta_GWB_GWD, "Ubisoft GWB+GWD header"},
|
||||
};
|
||||
|
||||
void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t out_size) {
|
||||
|
@ -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" />
|
||||
@ -446,6 +453,7 @@
|
||||
<ClCompile Include="meta\ghs.c" />
|
||||
<ClCompile Include="meta\gin.c" />
|
||||
<ClCompile Include="meta\gsnd.c" />
|
||||
<ClCompile Include="meta\gwb_gwd.c" />
|
||||
<ClCompile Include="meta\h4m.c" />
|
||||
<ClCompile Include="meta\halpst.c" />
|
||||
<ClCompile Include="meta\hca.c" />
|
||||
|
@ -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>
|
||||
@ -1159,6 +1180,9 @@
|
||||
<ClCompile Include="meta\gsnd.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\gwb_gwd.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\h4m.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
1271
src/meta/ea_eaac.c
1271
src/meta/ea_eaac.c
File diff suppressed because it is too large
Load Diff
197
src/meta/ea_eaac_abk.c
Normal file
197
src/meta/ea_eaac_abk.c
Normal 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;
|
||||
}
|
160
src/meta/ea_eaac_hdr_sth_dat.c
Normal file
160
src/meta/ea_eaac_hdr_sth_dat.c
Normal 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
343
src/meta/ea_eaac_mpf_mus.c
Normal 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
115
src/meta/ea_eaac_sbr.c
Normal 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;
|
||||
}
|
243
src/meta/ea_eaac_sbr_harmony.c
Normal file
243
src/meta/ea_eaac_sbr_harmony.c
Normal 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;
|
||||
}
|
82
src/meta/ea_eaac_standard.c
Normal file
82
src/meta/ea_eaac_standard.c
Normal file
@ -0,0 +1,82 @@
|
||||
#include "meta.h"
|
||||
#include "../util/endianness.h"
|
||||
|
||||
|
||||
#define EAAC_BLOCKID1_HEADER 0x48 /* '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 (read_u8(0x00, sf) != EAAC_BLOCKID1_HEADER) /* validated later but fails faster */
|
||||
return NULL;
|
||||
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);
|
||||
|
||||
uint32_t body_offset = read_u32(0x08,sf);
|
||||
uint8_t block_id = read_u8(body_offset, sf);
|
||||
|
||||
|
||||
if (block_id == EAAC_BLOCKID1_HEADER) {
|
||||
/* Dead Space 3 (PC) */
|
||||
info.sf_head = sf;
|
||||
info.head_offset = body_offset; /* header also at 0x10, but useless in SPS */
|
||||
info.type = meta_EA_SNU;
|
||||
info.is_sps = true;
|
||||
}
|
||||
else {
|
||||
info.sf_head = sf;
|
||||
info.sf_body = sf;
|
||||
info.head_offset = 0x10; /* SNR header */
|
||||
info.body_offset = body_offset; /* SNR body */
|
||||
info.type = meta_EA_SNU;
|
||||
}
|
||||
|
||||
return load_vgmstream_ea_eaac(&info);
|
||||
}
|
71
src/meta/ea_eaac_tmx.c
Normal file
71
src/meta/ea_eaac_tmx.c
Normal 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;
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
#include "../util/chunks.h"
|
||||
|
||||
|
||||
static int get_subsongs(STREAMFILE* sf, off_t fsb5_offset, size_t fsb5_size);
|
||||
static int get_subsongs(STREAMFILE* sf, uint32_t fsb5_offset, uint32_t fsb5_size);
|
||||
|
||||
/* FEV+FSB5 container [Just Cause 3 (PC), Shantae: Half-Genie Hero (Switch)] */
|
||||
VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) {
|
||||
@ -88,7 +88,7 @@ VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) {
|
||||
|
||||
/* 0x00: unknown (chunk version? ex LE: 0x00080003, 0x00080005) */
|
||||
banks = (bank_size - 0x04) / entry_size;
|
||||
|
||||
|
||||
/* multiple banks is possible but rare [Hades (Switch), Guacamelee 2 (Switch)],
|
||||
* must map bank (global) subsong to FSB (internal) subsong */
|
||||
|
||||
@ -99,11 +99,11 @@ VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) {
|
||||
total_subsongs = 0;
|
||||
for (i = 0; i < banks; i++) {
|
||||
//TODO: fsb5_size fails for v0x28< + encrypted, but only used with multibanks = unlikely
|
||||
off_t fsb5_offset = read_u32le(bank_offset + 0x04 + entry_size*i + 0x00,sf);
|
||||
size_t fsb5_size = read_u32le(bank_offset+0x08 + entry_size*i,sf);
|
||||
uint32_t fsb5_offset = read_u32le(bank_offset + 0x04 + entry_size*i + 0x00,sf);
|
||||
uint32_t fsb5_size = read_u32le(bank_offset + 0x08 + entry_size*i,sf);
|
||||
int fsb5_subsongs = get_subsongs(sf, fsb5_offset, fsb5_size);
|
||||
if (!fsb5_subsongs) {
|
||||
vgm_logi("FSB: couldn't load bank (encrypted?)\n");
|
||||
vgm_logi("FSB: couldn't load bank %i at %x (encrypted?)\n", i, fsb5_offset);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
@ -159,14 +159,13 @@ fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int get_subsongs(STREAMFILE* sf, off_t fsb5_offset, size_t fsb5_size) {
|
||||
static int get_subsongs(STREAMFILE* sf, uint32_t fsb5_offset, uint32_t fsb5_size) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
int subsongs = 0;
|
||||
|
||||
|
||||
/* standard */
|
||||
if (read_u32be(fsb5_offset, sf) == 0x46534235) { /* FSB5 */
|
||||
if (is_id32be(fsb5_offset, sf, "FSB5")) {
|
||||
return read_s32le(fsb5_offset + 0x08,sf);
|
||||
}
|
||||
|
||||
@ -174,6 +173,7 @@ static int get_subsongs(STREAMFILE* sf, off_t fsb5_offset, size_t fsb5_size) {
|
||||
temp_sf = setup_subfile_streamfile(sf, fsb5_offset, fsb5_size, "fsb");
|
||||
if (!temp_sf) goto end;
|
||||
|
||||
temp_sf->stream_index = 0;
|
||||
vgmstream = init_vgmstream_fsb_encrypted(temp_sf);
|
||||
if (!vgmstream) goto end;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
#ifndef _FSB_ENCRYPTED_STREAMFILE_H_
|
||||
#define _FSB_ENCRYPTED_STREAMFILE_H_
|
||||
|
||||
#define FSB_KEY_MAX 0x10000 //0x168
|
||||
#define FSB_KEY_MAX 0x80 /* known max ~0x33 */
|
||||
|
||||
|
||||
typedef struct {
|
||||
@ -57,7 +57,7 @@ static STREAMFILE* setup_fsb_streamfile(STREAMFILE* sf, const uint8_t* key, size
|
||||
size_t io_data_size = sizeof(fsb_decryption_data);
|
||||
|
||||
/* setup decryption with key (external) */
|
||||
if (!key_size || key_size > FSB_KEY_MAX)
|
||||
if (!key_size || key_size >= FSB_KEY_MAX)
|
||||
return NULL;
|
||||
|
||||
memcpy(io_data.key, key, key_size);
|
||||
|
@ -21,7 +21,7 @@ typedef struct {
|
||||
#define FLAG_FSB4 (1 << 0) /* key is valid for FSB4/3 */
|
||||
#define FLAG_FSB5 (1 << 1) /* key is valid for FSB5 */
|
||||
#define FLAG_STD (1 << 2) /* regular XOR mode */
|
||||
#define FLAG_ALT (1 << 3) /* alt XOR mode (seemingly not tied to FSB version or anything, maybe wrong key) */
|
||||
#define FLAG_ALT (1 << 3) /* alt XOR mode (seemingly older files or possibly FSB3 only) */
|
||||
|
||||
#define MODE_FSB4_STD (FLAG_FSB4 | FLAG_STD)
|
||||
#define MODE_FSB4_ALT (FLAG_FSB4 | FLAG_ALT)
|
||||
@ -55,7 +55,7 @@ static const fsbkey_info fsbkey_list[] = {
|
||||
{ MODE_FSBS_ALL, FSBKEY_ADD("5atu6w4zaw") }, // Guitar Hero 3 [untested]
|
||||
{ MODE_FSBS_ALL, FSBKEY_ADD("B2A7BB00") }, // Supreme Commander 2 [untested]
|
||||
{ MODE_FSB4_STD, FSBKEY_ADD("ghfxhslrghfxhslr") }, // Cookie Run: Ovenbreak
|
||||
{ MODE_FSB4_ALT, FSBKEY_ADD("truck/impact/carbody") },// Monster Jam (PS2) [FSB3]
|
||||
{ MODE_FSB4_ALT, FSBKEY_ADD("truck/impact/carbody") }, // Monster Jam (PS2) [FSB3]
|
||||
{ MODE_FSB4_ALT, FSBKEY_ADD("\xFC\xF9\xE4\xB3\xF5\x57\x5C\xA5\xAC\x13\xEC\x4A\x43\x19\x58\xEB\x4E\xF3\x84\x0B\x8B\x78\xFA\xFD\xBB\x18\x46\x7E\x31\xFB\xD0") }, // Guitar Hero 5 (X360)
|
||||
{ MODE_FSB5_STD, FSBKEY_ADD("G0KTrWjS9syqF7vVD6RaVXlFD91gMgkC") }, // Sekiro: Shadows Die Twice (PC)
|
||||
{ MODE_FSB5_STD, FSBKEY_ADD("BasicEncryptionKey") }, // SCP: Unity (PC)
|
||||
@ -73,6 +73,9 @@ static const fsbkey_info fsbkey_list[] = {
|
||||
{ MODE_FSB5_STD, FSBKEY_ADD("L36nshM520") }, // Nishuihan Mobile (Android)
|
||||
{ MODE_FSB5_STD, FSBKEY_ADD("Forza2!") }, // Forza Motorsport (PC)
|
||||
{ MODE_FSB5_STD, FSBKEY_ADD("cbfjZTlUPaZI") }, // JDM: Japanese Drift Master (PC)
|
||||
{ MODE_FSB4_ALT, FSBKEY_ADD("tkdnsem000") }, // Ys Online: The Call of Solum (PC) [FSB3] (alt key: 2ED62676CEA6B60C0C0C)
|
||||
{ MODE_FSB4_STD, FSBKEY_ADD("4DxgpNV3pQLPD6GT7g9Gf6eWU7SXutGQ") }, // Test Drive: Ferrari Racing Legends (PC)
|
||||
{ MODE_FSB4_STD, FSBKEY_ADD("AjaxIsTheGoodestBoy") }, // Hello Kitty: Island Adventure (iOS)
|
||||
|
||||
/* these games use a key per file, generated from the filename; could be possible to add them but there is a lot of songs,
|
||||
so external .fsbkey may be better (use guessfsb 3.1 with --write-key-file or ) */
|
||||
|
149
src/meta/gwb_gwd.c
Normal file
149
src/meta/gwb_gwd.c
Normal file
@ -0,0 +1,149 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* GWB+GWD - Ubisoft bank [Monster 4x4: World Circuit (Wii)] */
|
||||
VGMSTREAM* init_vgmstream_gwb_gwd(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* sf_body = NULL;
|
||||
uint32_t stream_offset = 0, stream_size = 0, coef_offset;
|
||||
int loop_flag, channels, sample_rate, interleave = 0;
|
||||
uint32_t loop_start, loop_end;
|
||||
int total_subsongs, target_subsong = sf->stream_index;
|
||||
|
||||
|
||||
/* checks */
|
||||
int version = read_u8(0x00, sf);
|
||||
if (version != 6 && version != 7)
|
||||
return NULL;
|
||||
if (read_u32be(0x01, sf) > 0x0400) /* ID, max seen */
|
||||
return NULL;
|
||||
if (get_streamfile_size(sf) > 0x2000) /* arbitrary max */
|
||||
return NULL;
|
||||
if (!check_extensions(sf,"gwb"))
|
||||
return NULL;
|
||||
|
||||
/* format (vaguely similar to ubi's hx and such banks)
|
||||
* common
|
||||
* 00: version (06/07, both found in the same game)
|
||||
* 01: file ID (low number: 0x0001, 0x0342...)
|
||||
* v6:
|
||||
* 05: subsongs
|
||||
* v7
|
||||
* 05: null
|
||||
* 09: subsongs
|
||||
*
|
||||
* per subsong:
|
||||
* - 00: flags: (v6: 09=stereo, 02=mono; v7: 0a=stereo)
|
||||
* - 01: id (ex. 0x0002, 0x0343...)
|
||||
* v6
|
||||
* - 05: 0x4a header * channels
|
||||
* v7
|
||||
* - 05: always 0x02?
|
||||
* - 09: stream offset
|
||||
* - 0d: stream size
|
||||
* - 11: always 5
|
||||
* - 15: 0x4a header * channels
|
||||
*
|
||||
* per header:
|
||||
* - 00: loop flag
|
||||
* - 04: sample rate
|
||||
* - 08: loop start nibbles
|
||||
* - 0c: loop end nibbles
|
||||
* - 10: end nibble
|
||||
* - 14: start nibble (after DSP frame header, so uses 0x02 at file start)
|
||||
* - 18: null
|
||||
* - 1c: coefs + gain + initial ps/hists + loop ps/hists
|
||||
* Data in .gwd is N headerless DSPs. All nibble values are absolute within the file. */
|
||||
|
||||
uint32_t offset = version == 6 ? 0x05 : 0x09;
|
||||
|
||||
total_subsongs = read_s32be(offset, sf);
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) return NULL;
|
||||
offset += 0x04;
|
||||
|
||||
/* find target header */
|
||||
for (int i = 0; i < total_subsongs; i++) {
|
||||
if (i + 1 == target_subsong)
|
||||
break;
|
||||
|
||||
uint8_t type = read_u8(offset + 0x00, sf);
|
||||
if (type != 0x0a && type != 0x09 && type != 0x02)
|
||||
goto fail;
|
||||
|
||||
offset += 0x05 + (version == 7 ? 0x10 : 0);
|
||||
offset += 0x4a * (type & 0x08 ? 2 : 1);
|
||||
}
|
||||
|
||||
/* header */
|
||||
{
|
||||
uint32_t st_nibble, ed_nibble, ls_nibble, le_nibble;
|
||||
uint8_t type = read_u8(offset + 0x00, sf);
|
||||
channels = (type & 0x08 ? 2 : 1);
|
||||
|
||||
offset += 0x05;
|
||||
if (version == 7) {
|
||||
stream_offset = read_u32be(offset + 0x04, sf);
|
||||
stream_size = read_u32be(offset + 0x08, sf);
|
||||
interleave = 0x4000;
|
||||
offset += 0x10;
|
||||
}
|
||||
loop_flag = read_u32be(offset + 0x00, sf) == 1;
|
||||
sample_rate = read_u32be(offset + 0x04, sf);
|
||||
ls_nibble = read_u32be(offset + 0x08, sf);
|
||||
le_nibble = read_u32be(offset + 0x0c, sf);
|
||||
ed_nibble = read_u32be(offset + 0x10, sf);
|
||||
st_nibble = read_u32be(offset + 0x14, sf);
|
||||
coef_offset = offset + 0x1c;
|
||||
|
||||
if (version == 6) {
|
||||
stream_offset = ((st_nibble - 2) / 2);
|
||||
stream_size = ((ed_nibble - st_nibble - 2) / 2) * channels;
|
||||
|
||||
/* stereo repeats loop flag/sample rate/offsets/etc but simplify */
|
||||
if (channels == 2) {
|
||||
uint32_t s2_nibble = read_u32be(offset + 0x4a + 0x14, sf);
|
||||
interleave = (s2_nibble - st_nibble) / 2;
|
||||
}
|
||||
}
|
||||
loop_start = ((ls_nibble - 2) / 2 - stream_offset);
|
||||
loop_end = ((le_nibble) / 2 - stream_offset) * channels;
|
||||
}
|
||||
|
||||
/* files also have an optional companion .gsb with volume/etc config that seems to be adapted from Xbox's .xsb */
|
||||
sf_body = open_streamfile_by_ext(sf, "gwd");
|
||||
if (!sf_body) goto fail;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_GWB_GWD;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = dsp_bytes_to_samples(stream_size, channels);
|
||||
vgmstream->loop_start_sample = dsp_bytes_to_samples(loop_start, channels);
|
||||
vgmstream->loop_end_sample = dsp_bytes_to_samples(loop_end, channels);;
|
||||
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
vgmstream->stream_size = stream_size;
|
||||
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = interleave;
|
||||
|
||||
dsp_read_coefs_be(vgmstream, sf, coef_offset + 0x00, 0x4a);
|
||||
dsp_read_hist_be (vgmstream, sf, coef_offset + 0x24, 0x4a);
|
||||
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf_body, stream_offset))
|
||||
goto fail;
|
||||
close_streamfile(sf_body);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(sf_body);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
@ -650,6 +650,18 @@ 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;
|
||||
bool is_sps;
|
||||
} eaac_meta_t;
|
||||
|
||||
VGMSTREAM* load_vgmstream_ea_eaac(eaac_meta_t* info);
|
||||
|
||||
VGMSTREAM* init_vgmstream_vid1(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM * init_vgmstream_flx(STREAMFILE * streamFile);
|
||||
@ -988,4 +1000,6 @@ VGMSTREAM* init_vgmstream_snds(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_nxof(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_gwb_gwd(STREAMFILE* sf);
|
||||
|
||||
#endif /*_META_H*/
|
||||
|
@ -37,8 +37,9 @@ VGMSTREAM* init_vgmstream_mpeg(STREAMFILE* sf) {
|
||||
* .mus: Marc Ecko's Getting Up (PC)
|
||||
* .imf: Colors (Gizmondo)
|
||||
* .aix: Classic Compendium 2 (Gizmondo)
|
||||
* .wav/lwav: The Seventh Seal (PC)
|
||||
* (extensionless): Interstellar Flames 2 (Gizmondo) */
|
||||
if (!check_extensions(sf, "mp3,mp2,lmp3,lmp2,mus,imf,aix,,"))
|
||||
if (!check_extensions(sf, "mp3,mp2,lmp3,lmp2,mus,imf,aix,wav,lwav,"))
|
||||
goto fail;
|
||||
|
||||
loop_flag = 0;
|
||||
|
@ -467,6 +467,9 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) {
|
||||
|
||||
else if (codec == 0xFFFE && riff_size + 0x08 + 0x40 == file_size)
|
||||
file_size -= 0x40; /* [Megami no Etsubo (PSP)] (has extra padding in all files) */
|
||||
|
||||
else if (codec == 0x0011 && file_size - riff_size - 0x08 <= 0x900 && is_id32be(riff_size + 0x08, sf, "cont"))
|
||||
riff_size = file_size - 0x08; /* [Shin Megami Tensei: Imagine (PC)] (extra "cont" info 0x800/0x900 chunk) */
|
||||
}
|
||||
|
||||
/* check for truncated RIFF */
|
||||
|
@ -167,8 +167,8 @@ VGMSTREAM* init_vgmstream_vab(STREAMFILE* sf) {
|
||||
|
||||
data_size = read_u16le(waves_off + i * 0x02, sf) << 3;
|
||||
|
||||
if (data_size == 0 && center == 0 && shift == 0) {
|
||||
// hack for empty sounds in Critical Depth
|
||||
if (data_size == 0 /*&& center == 0 && shift == 0*/) {
|
||||
// hack for empty sounds in rare cases (may set center/shift to 0 as well) [Critical Depth]
|
||||
vgmstream = init_vgmstream_silence(1, 44100, 44100);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
|
@ -25,8 +25,9 @@ VGMSTREAM* init_vgmstream_vag(STREAMFILE* sf) {
|
||||
* .l/r: Crash Nitro Kart (PS2), Gradius V (PS2)
|
||||
* .vas: Kingdom Hearts II (PS2)
|
||||
* .xa2: Shikigami no Shiro (PS2)
|
||||
* .snd: Alien Breed (Vita) */
|
||||
if (!check_extensions(sf,"vag,swag,str,vig,l,r,vas,xa2,snd"))
|
||||
* .snd: Alien Breed (Vita)
|
||||
* .svg: ModernGroove: Ministry of Sound Edition (PS2) */
|
||||
if (!check_extensions(sf,"vag,swag,str,vig,l,r,vas,xa2,snd,svg"))
|
||||
return NULL;
|
||||
|
||||
file_size = get_streamfile_size(sf);
|
||||
@ -148,6 +149,16 @@ VGMSTREAM* init_vgmstream_vag(STREAMFILE* sf) {
|
||||
|
||||
loop_flag = ps_find_loop_offsets(sf, start_offset, channel_size*channels, channels, interleave, &loop_start_sample, &loop_end_sample);
|
||||
}
|
||||
else if (version == 0x00000020 && is_id32be(0x800,sf, "VAGp")) {
|
||||
/* ModernGroove: Ministry of Sound Edition (PS2) */
|
||||
start_offset = 0x30;
|
||||
channels = 2;
|
||||
interleave = 0x800;
|
||||
interleave_first = interleave - start_offset; /* includes header */
|
||||
interleave_first_skip = start_offset;
|
||||
|
||||
loop_flag = 0;
|
||||
}
|
||||
else if (version == 0x40000000) {
|
||||
/* Killzone (PS2) */
|
||||
start_offset = 0x30;
|
||||
|
179
src/meta/xa.c
179
src/meta/xa.c
@ -3,16 +3,15 @@
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
static int xa_read_subsongs(STREAMFILE* sf, int target_subsong, off_t start, uint16_t* p_stream_config, off_t* p_stream_offset, size_t* p_stream_size, int* p_form2);
|
||||
static int xa_read_subsongs(STREAMFILE* sf, int target_subsong, uint32_t start, uint32_t* p_stream_offset, uint32_t* p_stream_size);
|
||||
static int xa_check_format(STREAMFILE* sf, off_t offset, int is_blocked);
|
||||
|
||||
/* XA - from Sony PS1 and Philips CD-i CD audio */
|
||||
VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
uint32_t start_offset, stream_size = 0;
|
||||
int loop_flag = 0, channels, sample_rate, bps;
|
||||
int is_riff = 0, is_form2 = 0, is_blocked;
|
||||
size_t stream_size = 0;
|
||||
int total_subsongs = 0, target_subsong = sf->stream_index;
|
||||
uint16_t target_config = 0;
|
||||
|
||||
@ -53,7 +52,7 @@ VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) {
|
||||
|
||||
/* find subsongs as XA can interleave sectors using 'file' and 'channel' makers (see blocked_xa.c) */
|
||||
if (/*!is_riff &&*/ is_blocked) {
|
||||
total_subsongs = xa_read_subsongs(sf, target_subsong, start_offset, &target_config, &start_offset, &stream_size, &is_form2);
|
||||
total_subsongs = xa_read_subsongs(sf, target_subsong, start_offset, &start_offset, &stream_size);
|
||||
if (total_subsongs <= 0) goto fail;
|
||||
}
|
||||
else {
|
||||
@ -63,7 +62,13 @@ VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) {
|
||||
/* data is ok: parse header */
|
||||
if (is_blocked) {
|
||||
/* parse 0x18 sector header (also see blocked_xa.c) */
|
||||
uint8_t xa_header = read_u8(start_offset + 0x13,sf);
|
||||
uint32_t curr_info = read_u32be(start_offset + 0x10, sf);
|
||||
uint16_t xa_config = (curr_info >> 16) & 0xFFFF; /* file+channel markers */
|
||||
uint8_t xa_submode = (curr_info >> 8) & 0xFF;
|
||||
uint8_t xa_header = (curr_info >> 0) & 0xFF;
|
||||
|
||||
target_config = xa_config;
|
||||
is_form2 = (xa_submode & 0x20);
|
||||
|
||||
switch((xa_header >> 0) & 3) { /* 0..1: mono/stereo */
|
||||
case 0: channels = 1; break;
|
||||
@ -175,7 +180,7 @@ static int xa_check_format(STREAMFILE *sf, off_t offset, int is_blocked) {
|
||||
if (get_u32be(frame_hdr+0x00) != get_u32be(frame_hdr+0x04) ||
|
||||
get_u32be(frame_hdr+0x08) != get_u32be(frame_hdr+0x0c))
|
||||
goto fail;
|
||||
/* blank frames should always use 0x0c0c0c0c (due to how shift works) */
|
||||
/* blank frames should always use 0x0c0c0c0c due to how shift works, (in rare/unused file-channels it may be blank though) */
|
||||
if (get_u32be(frame_hdr+0x00) == 0 &&
|
||||
get_u32be(frame_hdr+0x04) == 0 &&
|
||||
get_u32be(frame_hdr+0x08) == 0 &&
|
||||
@ -196,14 +201,11 @@ fail:
|
||||
}
|
||||
|
||||
|
||||
#define XA_SUBSONG_MAX 1024 /* +500 found in bigfiles like Castlevania SOTN */
|
||||
#define XA_MAX_CHANNELS 16
|
||||
|
||||
typedef struct xa_subsong_t {
|
||||
uint16_t config;
|
||||
off_t start;
|
||||
int form2;
|
||||
int sectors;
|
||||
int end_flag;
|
||||
typedef struct {
|
||||
uint32_t info;
|
||||
int subsong;
|
||||
} xa_subsong_t;
|
||||
|
||||
/* Get subsong info, as real XA data interleaves N sectors/subsongs (often 8/16). Extractors deinterleave
|
||||
@ -211,17 +213,38 @@ typedef struct xa_subsong_t {
|
||||
* usable sectors for bytes-to-samples.
|
||||
*
|
||||
* Bigfiles that paste tons of XA together are slow to parse since we need to read every sector to
|
||||
* count totals, but XA subsong handling is mainly for educational purposes. */
|
||||
static int xa_read_subsongs(STREAMFILE* sf, int target_subsong, off_t start, uint16_t* p_stream_config, off_t* p_stream_offset, size_t* p_stream_size, int* p_form2) {
|
||||
xa_subsong_t *cur_subsong = NULL;
|
||||
xa_subsong_t subsongs[XA_SUBSONG_MAX] = {0};
|
||||
* count totals, but XA subsong handling is mainly for educational purposes.
|
||||
*
|
||||
* Raw XA CD sectors are interleaved and classified into "files" and "channels" due to how CD driver/audio buffer works.
|
||||
* Devs select one file+channel (or just channel?) to play and CD ignores non-target sectors.
|
||||
* "files" can be any number in any order (but usually 00~64), and "channels" seem to be max 00~0F.
|
||||
* file+channel (=song) ends with a flag or when file changes; simplified example (upper=file, lower=channel):
|
||||
* 0101 0102 0101 0102 0201 0202 0201 0202 0101 0102
|
||||
* adapted to subsongs:
|
||||
* 0101 #1 (all 0101 sectors until file change = 2 sectors)
|
||||
* 0102 #2
|
||||
* 0201 #3
|
||||
* 0202 #4
|
||||
* 0101 #5 (different than first subsong since there was another file in between, 1 sector)
|
||||
* 0102 #6
|
||||
*
|
||||
* For video + audio the layout is the same with extra flags to detect video/audio sectors:
|
||||
* 0101v 0101v 0101v 0101v 0101v 0101v 0101v 0101a (usually 7 video sectors per 1 audio sector)
|
||||
*
|
||||
* CDs can't have 0101 0101 0101 ... audio sectors (need to interleave other channels, or repeat data),
|
||||
* but can be seen in demuxed XA. Combinations like a 0101 after 0201 probably only happen when devs
|
||||
* paste many XAs into a bigfile, which likely would jump via offsets in exe to the XA start (can be
|
||||
* split), but they are detected here for convenience.
|
||||
*/
|
||||
static int xa_read_subsongs(STREAMFILE* sf, int target_subsong, uint32_t start, uint32_t* p_stream_offset, uint32_t* p_stream_size) {
|
||||
const size_t sector_size = 0x930;
|
||||
uint16_t prev_config;
|
||||
int i, subsongs_count = 0;
|
||||
size_t file_size;
|
||||
off_t offset;
|
||||
STREAMFILE *sf_test = NULL;
|
||||
uint8_t header[4];
|
||||
int subsongs_count = 0;
|
||||
uint32_t offset, file_size;
|
||||
STREAMFILE* sf_test = NULL;
|
||||
|
||||
xa_subsong_t xa_subsongs[XA_MAX_CHANNELS] = {0};
|
||||
uint32_t target_start = 0xFFFFFFFFu;
|
||||
int target_sectors = 0;
|
||||
|
||||
|
||||
/* buffer to speed up header reading; bigger (+0x8000) is actually faster than very small (~0x10),
|
||||
@ -229,99 +252,71 @@ static int xa_read_subsongs(STREAMFILE* sf, int target_subsong, off_t start, uin
|
||||
sf_test = reopen_streamfile(sf, 0x10000);
|
||||
if (!sf_test) goto fail;
|
||||
|
||||
prev_config = 0xFFFFu;
|
||||
file_size = get_streamfile_size(sf);
|
||||
offset = start;
|
||||
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
|
||||
/* read XA sectors */
|
||||
while (offset < file_size) {
|
||||
uint16_t xa_config;
|
||||
uint8_t xa_submode;
|
||||
int is_audio, is_eof;
|
||||
uint32_t curr_info = read_u32be(offset + 0x10, sf_test);
|
||||
//uint8_t xa_file = (curr_info >> 24) & 0xFF;
|
||||
uint8_t xa_chan = (curr_info >> 16) & 0xFF;
|
||||
uint8_t xa_submode = (curr_info >> 8) & 0xFF;
|
||||
//uint8_t xa_header = (curr_info >> 0) & 0xFF;
|
||||
bool is_audio = !(xa_submode & 0x08) && (xa_submode & 0x04) && !(xa_submode & 0x02);
|
||||
bool is_eof = (xa_submode & 0x80);
|
||||
bool is_target = false;
|
||||
|
||||
read_streamfile(header, offset + 0x10, sizeof(header), sf_test);
|
||||
xa_config = get_u16be(header + 0x00); /* file+channel markers */
|
||||
xa_submode = get_u8 (header + 0x02); /* flags */
|
||||
is_audio = !(xa_submode & 0x08) && (xa_submode & 0x04) && !(xa_submode & 0x02);
|
||||
is_eof = (xa_submode & 0x80);
|
||||
if (xa_chan >= XA_MAX_CHANNELS) {
|
||||
VGM_LOG("XA: too many channels: %x\n", xa_chan);
|
||||
}
|
||||
|
||||
VGM_ASSERT((xa_submode & 0x01), "XA: end of audio at %lx\n", offset); /* rare, signals last sector [Tetris (CD-i)] */
|
||||
//;VGM_ASSERT(is_eof, "XA: eof at %lx\n", offset);
|
||||
//;VGM_ASSERT(!is_audio, "XA: not audio at %lx\n", offset);
|
||||
VGM_ASSERT((xa_submode & 0x01), "XA: end of audio at %x\n", offset); /* rare, signals last sector [Tetris (CD-i)] */
|
||||
//;VGM_ASSERT(is_eof, "XA: eof %02x%02x at %x\n", xa_file, xa_chan, offset); /* this sector still has data */
|
||||
//;VGM_ASSERT(!is_audio, "XA: not audio at %x\n", offset);
|
||||
|
||||
if (!is_audio) {
|
||||
offset += sector_size;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* detect file+channel change = sector of new subsong or existing subsong
|
||||
* (happens on every sector for interleaved XAs but only once for deint'd or video XAs) */
|
||||
if (xa_config != prev_config)
|
||||
{
|
||||
/* find if this sector/config belongs to known subsong that hasn't ended
|
||||
* (unsure if repeated configs+end flag is possible but probably in giant XAs) */
|
||||
cur_subsong = NULL;
|
||||
for (i = 0; i < subsongs_count; i++) { /* search algo could be improved, meh */
|
||||
if (subsongs[i].config == xa_config && !subsongs[i].end_flag) {
|
||||
cur_subsong = &subsongs[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* use info without submode to detect new subsongs */
|
||||
curr_info = curr_info & 0xFFFF00FF;
|
||||
|
||||
/* old subsong not found = add new to list */
|
||||
if (cur_subsong == NULL) {
|
||||
uint8_t xa_channel = get_u8(header + 0x01);
|
||||
|
||||
/* when file+channel changes mark prev subsong of the same channel as finished
|
||||
* (this ensures reused ids in bigfiles are handled properly, ex.: 0101... 0201... 0101...) */
|
||||
for (i = subsongs_count - 1; i >= 0; i--) {
|
||||
uint16_t subsong_config = subsongs[i].config;
|
||||
uint8_t subsong_channel = subsong_config & 0xFF;
|
||||
if (xa_config != subsong_config && xa_channel == subsong_channel) {
|
||||
subsongs[i].end_flag = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cur_subsong = &subsongs[subsongs_count];
|
||||
subsongs_count++;
|
||||
if (subsongs_count >= XA_SUBSONG_MAX) goto fail;
|
||||
|
||||
cur_subsong->config = xa_config;
|
||||
cur_subsong->form2 = (xa_submode & 0x20);
|
||||
cur_subsong->start = offset;
|
||||
//cur_subsong->sectors = 0;
|
||||
//cur_subsong->end_flag = 0;
|
||||
|
||||
//;VGM_LOG("XA: new subsong %i with config %04x at %lx\n", subsongs_count, xa_config, offset);
|
||||
}
|
||||
|
||||
prev_config = xa_config;
|
||||
}
|
||||
else {
|
||||
if (cur_subsong == NULL) goto fail;
|
||||
/* changes for a current channel = new subsong */
|
||||
if (xa_subsongs[xa_chan].info != curr_info) {
|
||||
subsongs_count++;
|
||||
xa_subsongs[xa_chan].info = curr_info;
|
||||
xa_subsongs[xa_chan].subsong = subsongs_count;
|
||||
}
|
||||
|
||||
cur_subsong->sectors++;
|
||||
if (is_eof)
|
||||
cur_subsong->end_flag = 1;
|
||||
/* current channel is still from expected subsong */
|
||||
is_target = xa_subsongs[xa_chan].subsong == target_subsong;
|
||||
if (is_target) {
|
||||
if (target_start == 0xFFFFFFFFu)
|
||||
target_start = offset;
|
||||
target_sectors++;
|
||||
}
|
||||
|
||||
/* remove info (after handling sector) so next comparison for this channel adds a subsong */
|
||||
if (is_eof) {
|
||||
xa_subsongs[xa_chan].info = 0;
|
||||
xa_subsongs[xa_chan].subsong = 0;
|
||||
}
|
||||
|
||||
offset += sector_size;
|
||||
}
|
||||
|
||||
VGM_ASSERT(subsongs_count < 1, "XA: no audio found\n");
|
||||
VGM_ASSERT(subsongs_count < 1, "XA: no audio found\n"); /* probably not possible even in videos */
|
||||
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > subsongs_count || subsongs_count < 1) goto fail;
|
||||
if (target_sectors == 0) goto fail;
|
||||
|
||||
cur_subsong = &subsongs[target_subsong - 1];
|
||||
*p_stream_config = cur_subsong->config;
|
||||
*p_stream_offset = cur_subsong->start;
|
||||
*p_stream_size = cur_subsong->sectors * sector_size;
|
||||
*p_form2 = cur_subsong->form2;
|
||||
*p_stream_offset = target_start;
|
||||
*p_stream_size = target_sectors * sector_size;
|
||||
|
||||
//;VGM_LOG("XA: subsong config=%x, offset=%lx, size=%x, form2=%i\n", *p_stream_config, *p_stream_offset, *p_stream_size, *p_form2);
|
||||
//;VGM_LOG("XA: subsong offset=%x, size=%x\n", *p_stream_offset, *p_stream_size);
|
||||
|
||||
close_streamfile(sf_test);
|
||||
return subsongs_count;
|
||||
|
@ -74,7 +74,7 @@ static const uint32_t MASK_TABLE_MSB[33] = {
|
||||
static inline int bm_get(bitstream_t* ib, uint32_t bits, uint32_t* value) {
|
||||
uint32_t shift, pos, mask;
|
||||
uint64_t val; //TODO: could use u32 with some shift fiddling
|
||||
int i, bit_buf, bit_val, left;
|
||||
int left;
|
||||
|
||||
if (bits > 32 || ib->b_off + bits > ib->b_max)
|
||||
goto fail;
|
||||
@ -83,8 +83,10 @@ static inline int bm_get(bitstream_t* ib, uint32_t bits, uint32_t* value) {
|
||||
shift = ib->b_off % 8; /* bit sub-offset */
|
||||
|
||||
#if 0 //naive approach
|
||||
int bit_val, bit_buf;
|
||||
|
||||
val = 0;
|
||||
for (i = 0; i < bits; i++) {
|
||||
for (int i = 0; i < bits; i++) {
|
||||
bit_buf = (1U << (8-1-shift)) & 0xFF; /* bit check for buf */
|
||||
bit_val = (1U << (bits-1-i)); /* bit to set in value */
|
||||
|
||||
|
@ -523,6 +523,7 @@ init_vgmstream_t init_vgmstream_functions[] = {
|
||||
init_vgmstream_snds,
|
||||
init_vgmstream_adm2,
|
||||
init_vgmstream_nxof,
|
||||
init_vgmstream_gwb_gwd,
|
||||
|
||||
/* lower priority metas (no clean header identity, somewhat ambiguous, or need extension/companion file to identify) */
|
||||
init_vgmstream_scd_pcm,
|
||||
|
@ -703,6 +703,7 @@ typedef enum {
|
||||
meta_SQUEAKSAMPLE,
|
||||
meta_SNDS,
|
||||
meta_NXOF,
|
||||
meta_GWB_GWD,
|
||||
|
||||
} meta_t;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user