Add cavia .hd2+bd [Drakengard 1/2 (PS2), Ghost in the Shell: SAC (PS2)]

This commit is contained in:
bnnm 2025-01-18 21:21:27 +01:00
parent 6ad192ec39
commit b215eb26fa
9 changed files with 117 additions and 23 deletions

View File

@ -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 */

View File

@ -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;
}

View File

@ -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) {

View File

@ -776,6 +776,7 @@
<ClCompile Include="meta\wxd_wxh.c" />
<ClCompile Include="meta\xa.c" />
<ClCompile Include="meta\xa2_acclaim.c" />
<ClCompile Include="meta\xabp.c" />
<ClCompile Include="meta\xau.c" />
<ClCompile Include="meta\xau_konami.c" />
<ClCompile Include="meta\xavs.c" />

View File

@ -2158,6 +2158,9 @@
<ClCompile Include="meta\xa2_acclaim.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\xabp.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\xau.c">
<Filter>meta\Source Files</Filter>
</ClCompile>

View File

@ -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

62
src/meta/xabp.c Normal file
View File

@ -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;
}

View File

@ -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,

View File

@ -714,6 +714,7 @@ typedef enum {
meta_KA1A,
meta_HD_BD,
meta_PPHD,
meta_XABP,
} meta_t;