From 5a6963ba97377b25e48ee16c9f1ba2ef2b7d3022 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 14 Aug 2022 12:36:58 +0200 Subject: [PATCH] Redo .hxd+str [Fatal Frame 3 (PS2), Tokobot (PS2)] --- src/formats.c | 2 +- src/meta/meta.h | 2 +- src/meta/ps2_dxh.c | 177 +++++++++++++++++++++++++++++++-------------- src/vgmstream.c | 2 +- src/vgmstream.h | 2 +- 5 files changed, 126 insertions(+), 59 deletions(-) diff --git a/src/formats.c b/src/formats.c index 8a39b05c..8e9c54df 100644 --- a/src/formats.c +++ b/src/formats.c @@ -1049,7 +1049,7 @@ static const meta_info meta_info_list[] = { {meta_ACM, "InterPlay ACM Header"}, {meta_MUS_ACM, "InterPlay MUS ACM header"}, {meta_PS2_KCES, "Konami KCES Header"}, - {meta_PS2_DXH, "Tokobot Plus DXH Header"}, + {meta_HXD, "Tecmo HXD Header"}, {meta_VSV, "Square Enix .vsv Header"}, {meta_RIFF_WAVE_labl, "RIFF WAVE header with loop markers"}, {meta_RIFF_WAVE_smpl, "RIFF WAVE header with sample looping info"}, diff --git a/src/meta/meta.h b/src/meta/meta.h index 867d2b3a..6882e9b6 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -240,7 +240,7 @@ VGMSTREAM * init_vgmstream_acm(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ps2_kces(STREAMFILE * streamFile); -VGMSTREAM * init_vgmstream_ps2_dxh(STREAMFILE * streamFile); +VGMSTREAM* init_vgmstream_hxd(STREAMFILE* sf); VGMSTREAM * init_vgmstream_vsv(STREAMFILE * streamFile); diff --git a/src/meta/ps2_dxh.c b/src/meta/ps2_dxh.c index c9f38324..65e3fd1f 100644 --- a/src/meta/ps2_dxh.c +++ b/src/meta/ps2_dxh.c @@ -1,76 +1,143 @@ #include "meta.h" -#include "../util.h" +#include "../coding/coding.h" -/* DXH (from Tokobot Plus - Mysteries of the Karakuri) */ -VGMSTREAM * init_vgmstream_ps2_dxh(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - char filename[PATH_LIMIT]; - off_t start_offset; - int loop_flag = 0; - int channel_count; - /* check extension, case insensitive */ - streamFile->get_name(streamFile,filename,sizeof(filename)); - if (strcasecmp("dxh",filename_extension(filename))) goto fail; +/* HXD - from Tecmo games [Tokobot Plus (PS2), Fatal Frame 2/3 (PS2), Gallop Racer 2004 (PS2)] */ +VGMSTREAM* init_vgmstream_hxd(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + STREAMFILE* sf_body = NULL; + uint32_t stream_offset, header_size, stream_size, interleave, loop_start, loop_end; + int channels, loop_flag, bank, sample_rate; + int total_subsongs, target_subsong = sf->stream_index; - /* check header */ - if (read_32bitBE(0x00,streamFile) != 0x00445848) /* 0\DXH" */ + + /* checks */ + if (!is_id32be(0x00,sf, "\0DXH")) goto fail; - loop_flag = (read_32bitLE(0x50,streamFile)!=0); - channel_count = read_32bitLE(0x08,streamFile); - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; + /* .hxd: actual extension (filenames in companion files/exe) */ + if (!check_extensions(sf, "hxd")) + goto fail; - /* fill in the vital statistics */ - start_offset = 0x800; - vgmstream->channels = channel_count; - vgmstream->sample_rate = read_32bitLE(0x20,streamFile); - - if (read_32bitBE(0x54,streamFile) == 0) { - /* if (loop_flag) { */ - vgmstream->loop_start_sample = 0; - vgmstream->loop_end_sample = get_streamfile_size(streamFile)*28/16/channel_count; - vgmstream->num_samples = get_streamfile_size(streamFile)*28/16/channel_count; - /* } */ + /* 0x04: version? (0x1000) */ + total_subsongs = read_u32le(0x08,sf); + bank = read_u32le(0x0c,sf); + header_size = read_u32le(0x10,sf); + interleave = read_u32le(0x14,sf); /* 0 in banks */ + /* 0x18-1c: null */ - } else { - - if (loop_flag) { - vgmstream->loop_start_sample = (read_32bitLE(0x50,streamFile)*0x20)*28/16/channel_count; - vgmstream->loop_end_sample = (read_32bitLE(0x54,streamFile)*0x20)*28/16/channel_count; - vgmstream->num_samples = (read_32bitLE(0x54,streamFile)*0x20)*28/16/channel_count; + /* Reject incorrectly ripped files, as .hxd is the header and data is always separate. + * Rips with header+data pasted were allowed before, but since bigfiles may store + * data first then header, audio could play wrong for no apparent reason. */ + if (header_size != get_streamfile_size(sf)) + goto fail; - } -} - - vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = read_32bitLE(0x14,streamFile); - vgmstream->meta_type = meta_PS2_DXH; - /* open the file for reading */ + /* .hxd has 2 modes, banks with N subsongs or bgm with N channels. In both cases + * there is header info per stream, though bgm just repeats values. */ + if (bank) { + channels = 1; + } + else { + channels = total_subsongs; /* seen 1/2 */ + total_subsongs = 1; + } + if (target_subsong == 0) target_subsong = 1; + if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; + { - int i; - STREAMFILE * file; - file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); - if (!file) goto fail; - for (i=0;ich[i].streamfile = file; + uint32_t info_offset = 0x20 + (target_subsong - 1) * 0x1c; + uint32_t flags; - vgmstream->ch[i].channel_start_offset= - vgmstream->ch[i].offset=start_offset+ - vgmstream->interleave_block_size*i; + sample_rate = read_s32le(info_offset + 0x00,sf); + stream_offset = read_u32le(info_offset + 0x04,sf); + /* 0x08: pitch? (44100=0x0EB3, 32000=0x0AAA, 22050=0759, 16000=0x5505, etc) */ + /* 0x0a: volume? (usually ~0x64, up to ~0x7F) */ + /* 0x0c: config? (pan, etc?) */ + flags = read_u16le(info_offset + 0x10,sf); + /* 0x12: ? (seen in FF2 XB) */ + loop_start = read_u32le(info_offset + 0x14,sf) * 0x20; + loop_end = read_u32le(info_offset + 0x18,sf) * 0x20; + /* flags: + * - 0x20: loop flag + * - 0x10: ? (seen in banks) + * - 0x02: ? (common in streams, not always) + * - 0x01: ? (sometimes in streams) */ + loop_flag = flags & 0x20; + + /* different games use different combos */ + if (bank) { + sf_body = open_streamfile_by_ext(sf, "bd"); /* Gallop Racer */ + if (!sf_body) { + sf_body = open_streamfile_by_ext(sf, "str"); /* just in case */ + } + if (!sf_body) goto fail; + } + else { + sf_body = open_streamfile_by_ext(sf, "str"); /* Fatal Frame 2/3, Gallop Racer */ + if (!sf_body) { + sf_body = open_streamfile_by_ext(sf, "at3"); /* Tokobot Plus (still ADPCM) */ + } + if (!sf_body) goto fail; + } + + /* size is not in the header (probably just leaves it to PS-ADPCM's EOF markets) */ + if (bank && target_subsong < total_subsongs) { + /* find next usable offset (sometimes offsets repeat) */ //TODO: meh + int i; + uint32_t next_offset = 0; + for (i = target_subsong; i < total_subsongs; i++) { + next_offset = read_u32le(0x20 + i * 0x1c + 0x04,sf); + + if (next_offset > stream_offset) + break; + } + if (i == total_subsongs) + next_offset = get_streamfile_size(sf_body); + if (!next_offset) + goto fail; + + stream_size = next_offset - stream_offset; + } + else { + stream_size = get_streamfile_size(sf_body) - stream_offset; } } + /* Xbox versions of Tecmo games use RIFF (music) or WBND/.xwb (sfx) in the body. Probably a quick + * hack since they reuse extensions like .pss for movies too, for now reject. */ + if (read_u32be(0x00, sf_body) != 0) + goto fail; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channels, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_HXD; + vgmstream->sample_rate = sample_rate; + vgmstream->num_streams = total_subsongs; + vgmstream->stream_size = stream_size; + + vgmstream->num_samples = ps_bytes_to_samples(stream_size, channels); + vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channels); + vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end, channels); + if (!vgmstream->loop_end_sample) + vgmstream->loop_end_sample = vgmstream->num_samples; + + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = interleave; + + if (!vgmstream_open_stream(vgmstream, sf_body, stream_offset)) + goto fail; + + close_streamfile(sf_body); return vgmstream; - /* clean up anything we may have opened */ fail: - if (vgmstream) close_vgmstream(vgmstream); + close_streamfile(sf_body); + close_vgmstream(vgmstream); return NULL; } diff --git a/src/vgmstream.c b/src/vgmstream.c index 3e79a975..921953df 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -95,7 +95,7 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = { init_vgmstream_acm, init_vgmstream_mus_acm, init_vgmstream_ps2_kces, - init_vgmstream_ps2_dxh, + init_vgmstream_hxd, init_vgmstream_vsv, init_vgmstream_scd_pcm, init_vgmstream_ps2_pcm, diff --git a/src/vgmstream.h b/src/vgmstream.h index b4674f61..ae3e5a9f 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -389,7 +389,7 @@ typedef enum { meta_BG00, /* Ibara, Mushihimesama */ meta_PS2_RSTM, /* Midnight Club 3 */ meta_PS2_KCES, /* Dance Dance Revolution */ - meta_PS2_DXH, /* Tokobot Plus - Myteries of the Karakuri */ + meta_HXD, meta_VSV, meta_SCD_PCM, /* Lunar - Eternal Blue */ meta_PS2_PCM, /* Konami KCEJ East: Ephemeral Fantasia, Yu-Gi-Oh! The Duelists of the Roses, 7 Blades */