From b215eb26fa4da6c4e6861c66fc1eb51c35c597d4 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 18 Jan 2025 21:21:27 +0100 Subject: [PATCH] Add cavia .hd2+bd [Drakengard 1/2 (PS2), Ghost in the Shell: SAC (PS2)] --- src/coding/coding.h | 7 ++-- src/coding/psx_decoder.c | 57 +++++++++++++++++++---------- src/formats.c | 4 ++- src/libvgmstream.vcxproj | 1 + src/libvgmstream.vcxproj.filters | 3 ++ src/meta/meta.h | 4 ++- src/meta/xabp.c | 62 ++++++++++++++++++++++++++++++++ src/vgmstream_init.c | 1 + src/vgmstream_types.h | 1 + 9 files changed, 117 insertions(+), 23 deletions(-) create mode 100644 src/meta/xabp.c diff --git a/src/coding/coding.h b/src/coding/coding.h index 85f5ebb6..4f8abeac 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -109,12 +109,13 @@ int32_t pcm8_bytes_to_samples(size_t bytes, int channels); void decode_psx(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int is_badflags, int config); void decode_psx_configurable(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int frame_size, int config); void decode_psx_pivotal(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int frame_size); -int ps_find_loop_offsets(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t* out_loop_start, int32_t* out_loop_end); -int ps_find_loop_offsets_full(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t* out_loop_start, int32_t* out_loop_end); +bool ps_find_loop_offsets(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t* out_loop_start, int32_t* out_loop_end); +bool ps_find_loop_offsets_full(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t* out_loop_start, int32_t* out_loop_end); +bool ps_find_stream_info(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t* p_loop_start, int32_t* p_loop_end, uint32_t* p_stream_size); size_t ps_find_padding(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int discard_empty); size_t ps_bytes_to_samples(size_t bytes, int channels); size_t ps_cfg_bytes_to_samples(size_t bytes, size_t frame_size, int channels); -int ps_check_format(STREAMFILE* sf, off_t offset, size_t max); +bool ps_check_format(STREAMFILE* sf, off_t offset, size_t max); /* psv_decoder */ diff --git a/src/coding/psx_decoder.c b/src/coding/psx_decoder.c index bf620b4a..6adc9713 100644 --- a/src/coding/psx_decoder.c +++ b/src/coding/psx_decoder.c @@ -250,39 +250,43 @@ void decode_psx_pivotal(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channels * - 0x7 (0111): End marker and don't decode * - 0x8+(1NNN): Not valid */ -static int ps_find_loop_offsets_internal(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t * p_loop_start, int32_t * p_loop_end, int config) { +static int ps_find_stream_info_internal(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t* p_loop_start, int32_t* p_loop_end, uint32_t* p_stream_size, int config) { int num_samples = 0, loop_start = 0, loop_end = 0; - int loop_start_found = 0, loop_end_found = 0; + bool loop_start_found = false, loop_end_found = false; off_t offset = start_offset; off_t max_offset = start_offset + data_size; size_t interleave_consumed = 0; - int detect_full_loops = config & 1; + bool detect_full_loops = config & 1; + bool stop_on_null = config & 2; + int frames = 0; if (data_size == 0 || channels == 0 || (channels > 1 && interleave == 0)) return 0; while (offset < max_offset) { - uint8_t flag = read_u8(offset+0x01, sf) & 0x0F; /* lower nibble only (for HEVAG) */ + uint16_t header = read_u16be(offset+0x00, sf); + uint8_t flag = header & 0x0F; /* lower nibble only (for HEVAG) */; + frames++; /* theoretically possible and would use last 0x06 */ VGM_ASSERT_ONCE(loop_start_found && flag == 0x06, "PS LOOPS: multiple loop start found at %x\n", (uint32_t)offset); if (flag == 0x06 && !loop_start_found) { loop_start = num_samples; /* loop start before this frame */ - loop_start_found = 1; + loop_start_found = true; } if (flag == 0x03 && !loop_end) { loop_end = num_samples + 28; /* loop end after this frame */ - loop_end_found = 1; + loop_end_found = true; /* ignore strange case in Commandos (PS2), has many loop starts and ends */ if (channels == 1 && offset + 0x10 < max_offset && (read_u8(offset + 0x11, sf) & 0x0F) == 0x06) { loop_end = 0; - loop_end_found = 0; + loop_end_found = false; } if (loop_start_found && loop_end_found) @@ -296,7 +300,7 @@ static int ps_find_loop_offsets_internal(STREAMFILE* sf, off_t start_offset, siz if (flag == 0x01 && detect_full_loops) { static const uint8_t eof[0x10] = {0xFF,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; uint8_t buf[0x10]; - uint8_t hdr = read_u8(offset + 0x00, sf); + uint8_t hdr = (header >> 8) & 0xFF; int read = read_streamfile(buf, offset+0x10, sizeof(buf), sf); if (read > 0 @@ -310,8 +314,8 @@ static int ps_find_loop_offsets_internal(STREAMFILE* sf, off_t start_offset, siz if (hdr == buf[0] && memcmp(buf+1, eof+1, sizeof(buf) - 1) == 0) { loop_start = 28; /* skip first frame as it's null in PS-ADPCM */ loop_end = num_samples + 28; /* loop end after this frame */ - loop_start_found = 1; - loop_end_found = 1; + loop_start_found = true; + loop_end_found = true; //;VGM_LOG("PS LOOPS: full loop found\n"); break; } @@ -326,8 +330,19 @@ static int ps_find_loop_offsets_internal(STREAMFILE* sf, off_t start_offset, siz interleave_consumed += 0x10; if (interleave_consumed == interleave) { interleave_consumed = 0; - offset += interleave*(channels - 1); + offset += interleave * (channels - 1); } + + // stream done flag + if (stop_on_null && offset > start_offset && (flag & 0x01)) { + frames++; + break; + } + } + + if (p_stream_size) { + // uses frames rather than offsets to take interleave into account + *p_stream_size = frames * 0x10 * channels; } VGM_ASSERT(loop_start_found && !loop_end_found, "PS LOOPS: found loop start but not loop end\n"); @@ -341,15 +356,21 @@ static int ps_find_loop_offsets_internal(STREAMFILE* sf, off_t start_offset, siz return 1; } + return 0; /* no loop */ } -int ps_find_loop_offsets(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t* p_loop_start, int32_t* p_loop_end) { - return ps_find_loop_offsets_internal(sf, start_offset, data_size, channels, interleave, p_loop_start, p_loop_end, 0); +//TODO: rename as it returns samples +bool ps_find_loop_offsets(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t* p_loop_start, int32_t* p_loop_end) { + return ps_find_stream_info_internal(sf, start_offset, data_size, channels, interleave, p_loop_start, p_loop_end, NULL, 0x00); } -int ps_find_loop_offsets_full(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t* p_loop_start, int32_t* p_loop_end) { - return ps_find_loop_offsets_internal(sf, start_offset, data_size, channels, interleave, p_loop_start, p_loop_end, 1); +bool ps_find_loop_offsets_full(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t* p_loop_start, int32_t* p_loop_end) { + return ps_find_stream_info_internal(sf, start_offset, data_size, channels, interleave, p_loop_start, p_loop_end, NULL, 0x01); +} + +bool ps_find_stream_info(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t* p_loop_start, int32_t* p_loop_end, uint32_t* p_stream_size) { + return ps_find_stream_info_internal(sf, start_offset, data_size, channels, interleave, p_loop_start, p_loop_end, p_stream_size, 0x02); } size_t ps_find_padding(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int discard_empty) { @@ -440,7 +461,7 @@ size_t ps_cfg_bytes_to_samples(size_t bytes, size_t frame_size, int channels) { } /* test PS-ADPCM frames for correctness */ -int ps_check_format(STREAMFILE* sf, off_t offset, size_t max) { +bool ps_check_format(STREAMFILE* sf, off_t offset, size_t max) { off_t max_offset = offset + max; if (max_offset > get_streamfile_size(sf)) max_offset = get_streamfile_size(sf); @@ -450,10 +471,10 @@ int ps_check_format(STREAMFILE* sf, off_t offset, size_t max) { uint8_t flags = read_8bit(offset+0x01,sf); if (predictor > 5 || flags > 7) { - return 0; + return false; } offset += 0x10; } - return 1; + return true; } diff --git a/src/formats.c b/src/formats.c index eed95a1b..39f00422 100644 --- a/src/formats.c +++ b/src/formats.c @@ -221,9 +221,10 @@ static const char* extension_list[] = { "h4m", "hab", + "hbd", "hca", "hd", - "hbd", + "hd2", "hd3", "hdr", "hdt", @@ -1457,6 +1458,7 @@ static const meta_info meta_info_list[] = { {meta_KA1A, "Koei Tecmo KA1A header"}, {meta_HD_BD, "Sony HD+BD header"}, {meta_PPHD, "Sony PPHD header"}, + {meta_XABP, "cavia XABp header"}, }; void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t out_size) { diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index 16509aef..32cd2276 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -776,6 +776,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index 351cee80..ba57fe07 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -2158,6 +2158,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files diff --git a/src/meta/meta.h b/src/meta/meta.h index 244edc67..90c0b363 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -1019,4 +1019,6 @@ VGMSTREAM* init_vgmstream_hd_bd(STREAMFILE* sf); VGMSTREAM* init_vgmstream_pphd(STREAMFILE* sf); -#endif /*_META_H*/ +VGMSTREAM* init_vgmstream_xabp(STREAMFILE* sf); + +#endif diff --git a/src/meta/xabp.c b/src/meta/xabp.c new file mode 100644 index 00000000..c1c28c4a --- /dev/null +++ b/src/meta/xabp.c @@ -0,0 +1,62 @@ +#include "meta.h" +#include "../coding/coding.h" +#include "../util/meta_utils.h" + + + /* XABp - cavia PS2 bank format [Drakengard 1/2 (PS2), Ghost in the Shell: SAC (PS2)] */ +VGMSTREAM* init_vgmstream_xabp(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + + + /* checks */ + if (!is_id32be(0x00,sf, "pBAX")) + return NULL; + + // .hd2+bd: from bigfiles + if (!check_extensions(sf, "hd2")) + return NULL; + + meta_header_t h = { + .meta = meta_XABP, + }; + h.target_subsong = sf->stream_index; + if (h.target_subsong == 0) + h.target_subsong = 1; + + // cavia's bank format inspired by .hd+bd + uint32_t bd_size = read_u32le(0x04,sf); + // 0x08: null + h.total_subsongs = read_s16le(0x0c,sf); + // 0x0e: always 0x0010? + + uint32_t head_offset = 0x10 + 0x20 * (h.target_subsong - 1); + // 00: file id? + h.sample_rate = read_s16le(head_offset + 0x16,sf); + h.stream_offset = read_u32le(head_offset + 0x18, sf); + // others: config? (can't make sense of them, don't seem quite like sizes/flags/etc) + + h.channels = 1; + + h.coding = coding_PSX; + h.layout = layout_none; + h.open_stream = true; + + h.sf_head = sf; + h.sf_body = open_streamfile_by_ext(sf,"bd"); + if (!h.sf_body) goto fail; + + // Entries/offsets aren't ordered .bd not it seems to have sizes (maybe mixes notes+streams into one) + // Since PS-ADPCM is wired to play until end frame end or loop, it's probably designed like that. + // It also repeats entries (different ID but same config) but for now just prints it as is. + h.loop_flag = ps_find_stream_info(h.sf_body, h.stream_offset, bd_size - h.stream_offset, h.channels, h.interleave, &h.loop_start, &h.loop_end, &h.stream_size); + h.num_samples = ps_bytes_to_samples(h.stream_size, h.channels); +VGM_LOG("s=%x, %x\n", h.stream_offset, h.stream_size); + + vgmstream = alloc_metastream(&h); + close_streamfile(h.sf_body); + return vgmstream; +fail: + close_streamfile(h.sf_body); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/vgmstream_init.c b/src/vgmstream_init.c index 9d07064f..b6a54e37 100644 --- a/src/vgmstream_init.c +++ b/src/vgmstream_init.c @@ -513,6 +513,7 @@ init_vgmstream_t init_vgmstream_functions[] = { init_vgmstream_ka1a, init_vgmstream_hd_bd, init_vgmstream_pphd, + init_vgmstream_xabp, /* lower priority metas (no clean header identity, somewhat ambiguous, or need extension/companion file to identify) */ init_vgmstream_agsc, diff --git a/src/vgmstream_types.h b/src/vgmstream_types.h index 8a1a8e21..71c70822 100644 --- a/src/vgmstream_types.h +++ b/src/vgmstream_types.h @@ -714,6 +714,7 @@ typedef enum { meta_KA1A, meta_HD_BD, meta_PPHD, + meta_XABP, } meta_t;