diff --git a/README.md b/README.md
index d091f247..77ca45f1 100644
--- a/README.md
+++ b/README.md
@@ -120,8 +120,9 @@ handling.
A few extensions that vgmstream supports clash with common ones. Since players
like foobar or Winamp don't react well to that, they may be renamed for
vgmstream (mainly to get looping in some cases).
-- .ac3 to .lac3
- .aac to .laac
+- .ac3 to .lac3
+- .aif to .aiffl or .aifcl
- .asf to .sng (EA formats)
- .mp4 to .lmp4
- .ogg to .logg
diff --git a/src/formats.c b/src/formats.c
index 1ffc6485..4ce2bb39 100644
--- a/src/formats.c
+++ b/src/formats.c
@@ -28,9 +28,11 @@ static const char* extension_list[] = {
"afc",
"agsc",
"ahx",
- "aifc",
- "aifcl",
+ //"aif", //common
+ "aifc", //common?
+ "aifcl", //fake extension, for AIF???
//"aiff", //common
+ "aiffl", //fake extension, for AIF???
"aix",
"akb",
"al2",
@@ -137,6 +139,7 @@ static const char* extension_list[] = {
"gtd",
"gwm",
+ "h4m",
"hca",
"hgc1",
"his",
@@ -621,6 +624,7 @@ static const layout_info layout_info_list[] = {
{layout_blocked_ea_wve_au00, "blocked (EA WVE au00)"},
{layout_blocked_ea_wve_ad10, "blocked (EA WVE Ad10)"},
{layout_blocked_sthd, "blocked (STHD)"},
+ {layout_blocked_h4m, "blocked (H4M)"},
};
static const meta_info meta_info_list[] = {
@@ -687,8 +691,8 @@ static const meta_info meta_info_list[] = {
{meta_SADL, "Procyon Studio SADL header"},
{meta_PS2_BMDX, "Beatmania .bmdx header"},
{meta_DSP_WSI, "Alone in the Dark .WSI header"},
- {meta_AIFC, "Audio Interchange File Format AIFF-C"},
- {meta_AIFF, "Audio Interchange File Format"},
+ {meta_AIFC, "Apple AIFF-C (Audio Interchange File Format) header"},
+ {meta_AIFF, "Apple AIFF (Audio Interchange File Format) header"},
{meta_STR_SNDS, ".str SNDS SHDR chunk"},
{meta_WS_AUD, "Westwood Studios .aud header"},
{meta_WS_AUD_old, "Westwood Studios .aud (old) header"},
@@ -798,7 +802,7 @@ static const meta_info meta_info_list[] = {
{meta_YDSP, "Yuke's DSP (YDSP) Header"},
{meta_MSVP, "MSVP Header"},
{meta_NGC_SSM, "SSM DSP Header"},
- {meta_PS2_JOE, "Disney/Pixar JOE Header"},
+ {meta_PS2_JOE, "Asobo Studio .JOE header"},
{meta_VGS, "Guitar Hero VGS Header"},
{meta_DC_DCSW_DCS, "Evil Twin DCS file with helper"},
{meta_WII_SMP, "SMP DSP Header"},
@@ -1018,6 +1022,7 @@ static const meta_info meta_info_list[] = {
{meta_TA_AAC_VITA, "tri-Ace AAC (Vita) header"},
{meta_OGG_GWM, "Ogg Vorbis (GWM header)"},
{meta_DSP_SADF, "Procyon Studio SADF header"},
+ {meta_H4M, "Hudson HVQM4 header"},
#ifdef VGM_USE_FFMPEG
{meta_FFmpeg, "FFmpeg supported file format"},
diff --git a/src/layout/blocked.c b/src/layout/blocked.c
index d59417d8..240e58c1 100644
--- a/src/layout/blocked.c
+++ b/src/layout/blocked.c
@@ -202,6 +202,9 @@ static void block_update(VGMSTREAM * vgmstream) {
case layout_blocked_sthd:
block_update_sthd(vgmstream->next_block_offset,vgmstream);
break;
+ case layout_blocked_h4m:
+ block_update_h4m(vgmstream->next_block_offset,vgmstream);
+ break;
default:
break;
}
diff --git a/src/layout/blocked_h4m.c b/src/layout/blocked_h4m.c
new file mode 100644
index 00000000..14f64cc8
--- /dev/null
+++ b/src/layout/blocked_h4m.c
@@ -0,0 +1,102 @@
+#include "layout.h"
+#include "../coding/coding.h"
+
+
+/* H4M video blocks with audio frames, based on h4m_audio_decode */
+void block_update_h4m(off_t block_offset, VGMSTREAM * vgmstream) {
+ STREAMFILE* streamFile = vgmstream->ch[0].streamfile;
+ int i;
+ size_t block_size, block_samples;
+
+
+ /* use full_block_size as counter (a bit hacky but whatevs) */
+ if (vgmstream->full_block_size <= 0) {
+ /* new full block */
+ /* 0x00: last_full_block_size */
+ uint32_t full_block_size = read_32bitBE(block_offset+0x04, streamFile);
+ /* 0x08: vid_frame_count */
+ /* 0x0c: aud_frame_count */
+ /* 0x10: block_header_unk (0x01000000, except 0 in a couple of Bomberman Jetters files) */
+
+ vgmstream->full_block_size = full_block_size; /* not including 0x14 block header */
+ block_size = 0x14; /* skip header and point to first frame in full block */
+ block_samples = 0; /* signal new block_update_h4m */
+ }
+ else {
+ /* new audio or video frames in the current full block */
+ uint16_t frame_type = read_16bitBE(block_offset+0x00, streamFile);
+ uint16_t frame_format = read_16bitBE(block_offset+0x02, streamFile);
+ uint32_t frame_size = read_32bitBE(block_offset+0x04, streamFile); /* not including 0x08 frame header */
+
+
+ if (frame_type == 0x00) {
+ /* HVQM4_AUDIO (there are more checks with frame_format but not too relevant for vgmstream) */
+ uint32_t frame_samples = read_32bitBE(block_offset+0x08, streamFile);
+ size_t block_skip;
+
+ if (vgmstream->codec_version & 0x80) {
+ frame_samples /= 2; /* ??? */
+ }
+
+ block_skip = 0x08 + 0x04;
+ block_size = 0x08 + frame_size;
+ block_samples = frame_samples;
+
+
+ /* skip data from other audio tracks */
+ if (vgmstream->num_streams) {
+ uint32_t audio_bytes = frame_size - 0x04;
+ block_skip += (audio_bytes / vgmstream->num_streams) * vgmstream->stream_index;
+ }
+
+ //VGM_ASSERT(frame_format < 1 && frame_format > 3, "H4M: unknown frame_format %x at %lx\n", frame_format, block_offset);
+ VGM_ASSERT(frame_format == 1, "H4M: unknown frame_format %x at %lx\n", frame_format, block_offset);
+
+ //todo handle in the decoder?
+ //todo right channel first?
+ /* get ADPCM hist (usually every new block) */
+ for (i = 0; i < vgmstream->channels; i++) {
+ if (frame_format == 1) { /* combined hist+index */
+ vgmstream->ch[i].adpcm_history1_32 = read_16bitBE(block_offset + block_skip + 0x02*i + 0x00,streamFile) & 0xFFFFFF80;
+ vgmstream->ch[i].adpcm_step_index = read_8bit(block_offset + block_skip + 0x02*i + 0x01,streamFile) & 0x7f;
+ vgmstream->ch[i].offset = block_offset + block_skip + 0x02*vgmstream->channels;
+ }
+ else if (frame_format == 3) { /* separate hist+index */
+ vgmstream->ch[i].adpcm_history1_32 = read_16bitBE(block_offset + block_skip + 0x03*i + 0x00,streamFile);
+ vgmstream->ch[i].adpcm_step_index = read_8bit(block_offset + block_skip + 0x03*i + 0x02,streamFile);
+ vgmstream->ch[i].offset = block_offset + block_skip + 0x03*vgmstream->channels;
+ }
+ else if (frame_format == 2) { /* no hist/index */
+ vgmstream->ch[i].offset = block_offset + block_skip;
+ }
+ }
+
+ //todo temp hack, at it must write header sample and ignore the last nibble to get fully correct output
+ if (frame_format == 1 || frame_format == 3) {
+ block_samples--;
+ }
+ }
+ else {
+ block_size = 0x08 + frame_size;
+ block_samples = 0; /* signal new block_update_h4m */
+ }
+
+ vgmstream->full_block_size -= block_size;
+ }
+
+ /* EOF check, there is some footer/garbage at the end */
+ if (block_offset == get_streamfile_size(streamFile)
+ || block_offset + block_size > get_streamfile_size(streamFile)) {
+ //block_samples = -1; /* signal end block */
+ vgmstream->full_block_size = 0;
+ vgmstream->current_block_samples = 0;
+ vgmstream->current_block_offset = get_streamfile_size(streamFile);
+ vgmstream->next_block_offset = get_streamfile_size(streamFile);
+ return;
+ }
+
+ vgmstream->current_block_samples = block_samples;
+ vgmstream->current_block_offset = block_offset;
+ vgmstream->next_block_offset = block_offset + block_size;
+}
+
diff --git a/src/layout/layout.h b/src/layout/layout.h
index a8fc7834..98090476 100644
--- a/src/layout/layout.h
+++ b/src/layout/layout.h
@@ -43,6 +43,7 @@ void block_update_xvag_subsong(off_t block_offset, VGMSTREAM * vgmstream);
void block_update_ea_wve_au00(off_t block_offset, VGMSTREAM * vgmstream);
void block_update_ea_wve_ad10(off_t block_offset, VGMSTREAM * vgmstream);
void block_update_sthd(off_t block_offset, VGMSTREAM * vgmstream);
+void block_update_h4m(off_t block_offset, VGMSTREAM * vgmstream);
/* other layouts */
void render_vgmstream_interleave(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream);
diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj
index 3d5993c0..87885a02 100644
--- a/src/libvgmstream.vcproj
+++ b/src/libvgmstream.vcproj
@@ -483,6 +483,10 @@
+
+
+
+
diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj
index 3b50e839..56a78a95 100644
--- a/src/libvgmstream.vcxproj
+++ b/src/libvgmstream.vcxproj
@@ -235,6 +235,7 @@
+
@@ -503,6 +504,7 @@
+
diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters
index 2b32c125..d3bf3998 100644
--- a/src/libvgmstream.vcxproj.filters
+++ b/src/libvgmstream.vcxproj.filters
@@ -295,6 +295,9 @@
meta\Source Files
+
+ meta\Source Files
+
meta\Source Files
@@ -1075,6 +1078,9 @@
layout\Source Files
+
+ layout\Source Files
+
layout\Source Files
diff --git a/src/meta/aifc.c b/src/meta/aifc.c
index 772dd181..5ae7d61c 100644
--- a/src/meta/aifc.c
+++ b/src/meta/aifc.c
@@ -1,11 +1,6 @@
#include "meta.h"
#include "../layout/layout.h"
-#include "../util.h"
-/* Audio Interchange File Format AIFF-C */
-/* also plain AIFF, for good measure */
-
-/* Included primarily for 3DO */
/* for reading integers inexplicably packed into 80 bit floats */
static uint32_t read80bitSANE(off_t offset, STREAMFILE *streamFile) {
@@ -53,9 +48,10 @@ static uint32_t find_marker(STREAMFILE *streamFile, off_t MarkerChunkOffset,
return -1;
}
+
+/* Audio Interchange File Format AIFF/AIFF-C - from Mac/3DO games */
VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
- char filename[PATH_LIMIT];
off_t file_size = -1;
int channel_count = 0;
@@ -82,22 +78,23 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
int InstrumentChunkFound =0;
off_t InstrumentChunkOffset = -1;
- /* check extension, case insensitive */
- streamFile->get_name(streamFile,filename,sizeof(filename));
- if (!strcasecmp("aifc",filename_extension(filename)) ||
- !strcasecmp("afc",filename_extension(filename)) ||
- !strcasecmp("aifcl",filename_extension(filename)) ||
- !strcasecmp("cbd2",filename_extension(filename)))
- {
+
+ /* checks */
+ /* .aif: common (AIFF or AIFC), .aiff: common AIFF, .aifc: common AIFC
+ * .cbd2: M2 games, .bgm: Super Street Fighter II Turbo (3DO), aifcl/aiffl: for plugins? */
+ if (check_extensions(streamFile, "aif")) {
AIFCext = 1;
- }
- else if (!strcasecmp("aiff",filename_extension(filename)) ||
- !strcasecmp("aif",filename_extension(filename)) ||
- !strcasecmp("aiffl",filename_extension(filename)))
- {
AIFFext = 1;
}
- else goto fail;
+ else if (check_extensions(streamFile, "aifc,aifcl,afc,cbd2,bgm")) {
+ AIFCext = 1;
+ }
+ else if (check_extensions(streamFile, "aiff,aiffl")) {
+ AIFFext = 1;
+ }
+ else {
+ goto fail;
+ }
/* check header */
if ((uint32_t)read_32bitBE(0,streamFile)==0x464F524D && /* "FORM" */
@@ -115,13 +112,16 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
AIFF = 1;
}
else goto fail;
- } else goto fail;
-
+ }
+ else {
+ goto fail;
+ }
+
file_size = get_streamfile_size(streamFile);
/* read through chunks to verify format and find metadata */
{
- off_t current_chunk = 0xc; /* start with first chunk within FORM */
+ off_t current_chunk = 0x0c; /* start with first chunk within FORM */
while (current_chunk < file_size) {
uint32_t chunk_type = read_32bitBE(current_chunk,streamFile);
@@ -134,54 +134,55 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
if (current_chunk+8+chunk_size > file_size) goto fail;
switch(chunk_type) {
- case 0x46564552: /* FVER */
- /* only one per file */
+ case 0x46564552: /* "FVER" (version info) */
if (FormatVersionChunkFound) goto fail;
- /* plain AIFF shouldn't have */
- if (AIFF) goto fail;
+ if (AIFF) goto fail; /* plain AIFF shouldn't have */
FormatVersionChunkFound = 1;
/* specific size */
if (chunk_size != 4) goto fail;
/* Version 1 of AIFF-C spec timestamp */
- if ((uint32_t)read_32bitBE(current_chunk+8,streamFile) !=
- 0xA2805140) goto fail;
+ if ((uint32_t)read_32bitBE(current_chunk+8,streamFile) != 0xA2805140) goto fail;
break;
- case 0x434F4D4D: /* COMM */
- /* only one per file */
+
+ case 0x434F4D4D: /* "COMM" (main header) */
if (CommonChunkFound) goto fail;
CommonChunkFound = 1;
channel_count = read_16bitBE(current_chunk+8,streamFile);
if (channel_count <= 0) goto fail;
- sample_count = (uint32_t)read_32bitBE(current_chunk+0xa,streamFile);
-
- sample_size = read_16bitBE(current_chunk+0xe,streamFile);
-
+ sample_count = (uint32_t)read_32bitBE(current_chunk+0x0a,streamFile); /* number of blocks, actually */
+ sample_size = read_16bitBE(current_chunk+0x0e,streamFile);
sample_rate = read80bitSANE(current_chunk+0x10,streamFile);
if (AIFC) {
switch (read_32bitBE(current_chunk+0x1a,streamFile)) {
- case 0x53445832: /* SDX2 */
+ case 0x53445832: /* "SDX2" [3DO games: Super Street Fighter II Turbo (3DO), etc] */
coding_type = coding_SDX2;
- interleave = 1;
+ interleave = 0x01;
break;
- case 0x43424432: /* CBD2 */
+ case 0x43424432: /* "CBD2" [M2 (arcade 3DO) games: IMSA Racing (M2), etc] */
coding_type = coding_CBD2;
- interleave = 1;
+ interleave = 0x01;
break;
- case 0x41445034: /* ADP4 */
+ case 0x41445034: /* "ADP4" */
coding_type = coding_DVI_IMA_int;
- /* don't know how stereo DVI is laid out */
- if (channel_count != 1) break;
+ if (channel_count != 1) break; /* don't know how stereo DVI is laid out */
+ break;
+ case 0x696D6134: /* "ima4" [Alida (PC) Lunar SSS (iOS)] */
+ coding_type = coding_APPLE_IMA4;
+ interleave = 0x22;
+ sample_count = sample_count * ((interleave-0x2)*2);
break;
default:
- /* we should probably support uncompressed here */
+ VGM_LOG("AIFC: unknown codec\n");
goto fail;
}
- } else if (AIFF) {
+ /* string size and human-readable AIFF-C codec follows */
+ }
+ else if (AIFF) {
switch (sample_size) {
case 8:
coding_type = coding_PCM8;
@@ -191,39 +192,40 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
coding_type = coding_PCM16BE;
interleave = 2;
break;
- /* 32 is a possibility, but we don't see it and I
- * don't have a reader for it yet */
default:
+ VGM_LOG("AIFF: unknown codec\n");
goto fail;
}
}
-
- /* we don't check the human-readable portion of AIFF-C*/
-
break;
- case 0x53534E44: /* SSND */
- /* at most one per file */
+
+ case 0x53534E44: /* "SSND" (main data) */
if (SoundDataChunkFound) goto fail;
SoundDataChunkFound = 1;
start_offset = current_chunk + 16 + read_32bitBE(current_chunk+8,streamFile);
break;
- case 0x4D41524B: /* MARK */
+
+ case 0x4D41524B: /* "MARK" (loops) */
if (MarkerChunkFound) goto fail;
MarkerChunkFound = 1;
+
MarkerChunkOffset = current_chunk;
break;
- case 0x494E5354: /* INST */
+
+ case 0x494E5354: /* "INST" (loops) */
if (InstrumentChunkFound) goto fail;
InstrumentChunkFound = 1;
+
InstrumentChunkOffset = current_chunk;
break;
+
default:
/* spec says we can skip unrecognized chunks */
break;
}
- current_chunk += 8+chunk_size;
+ current_chunk += 0x08+chunk_size;
}
}
@@ -235,6 +237,7 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
goto fail;
}
+
/* read loop points */
if (InstrumentChunkFound && MarkerChunkFound) {
int start_marker;
@@ -262,49 +265,31 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) {
}
}
- /* build the VGMSTREAM */
+ /* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
- /* fill in the vital statistics */
- vgmstream->num_samples = sample_count;
vgmstream->sample_rate = sample_rate;
-
- vgmstream->coding_type = coding_type;
- if (channel_count > 1)
- vgmstream->layout_type = layout_interleave;
- else
- vgmstream->layout_type = layout_none;
- vgmstream->interleave_block_size = interleave;
+ vgmstream->num_samples = sample_count;
vgmstream->loop_start_sample = loop_start;
vgmstream->loop_end_sample = loop_end;
+ vgmstream->coding_type = coding_type;
+ vgmstream->layout_type = (channel_count > 1) ? layout_interleave : layout_none;
+ vgmstream->interleave_block_size = interleave;
+
if (AIFC)
vgmstream->meta_type = meta_AIFC;
else if (AIFF)
vgmstream->meta_type = meta_AIFF;
- /* open the file, set up each channel */
- {
- int i;
-
- vgmstream->ch[0].streamfile = streamFile->open(streamFile,filename,
- STREAMFILE_DEFAULT_BUFFER_SIZE);
- if (!vgmstream->ch[0].streamfile) goto fail;
-
- for (i=0;ich[i].streamfile = vgmstream->ch[0].streamfile;
- vgmstream->ch[i].offset = vgmstream->ch[i].channel_start_offset =
- start_offset+i*interleave;
- }
- }
-
+ if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
+ goto fail;
return vgmstream;
- /* clean up anything we may have opened */
fail:
- if (vgmstream) close_vgmstream(vgmstream);
+ close_vgmstream(vgmstream);
return NULL;
}
diff --git a/src/meta/h4m.c b/src/meta/h4m.c
new file mode 100644
index 00000000..47ec0ece
--- /dev/null
+++ b/src/meta/h4m.c
@@ -0,0 +1,105 @@
+#include "meta.h"
+#include "../layout/layout.h"
+#include "../coding/coding.h"
+
+/* H4M - from Hudson HVQM4 videos [Resident Evil 0 (GC), Tales of Symphonia (GC)]
+ * (info from hcs/Nisto's h4m_audio_decode) */
+VGMSTREAM * init_vgmstream_h4m(STREAMFILE *streamFile) {
+ VGMSTREAM * vgmstream = NULL;
+ off_t start_offset;
+ int loop_flag, channel_count;
+ int format, extra_tracks, sample_rate;
+ int total_subsongs, target_subsong = streamFile->stream_index;
+
+
+ /* checks */
+ if (!check_extensions(streamFile, "h4m"))
+ goto fail;
+
+ if (read_32bitBE(0x00,streamFile) != 0x4856514D && /* "HVQM" */
+ read_32bitBE(0x04,streamFile) != 0x3420312E) /* "4 1." */
+ goto fail;
+ if (read_32bitBE(0x08,streamFile) != 0x33000000 && /* "3\0\0\0" */
+ read_32bitBE(0x08,streamFile) != 0x35000000) /* "5\0\0\0" */
+ goto fail;
+
+ /* header */
+ start_offset = read_32bitBE(0x10, streamFile); /* header_size */
+ if (start_offset != 0x44) /* known size */
+ goto fail;
+ if (read_32bitBE(0x14, streamFile) != get_streamfile_size(streamFile) - start_offset) /* body_size */
+ goto fail;
+ if (read_32bitBE(0x18, streamFile) == 0) /* blocks */
+ goto fail;
+ /* 0x1c: video_frames */
+ if (read_32bitBE(0x20, streamFile) == 0) /* audio_frames */
+ goto fail;
+ /* 0x24: frame interval */
+ /* 0x28: max_video_frame_size */
+ /* 0x2c: unk2C (0) */
+ if (read_32bitBE(0x30, streamFile) == 0) /* max_audio_frame_size */
+ goto fail;
+ /* 0x34: hres */
+ /* 0x36: vres */
+ /* 0x38: h_srate */
+ /* 0x39: v_srate */
+ /* 0x3a: unk3A (0 or 0x12) */
+ /* 0x3b: unk3B (0) */
+ channel_count = read_8bit(0x3c,streamFile);
+ if (read_8bit(0x3d,streamFile) != 16) /* bitdepth */ //todo Pikmin not working
+ goto fail;
+ format = read_8bit(0x3e,streamFile); /* flags? */
+ extra_tracks = read_8bit(0x3f,streamFile);
+ sample_rate = read_32bitBE(0x40,streamFile);
+
+ loop_flag = 0;
+
+ total_subsongs = extra_tracks + 1; /* tracks for languages [Pokemon Channel], or sometimes used to fake multichannel [Tales of Symphonia] */
+ if (target_subsong == 0) target_subsong = 1;
+ if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
+
+
+ /* build the VGMSTREAM */
+ vgmstream = allocate_vgmstream(channel_count, loop_flag);
+ if (!vgmstream) goto fail;
+
+ vgmstream->sample_rate = sample_rate;
+ vgmstream->num_streams = total_subsongs;
+ vgmstream->stream_size = get_streamfile_size(streamFile) / total_subsongs; /* approx... */
+ vgmstream->codec_version = format; /* for blocks */
+ vgmstream->meta_type = meta_H4M;
+ vgmstream->layout_type = layout_blocked_h4m;
+
+ switch(format & 0x7F) {
+ case 0x00:
+ vgmstream->coding_type = coding_DVI_IMA; //todo H4M_IMA
+ break;
+ /* no games known to use this, h4m_audio_decode may decode them */
+ case 0x01: /* Uncompressed PCM */
+ case 0x04: /* 8-bit (A)DPCM */
+ default:
+ VGM_LOG("H4M: unknown codec %x\n", format);
+ goto fail;
+ }
+
+ if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
+ goto fail;
+
+ /* calc num_samples manually */
+ {
+ vgmstream->next_block_offset = start_offset;
+ do {
+ block_update_h4m(vgmstream->next_block_offset,vgmstream);
+ vgmstream->num_samples += vgmstream->current_block_samples;
+ }
+ while (vgmstream->next_block_offset < get_streamfile_size(streamFile));
+ }
+
+ block_update_h4m(start_offset, vgmstream);
+
+ return vgmstream;
+
+fail:
+ close_vgmstream(vgmstream);
+ return NULL;
+}
diff --git a/src/meta/meta.h b/src/meta/meta.h
index 667fb7a6..00bb84a2 100644
--- a/src/meta/meta.h
+++ b/src/meta/meta.h
@@ -754,4 +754,7 @@ VGMSTREAM * init_vgmstream_ubi_bao_pk(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_dsp_switch_audio(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_dsp_sadf(STREAMFILE *streamFile);
+
+VGMSTREAM * init_vgmstream_h4m(STREAMFILE *streamFile);
+
#endif /*_META_H*/
diff --git a/src/meta/ps2_joe.c b/src/meta/ps2_joe.c
index 5856f528..5590cd4e 100644
--- a/src/meta/ps2_joe.c
+++ b/src/meta/ps2_joe.c
@@ -1,114 +1,119 @@
#include "meta.h"
-#include "../util.h"
+#include "../coding/coding.h"
-/* JOE (found in Wall-E and some more Pixar games) */
+/* .JOE - from Asobo Studio games [Up (PS2), Wall-E (PS2)] */
VGMSTREAM * init_vgmstream_ps2_joe(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
- char filename[PATH_LIMIT];
off_t start_offset;
- uint8_t testBuffer[0x10];
- off_t loopStart = 0;
- off_t loopEnd = 0;
- off_t readOffset = 0;
- off_t blockOffset = 0;
- off_t sampleOffset = 0;
- size_t fileLength;
- size_t dataLength;
- size_t dataInterleave;
+ size_t file_size, data_size, unknown1, unknown2, interleave;
int loop_flag;
- int channel_count;
+ int channel_count;
- /* check extension, case insensitive */
- streamFile->get_name(streamFile,filename,sizeof(filename));
- if (strcasecmp("joe",filename_extension(filename))) goto fail;
-
- /* check header */
- // if (read_32bitBE(0x0C,streamFile) != 0xCCCCCCCC)
- // goto fail;
+ /* checks */
+ if (!check_extensions(streamFile, "joe"))
+ goto fail;
loop_flag = 1;
channel_count = 2;
- /* build the VGMSTREAM */
+ file_size = get_streamfile_size(streamFile);
+ data_size = read_32bitLE(0x04,streamFile);
+ unknown1 = read_32bitLE(0x08,streamFile);
+ unknown2 = read_32bitLE(0x0c,streamFile);
+
+ /* detect version */
+ if (data_size/2 == file_size - 0x10
+ && unknown1 == 0x0045039A && unknown2 == 0x00108920) { /* Super Farm */
+ data_size = data_size / 2;
+ interleave = 0x4000;
+ }
+ else if (data_size/2 == file_size - 0x10
+ && unknown1 == 0xCCCCCCCC && unknown2 == 0xCCCCCCCC) { /* Sitting Ducks */
+ data_size = data_size / 2;
+ interleave = 0x8000;
+ }
+ else if (data_size == file_size - 0x10
+ && unknown1 == 0xCCCCCCCC && unknown2 == 0xCCCCCCCC) { /* The Mummy: The Animated Series */
+ data_size = data_size / 2;
+ interleave = 0x8000;
+ }
+ else if (data_size == file_size - 0x4020) { /* CT Special Forces (and all games beyond) */
+ data_size = data_size / 2;
+ interleave = unknown1; /* always 0? */
+ if (!interleave)
+ interleave = 0x10;
+ /* header padding contains garbage */
+ }
+ else {
+ goto fail;
+ }
+
+ start_offset = file_size - data_size;
+
+
+ /* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
- fileLength = get_streamfile_size(streamFile);
- dataLength = read_32bitLE(0x4,streamFile);
- dataInterleave = read_32bitLE(0x8,streamFile);
-
- if (!dataInterleave)
- dataInterleave = 16; /* XXX */
-
- /* fill in the vital statistics */
- start_offset = fileLength - dataLength;
- vgmstream->channels = channel_count;
- vgmstream->sample_rate = read_32bitLE(0x0,streamFile);
+ vgmstream->sample_rate = read_32bitLE(0x00,streamFile);
vgmstream->coding_type = coding_PSX;
- vgmstream->num_samples = dataLength*28/16/channel_count;
-
-
- readOffset = start_offset;
- do {
- off_t blockRead = (off_t)read_streamfile(testBuffer,readOffset,0x10,streamFile);
+ vgmstream->num_samples = ps_bytes_to_samples(data_size, channel_count);
- readOffset += blockRead;
- blockOffset += blockRead;
-
- if (blockOffset >= dataInterleave) {
- readOffset += dataInterleave;
- blockOffset -= dataInterleave;
- }
-
- /* Loop Start */
- if(testBuffer[0x01]==0x06) {
- if(loopStart == 0) loopStart = sampleOffset;
- /* break; */
- }
-
- sampleOffset += 28;
-
- /* Loop End */
- if(testBuffer[0x01]==0x03) {
- if(loopEnd == 0) loopEnd = sampleOffset;
- /* break; */
- }
-
- } while (streamFile->get_offset(streamFile)<(int32_t)fileLength);
-
- if(loopStart == 0 && loopEnd == 0) {
- loop_flag = 0;
- vgmstream->num_samples = dataLength*28/16/channel_count;
- } else {
- loop_flag = 1;
- vgmstream->loop_start_sample = loopStart;
- vgmstream->loop_end_sample = loopEnd;
- }
-
- vgmstream->layout_type = layout_interleave;
- vgmstream->interleave_block_size = dataInterleave;
- vgmstream->meta_type = meta_PS2_JOE;
-
- /* open the file for reading */
+ //todo improve, not working 100% with early .joe
{
- int i;
- STREAMFILE * file;
- file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
- if (!file) goto fail;
- for (i=0;ich[i].streamfile = file;
+ uint8_t testBuffer[0x10];
+ off_t blockOffset = 0;
+ off_t sampleOffset = 0;
+ off_t readOffset = 0;
+ off_t loopStart = 0, loopEnd = 0;
- vgmstream->ch[i].channel_start_offset=
- vgmstream->ch[i].offset=start_offset+
- vgmstream->interleave_block_size*i;
+ readOffset = start_offset;
+ do {
+ off_t blockRead = (off_t)read_streamfile(testBuffer,readOffset,0x10,streamFile);
+ readOffset += blockRead;
+ blockOffset += blockRead;
+
+ if (blockOffset >= interleave) {
+ readOffset += interleave;
+ blockOffset -= interleave;
+ }
+
+ /* Loop Start */
+ if(testBuffer[0x01]==0x06) {
+ if(loopStart == 0)
+ loopStart = sampleOffset;
+ /* break; */
+ }
+
+ sampleOffset += 28;
+
+ /* Loop End */
+ if(testBuffer[0x01]==0x03) {
+ if(loopEnd == 0)
+ loopEnd = sampleOffset;
+ /* break; */
+ }
+
+ } while (streamFile->get_offset(streamFile)<(int32_t)file_size);
+
+ if (loopStart == 0 && loopEnd == 0) {
+ vgmstream->loop_flag = 0;
+ } else {
+ vgmstream->loop_start_sample = loopStart;
+ vgmstream->loop_end_sample = loopEnd;
}
}
+ vgmstream->layout_type = layout_interleave;
+ vgmstream->interleave_block_size = interleave;
+ vgmstream->meta_type = meta_PS2_JOE;
+
+ if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
+ goto fail;
return vgmstream;
- /* clean up anything we may have opened */
fail:
- if (vgmstream) close_vgmstream(vgmstream);
+ close_vgmstream(vgmstream);
return NULL;
}
diff --git a/src/meta/txtp.c b/src/meta/txtp.c
index da54c9d5..a0a8dcf2 100644
--- a/src/meta/txtp.c
+++ b/src/meta/txtp.c
@@ -238,7 +238,7 @@ static int add_filename(txtp_header * txtp, char *filename) {
if (sscanf(config, "%d~%d", &subsong_start, &subsong_end) == 2) {
if (subsong_start > 0 && subsong_end > 0) {
range_start = subsong_start-1;
- range_end = subsong_end-1;
+ range_end = subsong_end;
}
}
else if (sscanf(config, "%u", &subsong_start) == 1) {
diff --git a/src/meta/xwc.c b/src/meta/xwc.c
index a9f7b4c5..fbc5c28e 100644
--- a/src/meta/xwc.c
+++ b/src/meta/xwc.c
@@ -4,26 +4,44 @@
/* .XWC - Starbreeze games [Chronicles of Riddick: Assault on Dark Athena, Syndicate] */
VGMSTREAM * init_vgmstream_xwc(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
- off_t start_offset;
+ off_t start_offset, extra_offset;
size_t data_size;
- int loop_flag, channel_count, codec;
+ int loop_flag, channel_count, codec, num_samples;
/* check extensions (.xwc is the extension of the bigfile, individual files don't have one) */
if ( !check_extensions(streamFile,"xwc"))
goto fail;
- if(read_32bitBE(0x00,streamFile) != 0x00040000 && /* version? */
- read_32bitBE(0x04,streamFile) != 0x00900000)
- goto fail;
- data_size = read_32bitLE(0x08, streamFile); /* including subheader */
- channel_count = read_32bitLE(0x0c, streamFile);
- /* 0x10: num_samples */
- /* 0x14: 0x8000? */
- codec = read_32bitBE(0x24, streamFile);
- /* 0x28: num_samples */
- /* 0x2c: config data? (first nibble: 0x4=mono, 0x8=stereo) */
- /* 0x30+: codec dependant */
+ /* version */
+ if (read_32bitBE(0x00,streamFile) == 0x00030000 &&
+ read_32bitBE(0x04,streamFile) == 0x00900000) { /* The Darkness */
+ data_size = read_32bitLE(0x08, streamFile); /* including subheader */
+ channel_count = read_32bitLE(0x0c, streamFile);
+ /* 0x10: num_samples */
+ /* 0x14: 0x8000? */
+ /* 0x18: null */
+ codec = read_32bitBE(0x1c, streamFile);
+ num_samples = read_32bitLE(0x20, streamFile);
+ /* 0x24: config data >> 2? (0x00(1): channels; 0x01(2): ?, 0x03(2): sample_rate) */
+ extra_offset = 0x28;
+ }
+ else if (read_32bitBE(0x00,streamFile) == 0x00040000 &&
+ read_32bitBE(0x04,streamFile) == 0x00900000) { /* Riddick, Syndicate */
+ data_size = read_32bitLE(0x08, streamFile); /* including subheader */
+ channel_count = read_32bitLE(0x0c, streamFile);
+ /* 0x10: num_samples */
+ /* 0x14: 0x8000? */
+ codec = read_32bitBE(0x24, streamFile);
+ num_samples = read_32bitLE(0x28, streamFile);
+ /* 0x2c: config data >> 2? (0x00(1): channels; 0x01(2): ?, 0x03(2): sample_rate) */
+ /* 0x30+: codec dependant */
+ extra_offset = 0x30;
+ }
+ else {
+ goto fail;
+ }
+
loop_flag = 0; /* seemingly not in the file */
@@ -31,7 +49,7 @@ VGMSTREAM * init_vgmstream_xwc(STREAMFILE *streamFile) {
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
- vgmstream->num_samples = read_32bitLE(0x28, streamFile);
+ vgmstream->num_samples = num_samples;
vgmstream->meta_type = meta_XWC;
switch(codec) {
@@ -40,8 +58,8 @@ VGMSTREAM * init_vgmstream_xwc(STREAMFILE *streamFile) {
mpeg_custom_config cfg = {0};
start_offset = 0x800;
- vgmstream->num_samples = read_32bitLE(0x30, streamFile); /* with encoder delay */ //todo improve
- cfg.data_size = read_32bitLE(0x34, streamFile); //data_size - 0x28;
+ vgmstream->num_samples = read_32bitLE(extra_offset+0x00, streamFile); /* with encoder delay */ //todo improve
+ cfg.data_size = read_32bitLE(extra_offset+0x04, streamFile); //data_size - 0x28;
vgmstream->codec_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_STANDARD, &cfg);
if (!vgmstream->codec_data) goto fail;
@@ -56,13 +74,13 @@ VGMSTREAM * init_vgmstream_xwc(STREAMFILE *streamFile) {
uint8_t buf[0x100];
int32_t bytes, seek_size, block_size, block_count, sample_rate;
- seek_size = read_32bitLE(0x30, streamFile);
- start_offset = 0x34 + seek_size + read_32bitLE(0x34+seek_size, streamFile) + 0x08;
+ seek_size = read_32bitLE(extra_offset+0x00, streamFile);
+ start_offset = extra_offset+0x04 + seek_size + read_32bitLE(extra_offset+0x04+seek_size, streamFile) + 0x08;
start_offset += (start_offset % 0x800) ? 0x800 - (start_offset % 0x800) : 0; /* padded */
- sample_rate = read_32bitBE(0x34+seek_size+0x10, streamFile);
- block_size = read_32bitBE(0x34+seek_size+0x1c, streamFile);
- block_count = read_32bitBE(0x34+seek_size+0x28, streamFile);
+ sample_rate = read_32bitBE(extra_offset+0x04+seek_size+0x10, streamFile);
+ block_size = read_32bitBE(extra_offset+0x04+seek_size+0x1c, streamFile);
+ block_count = read_32bitBE(extra_offset+0x04+seek_size+0x28, streamFile);
/* others: scrambled RIFF fmt BE values */
bytes = ffmpeg_make_riff_xma2(buf,0x100, vgmstream->num_samples, data_size, vgmstream->channels, sample_rate, block_count, block_size);
@@ -93,10 +111,6 @@ VGMSTREAM * init_vgmstream_xwc(STREAMFILE *streamFile) {
goto fail;
}
- if (vgmstream->sample_rate != 48000) { /* get from config data instead of codecs? */
- VGM_LOG("XWC: unexpected sample rate %i\n",vgmstream->sample_rate);
- goto fail;
- }
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
goto fail;
diff --git a/src/vgmstream.c b/src/vgmstream.c
index c262387b..0679edde 100644
--- a/src/vgmstream.c
+++ b/src/vgmstream.c
@@ -410,6 +410,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_ubi_bao_pk,
init_vgmstream_dsp_switch_audio,
init_vgmstream_dsp_sadf,
+ init_vgmstream_h4m,
init_vgmstream_txth, /* should go at the end (lower priority) */
#ifdef VGM_USE_FFMPEG
@@ -936,6 +937,7 @@ void render_vgmstream(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstre
case layout_blocked_ea_wve_au00:
case layout_blocked_ea_wve_ad10:
case layout_blocked_sthd:
+ case layout_blocked_h4m:
render_vgmstream_blocked(buffer,sample_count,vgmstream);
break;
case layout_aix:
diff --git a/src/vgmstream.h b/src/vgmstream.h
index 58ecb30a..3ec22ae9 100644
--- a/src/vgmstream.h
+++ b/src/vgmstream.h
@@ -252,6 +252,7 @@ typedef enum {
layout_blocked_ea_wve_au00, /* EA WVE au00 blocks */
layout_blocked_ea_wve_ad10, /* EA WVE Ad10 blocks */
layout_blocked_sthd, /* Dream Factory STHD */
+ layout_blocked_h4m, /* H4M video */
/* otherwise odd */
layout_aix, /* CRI AIX's wheels within wheels */
@@ -678,6 +679,7 @@ typedef enum {
meta_DSP_SWITCH_AUDIO, /* Gal Gun 2 (Switch) */
meta_TA_AAC_VITA, /* tri-Ace AAC (Judas Code) */
meta_OGG_GWM, /* Ogg Vorbis with encryption [Metronomicon (PC)] */
+ meta_H4M, /* Hudson HVQM4 video [Resident Evil 0 (GC), Tales of Symphonia (GC)] */
#ifdef VGM_USE_FFMPEG
meta_FFmpeg,