diff --git a/src/formats.c b/src/formats.c
index d6ce7207..90b68155 100644
--- a/src/formats.c
+++ b/src/formats.c
@@ -185,6 +185,12 @@ static const char* extension_list[] = {
"hlwav",
"hps",
"hsf",
+ "hx2",
+ "hx3",
+ "hxc",
+ "hxd",
+ "hxg",
+ "hxx",
"hwas",
"iab",
@@ -1198,6 +1204,7 @@ static const meta_info meta_info_list[] = {
{meta_DSP_ITL_i, "Infernal .ITL DSP header"},
{meta_IMA, "Blitz Games .IMA header"},
{meta_XMV_VALVE, "Valve XMV header"},
+ {meta_UBI_HX, "Ubisoft HXx header"},
};
diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj
index ca72cc73..45305963 100644
--- a/src/libvgmstream.vcproj
+++ b/src/libvgmstream.vcproj
@@ -1586,10 +1586,14 @@
RelativePath=".\meta\ubi_bao.c"
>
-
-
+
+
+
+
diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj
index bc07b7c4..2ffb3f89 100644
--- a/src/libvgmstream.vcxproj
+++ b/src/libvgmstream.vcxproj
@@ -481,6 +481,7 @@
+
diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters
index 6257eef7..b7c91294 100644
--- a/src/libvgmstream.vcxproj.filters
+++ b/src/libvgmstream.vcxproj.filters
@@ -979,6 +979,9 @@
meta\Source Files
+
+ meta\Source Files
+
meta\Source Files
diff --git a/src/meta/meta.h b/src/meta/meta.h
index 57199ccd..ebc3fb14 100644
--- a/src/meta/meta.h
+++ b/src/meta/meta.h
@@ -864,4 +864,6 @@ VGMSTREAM * init_vgmstream_nub_is14(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_xmv_valve(STREAMFILE * streamFile);
+VGMSTREAM * init_vgmstream_ubi_hx(STREAMFILE * streamFile);
+
#endif /*_META_H*/
diff --git a/src/meta/ubi_hx.c b/src/meta/ubi_hx.c
new file mode 100644
index 00000000..df38bb35
--- /dev/null
+++ b/src/meta/ubi_hx.c
@@ -0,0 +1,649 @@
+#include "meta.h"
+#include "../layout/layout.h"
+#include "../coding/coding.h"
+
+
+typedef enum { PCM, UBI, PSX, DSP, XIMA, ATRAC3, XMA2 } ubi_hx_codec;
+
+typedef struct {
+ int big_endian;
+ int total_subsongs;
+
+ int codec_id;
+ ubi_hx_codec codec; /* unified codec */
+ int header_index; /* entry number within section2 */
+ off_t header_offset; /* entry offset within internal .HXx */
+ size_t header_size; /* entry offset within internal .HXx */
+ char class_name[255];
+ size_t class_size;
+ size_t stream_mode;
+
+ off_t stream_offset; /* data offset within external stream */
+ size_t stream_size; /* data size within external stream */
+ uint32_t cuuid1; /* usually "Res" id1: class (1=Event, 3=Wave), id2: group id+sound id, */
+ uint32_t cuuid2; /* others have some complex id (not hash), id1: parent id?, id2: file id? */
+
+ int loop_flag;
+ int channels;
+ int sample_rate;
+ int num_samples;
+
+ int is_external;
+ char resource_name[0x28]; /* filename to the external stream */
+ char internal_name[255]; /* WavRes's assigned name */
+ char readable_name[255]; /* final subsong name */
+
+} ubi_hx_header;
+
+
+static int parse_hx(ubi_hx_header * hx, STREAMFILE *sf, int target_subsong);
+static VGMSTREAM * init_vgmstream_ubi_hx_header(ubi_hx_header *hx, STREAMFILE *sf);
+
+/* .HXx - banks from Ubisoft's HXAudio engine games [Rayman Arena, Rayman 3, XIII] */
+VGMSTREAM * init_vgmstream_ubi_hx(STREAMFILE *streamFile) {
+ VGMSTREAM* vgmstream = NULL;
+ ubi_hx_header hx = {0};
+ int target_subsong = streamFile->stream_index;
+
+
+ /* checks */
+ /* .hxd: Rayman Arena (all)
+ * .hxc: Rayman 3 (PC), XIII (PC)
+ * .hx2: Rayman 3 (PS2), XIII (PS2)
+ * .hxg: Rayman 3 (GC), XIII (GC)
+ * .hxx: Rayman 3 (Xbox), Rayman 3 HD (X360)
+ * .hx3: Rayman 3 HD (PS3) */
+ if (!check_extensions(streamFile, "hxd,hxc,hx2,hxg,hxx,hx3"))
+ goto fail;
+
+ /* .HXx is a slightly less bizarre bank with various resource classes (events, streams, etc, not unlike other Ubi's engines)
+ * then an index to those types. Some games leave a companion .bnh with text info, probably leftover from their tools.
+ * Game seems to play files by calling linked ids: EventResData (play/stop/etc) > Random/Program/Wav ResData (1..N refs) > FileIdObj */
+
+ /* HX CONFIG */
+ hx.big_endian = guess_endianness32bit(0x00, streamFile);
+
+ /* HX HEADER */
+ if (!parse_hx(&hx, streamFile, target_subsong))
+ goto fail;
+
+ /* CREATE VGMSTREAM */
+ vgmstream = init_vgmstream_ubi_hx_header(&hx, streamFile);
+ return vgmstream;
+
+fail:
+ close_vgmstream(vgmstream);
+ return NULL;
+}
+
+
+static void build_readable_name(char * buf, size_t buf_size, ubi_hx_header * hx) {
+ const char *grp_name;
+
+ if (hx->is_external)
+ grp_name = hx->resource_name;
+ else
+ grp_name = "internal";
+
+ if (hx->internal_name[0])
+ snprintf(buf,buf_size, "%s/%i/%08x-%08x/%s/%s", "hx", hx->header_index, hx->cuuid1,hx->cuuid2, grp_name, hx->internal_name);
+ else
+ snprintf(buf,buf_size, "%s/%i/%08x-%08x/%s", "hx", hx->header_index, hx->cuuid1,hx->cuuid2, grp_name);
+}
+
+
+/* get referenced name from WavRes, using the index again (abridged) */
+static int parse_name(ubi_hx_header * hx, STREAMFILE *sf) {
+ int32_t (*read_32bit)(off_t,STREAMFILE*) = hx->big_endian ? read_32bitBE : read_32bitLE;
+ off_t index_offset, offset;
+ int i, index_entries;
+ char class_name[255];
+
+
+ index_offset = read_32bit(0x00, sf);
+ index_entries = read_32bit(index_offset + 0x08, sf);
+ offset = index_offset + 0x0c;
+ for (i = 0; i < index_entries; i++) {
+ off_t header_offset;
+ size_t class_size;
+ int j, link_count, language_count, is_found = 0;
+
+
+ class_size = read_32bit(offset + 0x00, sf);
+ if (class_size > sizeof(class_name)+1) goto fail;
+ read_string(class_name,class_size+1, offset + 0x04, sf); /* not null-terminated */
+ offset += 0x04 + class_size;
+
+ header_offset = read_32bit(offset + 0x08, sf);
+ offset += 0x10;
+
+ //unknown_count = read_32bit(offset + 0x00, sf);
+ offset += 0x04;
+
+ link_count = read_32bit(offset + 0x00, sf);
+ offset += 0x04;
+ for (j = 0; j < link_count; j++) {
+ uint32_t link_id1 = (uint32_t)read_32bit(offset + 0x00, sf);
+ uint32_t link_id2 = (uint32_t)read_32bit(offset + 0x04, sf);
+
+ if (link_id1 == hx->cuuid1 && link_id2 == hx->cuuid2) {
+ is_found = 1;
+ }
+ offset += 0x08;
+ }
+
+ language_count = read_32bit(offset + 0x00, sf);
+ offset += 0x04;
+ for (j = 0; j < language_count; j++) {
+ uint32_t link_id1 = (uint32_t)read_32bit(offset + 0x08, sf);
+ uint32_t link_id2 = (uint32_t)read_32bit(offset + 0x0c, sf);
+
+ if (link_id1 == hx->cuuid1 && link_id2 == hx->cuuid2) {
+ is_found = 1;
+ }
+
+ offset += 0x10;
+ }
+
+ /* identify all possible names so unknown platforms fail */
+ if (is_found && (
+ strcmp(class_name, "CPCWavResData") == 0 ||
+ strcmp(class_name, "CPS2WavResData") == 0 ||
+ strcmp(class_name, "CGCWavResData") == 0 ||
+ strcmp(class_name, "CXBoxWavResData") == 0 ||
+ strcmp(class_name, "CPS3WavResData") == 0)) {
+ size_t resclass_size, internal_size;
+ off_t wavres_offset = header_offset;
+
+ /* parse WavRes header */
+ resclass_size = read_32bit(wavres_offset, sf);
+ wavres_offset += 0x04 + resclass_size + 0x08 + 0x04; /* skip class + cuiid + flags */
+
+ internal_size = read_32bit(wavres_offset + 0x00, sf); /* usually 0 in consoles */
+ if (internal_size > sizeof(hx->internal_name)+1) goto fail;
+ read_string(hx->internal_name,internal_size+1, wavres_offset + 0x04, sf);
+ return 1;
+ }
+ }
+
+fail:
+ return 0;
+}
+
+
+/* parse a single known header resource at offset */
+static int parse_header(ubi_hx_header * hx, STREAMFILE *sf, off_t offset, size_t size, int index) {
+ int32_t (*read_32bit)(off_t,STREAMFILE*) = hx->big_endian ? read_32bitBE : read_32bitLE;
+ int16_t (*read_16bit)(off_t,STREAMFILE*) = hx->big_endian ? read_16bitBE : read_16bitLE;
+ off_t riff_offset, riff_size, chunk_offset, stream_adjust = 0, resource_size;
+ int cue_flag = 0;
+
+ //todo cleanup/unify common readings
+
+ //;VGM_LOG("UBI HX: header class %s, o=%lx, s=%x\n\n", class_name, header_offset, header_size);
+
+ hx->header_index = index;
+ hx->header_offset = offset;
+ hx->header_size = size;
+
+ hx->class_size = read_32bit(offset + 0x00, sf);
+ if (hx->class_size > sizeof(hx->class_name)+1) goto fail;
+ read_string(hx->class_name,hx->class_size+1, offset + 0x04, sf);
+ offset += 0x04 + hx->class_size;
+
+ hx->cuuid1 = (uint32_t)read_32bit(offset + 0x00, sf);
+ hx->cuuid2 = (uint32_t)read_32bit(offset + 0x04, sf);
+ offset += 0x08;
+
+ if (strcmp(hx->class_name, "CPCWaveFileIdObj") == 0 ||
+ strcmp(hx->class_name, "CPS2WaveFileIdObj") == 0 ||
+ strcmp(hx->class_name, "CGCWaveFileIdObj") == 0) {
+ uint32_t flag_type = read_32bit(offset + 0x00, sf);
+
+ if (flag_type == 0x01 || flag_type == 0x02) { /* Rayman Arena */
+ if (read_32bit(offset + 0x04, sf) != 0x00) goto fail;
+ hx->stream_mode = read_32bit(offset + 0x08, sf); /* flag: 0=internal, 1=external */
+ /* 0x0c: flag: 0=static, 1=stream */
+ offset += 0x10;
+ }
+ else if (flag_type == 0x03) { /* others */
+ /* 0x04: some kind of parent id shared by multiple Waves, or 0 */
+ offset += 0x08;
+
+ if (strcmp(hx->class_name, "CGCWaveFileIdObj") == 0) {
+ if (read_32bit(offset + 0x00, sf) != read_32bit(offset + 0x04, sf)) goto fail; /* meaning? */
+ hx->stream_mode = read_32bit(offset + 0x04, sf);
+ offset += 0x08;
+ }
+ else {
+ hx->stream_mode = read_8bit(offset, sf);
+ offset += 0x01;
+ }
+ }
+ else {
+ VGM_LOG("UBI HX: unknown flag-type\n");
+ goto fail;
+ }
+
+ /* get bizarro adjust (found in XIII external files) */
+ if (hx->stream_mode == 0x0a) {
+ stream_adjust = read_32bit(offset, sf); /* what */
+ offset += 0x04;
+ }
+
+ //todo probably a flag: &1=external, &2=stream, &8=has adjust (XIII), &4=??? (XIII PS2, small, mono)
+ switch(hx->stream_mode) {
+ case 0x00: /* memory (internal file) */
+ riff_offset = offset;
+ riff_size = read_32bit(riff_offset + 0x04, sf) + 0x08;
+ break;
+
+ case 0x01: /* static (smaller external file) */
+ case 0x03: /* stream (bigger external file) */
+ case 0x07: /* static? */
+ case 0x0a: /* static? */
+ resource_size = read_32bit(offset + 0x00, sf);
+ if (resource_size > sizeof(hx->resource_name)+1) goto fail;
+ read_string(hx->resource_name,resource_size+1, offset + 0x04, sf);
+
+ riff_offset = offset + 0x04 + resource_size;
+ riff_size = read_32bit(riff_offset + 0x04, sf) + 0x08;
+
+ hx->is_external = 1;
+ break;
+
+ default:
+ goto fail;
+ }
+
+
+ /* parse pseudo-RIFF "fmt" */
+ if (read_32bit(riff_offset, sf) != 0x46464952) /* "RIFF" in machine endianness */
+ goto fail;
+
+ hx->codec_id = read_16bit(riff_offset + 0x14 , sf);
+ switch(hx->codec_id) {
+ case 0x01: hx->codec = PCM; break;
+ case 0x02: hx->codec = UBI; break;
+ case 0x03: hx->codec = PSX; break;
+ case 0x04: hx->codec = DSP; break;
+ default: goto fail;
+ }
+ hx->channels = read_16bit(riff_offset + 0x16, sf);
+ hx->sample_rate = read_32bit(riff_offset + 0x18, sf);
+
+ /* find "datx" (external) or "data" (internal) also in machine endianness */
+ if (hx->is_external) {
+ if (!find_chunk_riff_ve(sf, 0x78746164,riff_offset + 0x0c,riff_size - 0x0c, &chunk_offset,NULL, hx->big_endian))
+ goto fail;
+ hx->stream_size = read_32bit(chunk_offset + 0x00, sf);
+ hx->stream_offset = read_32bit(chunk_offset + 0x04, sf) + stream_adjust;
+ }
+ else {
+ if (!find_chunk_riff_ve(sf, 0x61746164,riff_offset + 0x0c,riff_size - 0x0c, &chunk_offset,NULL, hx->big_endian))
+ goto fail;
+ hx->stream_offset = chunk_offset;
+ hx->stream_size = riff_size - (chunk_offset - riff_offset);
+ }
+
+ /* can contain other RIFF stuff like "cue ", "labl" and "ump3"
+ * XIII music uses cue/labl to play/loop dynamic sections */
+ }
+ else if (strcmp(hx->class_name, "CXBoxStaticHWWaveFileIdObj") == 0 ||
+ strcmp(hx->class_name, "CXBoxStreamHWWaveFileIdObj") == 0 ||
+ strcmp(hx->class_name, "CPS3StaticAC3WaveFileIdObj") == 0 ||
+ strcmp(hx->class_name, "CPS3StreamAC3WaveFileIdObj") == 0) {
+
+ hx->stream_offset = read_32bit(offset + 0x00, sf);
+ hx->stream_size = read_32bit(offset + 0x04, sf);
+ offset += 0x08;
+
+ if (read_32bit(offset + 0x00, sf) != 0x01) goto fail;
+ /* 0x04: 0? */
+ offset += 0x08;
+
+ hx->stream_mode = read_8bit(offset, sf);
+ offset += 0x01;
+
+ if ((strcmp(hx->class_name, "CXBoxStaticHWWaveFileIdObj") == 0 ||
+ strcmp(hx->class_name, "CXBoxStreamHWWaveFileIdObj") == 0) && !hx->big_endian) {
+ /* micro header: some mix of channels + block size + sample rate + flags, unsure of which bits */
+ hx->codec = XIMA;
+ hx->channels = (uint8_t)read_8bit(offset + 0x01, sf);
+ switch(hx->channels) { /* upper 2 bits? */
+ case 0x48: hx->channels = 1; break;
+ case 0x90: hx->channels = 2; break;
+ default: goto fail;
+ }
+ hx->sample_rate = (uint16_t)(read_16bit(offset + 0x02, sf) & 0x7FFF) << 1; /* ??? */
+ cue_flag = (uint8_t) read_8bit (offset + 0x03, sf) & (1<<7);
+ offset += 0x04;
+ }
+ else if ((strcmp(hx->class_name, "CXBoxStaticHWWaveFileIdObj") == 0 ||
+ strcmp(hx->class_name, "CXBoxStreamHWWaveFileIdObj") == 0) && hx->big_endian) {
+ /* fake fmt chunk */
+ hx->codec = XMA2;
+ hx->channels = (uint16_t)read_16bit(offset + 0x02, sf);
+ hx->sample_rate = read_32bit(offset + 0x04, sf);
+ hx->num_samples = read_32bit(offset + 0x18, sf) / 0x02 / hx->channels;
+ cue_flag = read_32bit(offset + 0x34, sf);
+ offset += 0x38;
+ }
+ else {
+ /* MSFC header */
+ hx->codec = ATRAC3;
+ hx->codec_id = read_32bit(offset + 0x04, sf);
+ hx->channels = read_32bit(offset + 0x08, sf);
+ hx->sample_rate = read_32bit(offset + 0x10, sf);
+ cue_flag = read_32bit(offset + 0x40, sf);
+ offset += 0x44;
+ }
+
+ /* skip cue table that sometimes exists in streams */
+ if (cue_flag) {
+ int j;
+
+ size_t cue_count = read_32bit(offset, sf);
+ offset += 0x04;
+ for (j = 0; j < cue_count; j++) {
+ /* 0x00: id? */
+ size_t description_size = read_32bit(offset + 0x04, sf); /* for next string */
+ offset += 0x08 + description_size;
+ }
+ }
+
+ switch(hx->stream_mode) {
+ case 0x01: /* static (smaller external file) */
+ case 0x03: /* stream (bigger external file) */
+ resource_size = read_32bit(offset + 0x00, sf);
+ if (resource_size > sizeof(hx->resource_name)+1) goto fail;
+ read_string(hx->resource_name,resource_size+1, offset + 0x04, sf);
+
+ hx->is_external = 1;
+ break;
+
+ default:
+ goto fail;
+ }
+ }
+ else {
+ goto fail;
+ }
+
+ return 1;
+fail:
+ VGM_LOG("UBI HX: error parsing header at %lx\n", hx->header_offset);
+ return 0;
+}
+
+
+/* parse a bank index and its possible audio headers (some info from Droolie's .bms) */
+static int parse_hx(ubi_hx_header * hx, STREAMFILE *sf, int target_subsong) {
+ int32_t (*read_32bit)(off_t,STREAMFILE*) = hx->big_endian ? read_32bitBE : read_32bitLE;
+ off_t index_offset, offset;
+ int i, index_entries;
+ char class_name[255];
+
+
+ index_offset = read_32bit(0x00, sf);
+ if (read_32bit(index_offset + 0x00, sf) != 0x58444E49) /* "XDNI" (INDX in given endianness) */
+ goto fail;
+ if (read_32bit(index_offset + 0x04, sf) != 0x02) /* type? */
+ goto fail;
+
+ if (target_subsong == 0) target_subsong = 1;
+
+ index_entries = read_32bit(index_offset + 0x08, sf);
+ offset = index_offset + 0x0c;
+ for (i = 0; i < index_entries; i++) {
+ off_t header_offset;
+ size_t class_size, header_size;
+ int j, unknown_count, link_count, language_count;
+
+ //;VGM_LOG("UBI HX: index %i at %lx\n", i, offset);
+
+ /* parse index entries: offset to actual header plus some extra info also in the header */
+
+ class_size = read_32bit(offset + 0x00, sf);
+ if (class_size > sizeof(class_name)+1) goto fail;
+
+ read_string(class_name,class_size+1, offset + 0x04, sf); /* not null-terminated */
+ offset += 0x04 + class_size;
+
+ /* 0x00: id1+2 */
+ header_offset = read_32bit(offset + 0x08, sf);
+ header_size = read_32bit(offset + 0x0c, sf);
+ offset += 0x10;
+
+ /* not seen */
+ unknown_count = read_32bit(offset + 0x00, sf);
+ if (unknown_count != 0) {
+ VGM_LOG("UBI HX: found unknown near %lx\n", offset);
+ goto fail;
+ }
+ offset += 0x04;
+
+ /* ids that this object directly points to (ex. Event > Random) */
+ link_count = read_32bit(offset + 0x00, sf);
+ offset += 0x04 + 0x08 * link_count;
+
+ /* localized id list of WavRes (can use this list instead of the prev one) */
+ language_count = read_32bit(offset + 0x00, sf);
+ offset += 0x04;
+ for (j = 0; j < language_count; j++) {
+ /* 0x00: lang code, in reverse endianness: "en ", "fr ", etc */
+ /* 0x04: possibly count of ids for this lang */
+ /* 0x08: id1+2 */
+
+ if (read_32bit(offset + 0x04, sf) != 1) {
+ VGM_LOG("UBI HX: wrong lang count near %lx\n", offset);
+ goto fail; /* WavRes doesn't have this field */
+ }
+ offset += 0x10;
+ }
+
+ //todo figure out CProgramResData sequences
+ /* identify all possible names so unknown platforms fail */
+ if (strcmp(class_name, "CEventResData") == 0 || /* play/stop/etc event */
+ strcmp(class_name, "CProgramResData") == 0 || /* some kind of map/object-like config to make sequences in some cases? */
+ strcmp(class_name, "CActorResData") == 0 || /* same? */
+ strcmp(class_name, "CRandomResData") == 0 || /* chooses random WavRes from a list */
+ strcmp(class_name, "CTreeBank") == 0 || /* points to TreeRes? */
+ strcmp(class_name, "CTreeRes") == 0 || /* points to TreeBank? */
+ strcmp(class_name, "CSwitchResData") == 0 || /* big list of WavRes */
+ strcmp(class_name, "CPCWavResData") == 0 || /* points to WaveFileIdObj */
+ strcmp(class_name, "CPS2WavResData") == 0 ||
+ strcmp(class_name, "CGCWavResData") == 0 ||
+ strcmp(class_name, "CXBoxWavResData") == 0 ||
+ strcmp(class_name, "CPS3WavResData") == 0) {
+ continue;
+ }
+ else if (strcmp(class_name, "CPCWaveFileIdObj") == 0 ||
+ strcmp(class_name, "CPS2WaveFileIdObj") == 0 ||
+ strcmp(class_name, "CGCWaveFileIdObj") == 0 ||
+ strcmp(class_name, "CXBoxStaticHWWaveFileIdObj") == 0 ||
+ strcmp(class_name, "CXBoxStreamHWWaveFileIdObj") == 0 ||
+ strcmp(class_name, "CPS3StaticAC3WaveFileIdObj") == 0 ||
+ strcmp(class_name, "CPS3StreamAC3WaveFileIdObj") == 0) {
+ ;
+ }
+ else {
+ VGM_LOG("UBI HX: unknown type: %s\n", class_name);
+ goto fail;
+ }
+
+ if (link_count != 0) {
+ VGM_LOG("UBI HX: found links in wav object\n");
+ goto fail;
+ }
+
+ hx->total_subsongs++;
+ if (hx->total_subsongs != target_subsong)
+ continue;
+
+ if (!parse_header(hx, sf, header_offset, header_size, i))
+ goto fail;
+ if (!parse_name(hx, sf))
+ goto fail;
+
+ build_readable_name(hx->readable_name,sizeof(hx->readable_name), hx);
+ }
+
+ if (target_subsong < 0 || target_subsong > hx->total_subsongs || hx->total_subsongs < 1) goto fail;
+
+
+ return 1;
+fail:
+ return 0;
+}
+
+
+static STREAMFILE * open_hx_streamfile(ubi_hx_header *hx, STREAMFILE *sf) {
+ STREAMFILE *streamData = NULL;
+
+
+ if (!hx->is_external)
+ return NULL;
+
+ streamData = open_streamfile_by_filename(sf, hx->resource_name);
+ if (streamData == NULL) {
+ VGM_LOG("UBI HX: external stream '%s' not found\n", hx->resource_name);
+ goto fail;
+ }
+
+ /* streams often have a "RIFF" with "fmt" and "data" but stream offset/size is already adjusted to skip them */
+
+ return streamData;
+fail:
+ return NULL;
+}
+
+static VGMSTREAM * init_vgmstream_ubi_hx_header(ubi_hx_header *hx, STREAMFILE *streamFile) {
+ STREAMFILE *streamTemp = NULL;
+ STREAMFILE *streamData = NULL;
+ VGMSTREAM* vgmstream = NULL;
+
+
+ if (hx->is_external) {
+ streamTemp = open_hx_streamfile(hx, streamFile);
+ if (streamTemp == NULL) goto fail;
+ streamData = streamTemp;
+ }
+ else {
+ streamData = streamFile;
+ }
+
+
+ /* build the VGMSTREAM */
+ vgmstream = allocate_vgmstream(hx->channels, hx->loop_flag);
+ if (!vgmstream) goto fail;
+
+ vgmstream->meta_type = meta_UBI_HX;
+ vgmstream->sample_rate = hx->sample_rate;
+ vgmstream->num_streams = hx->total_subsongs;
+ vgmstream->stream_size = hx->stream_size;
+
+ switch(hx->codec) {
+ case PCM:
+ vgmstream->coding_type = coding_PCM16LE;
+ vgmstream->layout_type = layout_interleave;
+ vgmstream->interleave_block_size = 0x02;
+
+ vgmstream->num_samples = pcm_bytes_to_samples(hx->stream_size, hx->channels, 16);
+ break;
+
+ case UBI:
+ vgmstream->codec_data = init_ubi_adpcm(streamData, hx->stream_offset, vgmstream->channels);
+ if (!vgmstream->codec_data) goto fail;
+ vgmstream->coding_type = coding_UBI_ADPCM;
+ vgmstream->layout_type = layout_none;
+
+ vgmstream->num_samples = ubi_adpcm_get_samples(vgmstream->codec_data);
+ /* XIII has 6-bit stereo music, Rayman 3 4-bit music, both use 6-bit mono) */
+ break;
+
+ case PSX:
+ vgmstream->coding_type = coding_PSX;
+ vgmstream->layout_type = layout_interleave;
+ vgmstream->interleave_block_size = 0x10;
+
+ vgmstream->num_samples = ps_bytes_to_samples(hx->stream_size, hx->channels);
+ break;
+
+ case DSP:
+ vgmstream->coding_type = coding_NGC_DSP;
+ vgmstream->layout_type = layout_interleave;
+ vgmstream->interleave_block_size = 0x08;
+
+ /* dsp header at start offset */
+ vgmstream->num_samples = read_32bitBE(hx->stream_offset + 0x00, streamData);
+ dsp_read_coefs_be(vgmstream, streamData, hx->stream_offset + 0x1c, 0x60);
+ dsp_read_hist_be (vgmstream, streamData, hx->stream_offset + 0x40, 0x60);
+ hx->stream_offset += 0x60 * hx->channels;
+ hx->stream_size -= 0x60 * hx->channels;
+ break;
+
+ case XIMA:
+ vgmstream->coding_type = coding_XBOX_IMA;
+ vgmstream->layout_type = layout_none;
+
+ vgmstream->num_samples = xbox_ima_bytes_to_samples(hx->stream_size, hx->channels);
+ break;
+
+#ifdef VGM_USE_FFMPEG
+ case XMA2: {
+ int bytes, block_count, block_size;
+ uint8_t buf[0x200];
+
+ block_size = 0x800;
+ block_count = hx->stream_size / block_size;
+
+ bytes = ffmpeg_make_riff_xma2(buf,0x200, hx->num_samples, hx->stream_size, hx->channels, hx->sample_rate, block_count, block_size);
+ vgmstream->codec_data = init_ffmpeg_header_offset(streamData, buf,bytes, hx->stream_offset,hx->stream_size);
+ if (!vgmstream->codec_data) goto fail;
+ vgmstream->coding_type = coding_FFmpeg;
+ vgmstream->layout_type = layout_none;
+
+ vgmstream->num_samples = hx->num_samples;
+
+ xma_fix_raw_samples_ch(vgmstream, streamData, hx->stream_offset,hx->stream_size, hx->channels, 0,0);
+ break;
+ }
+
+ case ATRAC3: {
+ int block_align, encoder_delay;
+
+ encoder_delay = 1024 + 69*2;
+ switch(hx->codec_id) {
+ case 4: block_align = 0x60 * vgmstream->channels; break;
+ case 5: block_align = 0x98 * vgmstream->channels; break;
+ case 6: block_align = 0xC0 * vgmstream->channels; break;
+ default: goto fail;
+ }
+
+ vgmstream->num_samples = atrac3_bytes_to_samples(hx->stream_size, block_align) - encoder_delay;
+
+ vgmstream->codec_data = init_ffmpeg_atrac3_raw(streamData, hx->stream_offset,hx->stream_size, vgmstream->num_samples,vgmstream->channels,vgmstream->sample_rate, block_align, encoder_delay);
+ if (!vgmstream->codec_data) goto fail;
+ vgmstream->coding_type = coding_FFmpeg;
+ vgmstream->layout_type = layout_none;
+ break;
+ }
+#endif
+
+ default:
+ goto fail;
+ }
+
+ strcpy(vgmstream->stream_name, hx->readable_name);
+
+ if (!vgmstream_open_stream(vgmstream, streamData, hx->stream_offset))
+ goto fail;
+ close_streamfile(streamTemp);
+ return vgmstream;
+
+fail:
+ close_streamfile(streamTemp);
+ close_vgmstream(vgmstream);
+ return NULL;
+}
diff --git a/src/vgmstream.c b/src/vgmstream.c
index e13a84f8..88c7cf50 100644
--- a/src/vgmstream.c
+++ b/src/vgmstream.c
@@ -477,6 +477,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_nub_idsp,
init_vgmstream_nub_is14,
init_vgmstream_xmv_valve,
+ init_vgmstream_ubi_hx,
/* 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 */
@@ -501,7 +502,7 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) {
fcns_size = (sizeof(init_vgmstream_functions)/sizeof(init_vgmstream_functions[0]));
/* try a series of formats, see which works */
- for (i =0; i < fcns_size; i++) {
+ for (i = 0; i < fcns_size; i++) {
/* call init function and see if valid VGMSTREAM was returned */
VGMSTREAM * vgmstream = (init_vgmstream_functions[i])(streamFile);
if (!vgmstream)
diff --git a/src/vgmstream.h b/src/vgmstream.h
index b7d7b935..823a86d2 100644
--- a/src/vgmstream.h
+++ b/src/vgmstream.h
@@ -711,6 +711,7 @@ typedef enum {
meta_DSP_ITL_i,
meta_IMA,
meta_XMV_VALVE,
+ meta_UBI_HX,
} meta_t;