diff --git a/src/formats.c b/src/formats.c
index 720b22ea..be29f374 100644
--- a/src/formats.c
+++ b/src/formats.c
@@ -73,6 +73,7 @@ static const char* extension_list[] = {
"b1s",
"baf",
"baka",
+ "bank",
"bar",
"bcstm",
"bcwav",
diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj
index 99b9fafd..577084f7 100644
--- a/src/libvgmstream.vcproj
+++ b/src/libvgmstream.vcproj
@@ -608,10 +608,14 @@
RelativePath=".\meta\fsb.c"
>
-
-
+
+
+
+
diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj
index 25ceb131..ff20d5ef 100644
--- a/src/libvgmstream.vcxproj
+++ b/src/libvgmstream.vcxproj
@@ -275,6 +275,7 @@
+
diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters
index ef23711f..ce1ec309 100644
--- a/src/libvgmstream.vcxproj.filters
+++ b/src/libvgmstream.vcxproj.filters
@@ -391,6 +391,9 @@
meta\Source Files
+
+ meta\Source Files
+
meta\Source Files
diff --git a/src/meta/fsb5_fev.c b/src/meta/fsb5_fev.c
new file mode 100644
index 00000000..74b1dba4
--- /dev/null
+++ b/src/meta/fsb5_fev.c
@@ -0,0 +1,54 @@
+#include "meta.h"
+#include "../coding/coding.h"
+
+/* FEV+FSB5 container [Shantae: Half-Genie Hero (Switch)] */
+VGMSTREAM * init_vgmstream_fsb5_fev_bank(STREAMFILE *streamFile) {
+ VGMSTREAM * vgmstream = NULL;
+ STREAMFILE *temp_streamFile = NULL;
+ off_t subfile_offset, chunk_offset, first_offset = 0x0c;
+ size_t subfile_size, chunk_size;
+
+
+ /* checks */
+ if (!check_extensions(streamFile, "bank"))
+ goto fail;
+
+ if (read_32bitBE(0x00,streamFile) != 0x52494646) /* "RIFF" */
+ goto fail;
+ if (read_32bitBE(0x08,streamFile) != 0x46455620) /* "FEV " */
+ goto fail;
+
+ /* .fev is an event format referencing various external .fsb, but FMOD can bake .fev and .fsb to
+ * form a .bank, which is the format we support here (regular .fev is complex and not very interesting).
+ * Format is RIFF with FMT (main), LIST (config) and SND (FSB5 data), we want the FSB5 offset inside LIST */
+ if (!find_chunk_le(streamFile, 0x4C495354,first_offset,0, &chunk_offset,NULL)) /* "LIST" */
+ goto fail;
+
+ if (read_32bitBE(chunk_offset+0x00,streamFile) != 0x50524F4A || /* "PROJ" */
+ read_32bitBE(chunk_offset+0x04,streamFile) != 0x424E4B49) /* "BNKI" */
+ goto fail; /* event .fev has "OBCT" instead of "BNKI" */
+
+ /* inside BNKI is a bunch of LIST each with event subchunks and finally the fsb offset */
+ first_offset = chunk_offset + 0x04;
+ if (!find_chunk_le(streamFile, 0x534E4448,first_offset,0, &chunk_offset,&chunk_size)) /* "SNDH" */
+ goto fail;
+
+ if (chunk_size != 0x0c)
+ goto fail; /* assuming only one FSB5 is possible */
+ subfile_offset = read_32bitLE(chunk_offset+0x04,streamFile);
+ subfile_size = read_32bitLE(chunk_offset+0x08,streamFile);
+
+
+ temp_streamFile = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, "fsb");
+ if (!temp_streamFile) goto fail;
+
+ vgmstream = init_vgmstream_fsb5(temp_streamFile);
+ close_streamfile(temp_streamFile);
+
+ return vgmstream;
+
+fail:
+ close_streamfile(temp_streamFile);
+ close_vgmstream(vgmstream);
+ return NULL;
+}
diff --git a/src/meta/meta.h b/src/meta/meta.h
index 631eac00..a1111095 100644
--- a/src/meta/meta.h
+++ b/src/meta/meta.h
@@ -852,4 +852,6 @@ VGMSTREAM * init_vgmstream_xwma_konami(STREAMFILE* streamFile);
VGMSTREAM * init_vgmstream_9tav(STREAMFILE* streamFile);
+VGMSTREAM * init_vgmstream_fsb5_fev_bank(STREAMFILE * streamFile);
+
#endif /*_META_H*/
diff --git a/src/meta/ps2_ads.c b/src/meta/ps2_ads.c
index fc00f467..75559c6b 100644
--- a/src/meta/ps2_ads.c
+++ b/src/meta/ps2_ads.c
@@ -35,7 +35,7 @@ VGMSTREAM * init_vgmstream_ps2_ads(STREAMFILE *streamFile) {
{
codec = read_32bitLE(0x08,streamFile);
sample_rate = read_32bitLE(0x0C,streamFile);
- channel_count = read_32bitLE(0x10,streamFile); /* up to 4 [Eve of Extinction (PS2)]*/
+ channel_count = read_32bitLE(0x10,streamFile); /* up to 4 [Eve of Extinction (PS2)] */
interleave = read_32bitLE(0x14,streamFile); /* set even when mono */
@@ -150,7 +150,7 @@ VGMSTREAM * init_vgmstream_ps2_ads(STREAMFILE *streamFile) {
loop_start_sample = loop_start / 2 / channel_count;
is_loop_samples = 1;
}
- else if ((loop_start % 0x800 == 0) && loop_start > 0) {/* sector-aligned, min/0 is 0x800 */
+ else if ((loop_start % 0x800 == 0) && loop_start > 0) { /* sector-aligned, min/0 is 0x800 */
/* cavia games: loop_start is offset [Drakengard 1/2, GITS: Stand Alone Complex] */
/* offset is absolute from the "cavia stream format" container that adjusts ADS start */
loop_flag = 1;
@@ -192,12 +192,18 @@ VGMSTREAM * init_vgmstream_ps2_ads(STREAMFILE *streamFile) {
loop_start_offset = loop_start * 0x20;
loop_end_offset = loop_end * 0x20;
}
- else if (loop_end <= body_size / 0x20 && coding_type == coding_PSX) { /* close to body_size */
+ else if (loop_end <= body_size / 0x20 && coding_type == coding_PSX) {
/* various games: loops is address * 0x20 [Fire Pro Wrestling Returns, A.C.E. - Another Century's Episode] */
loop_flag = 1;
loop_start_offset = loop_start * 0x20;
loop_end_offset = loop_end * 0x20;
}
+ else if (loop_end <= body_size / 0x10 && coding_type == coding_PSX
+ && (read_32bitBE(0x28 + loop_end*0x10 + 0x10 + 0x00, streamFile) == 0x00077777 ||
+ read_32bitBE(0x28 + loop_end*0x10 + 0x20 + 0x00, streamFile) == 0x00077777)) {
+ /* not-quite-looping sfx, ending with a "non-looping PS-ADPCM end frame" [Kono Aozora ni Yakusoku, Chanter] */
+ loop_flag = 0;
+ }
else if ((loop_end > body_size / 0x20 && coding_type == coding_PSX) ||
(loop_end > body_size / 0x70 && coding_type == coding_PCM16LE)) {
/* various games: loops in samples [Eve of Extinction, Culdcept, WWE Smackdown! 3] */
diff --git a/src/vgmstream.c b/src/vgmstream.c
index 4e6c327d..1cd0be53 100644
--- a/src/vgmstream.c
+++ b/src/vgmstream.c
@@ -479,6 +479,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_msf_konami,
init_vgmstream_xwma_konami,
init_vgmstream_9tav,
+ init_vgmstream_fsb5_fev_bank,
/* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */
init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */