diff --git a/src/formats.c b/src/formats.c
index 2b45dd1a..b01867a4 100644
--- a/src/formats.c
+++ b/src/formats.c
@@ -26,6 +26,7 @@ static const char* extension_list[] = {
"aax",
"abk",
//"ac3", //common, FFmpeg/not parsed (AC3)
+ "acb",
"ace", //fake extension for tri-Ace's .aac (renamed, to be removed)
"acm",
"ad", //txth/reserved [Xenosaga Freaks (PS2)]
diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj
index 7efddf5f..dafbda9d 100644
--- a/src/libvgmstream.vcproj
+++ b/src/libvgmstream.vcproj
@@ -232,6 +232,10 @@
RelativePath=".\meta\aax_utf.h"
>
+
+
@@ -396,6 +400,10 @@
RelativePath=".\meta\aax.c"
>
+
+
diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj
index 58b5600d..d964b2a5 100644
--- a/src/libvgmstream.vcxproj
+++ b/src/libvgmstream.vcxproj
@@ -101,6 +101,7 @@
+
@@ -231,6 +232,7 @@
+
diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters
index 6537c291..aa304cc3 100644
--- a/src/libvgmstream.vcxproj.filters
+++ b/src/libvgmstream.vcxproj.filters
@@ -71,6 +71,9 @@
meta\Header Files
+
+ meta\Header Files
+
meta\Header Files
@@ -259,6 +262,9 @@
meta\Source Files
+
+ meta\Source Files
+
meta\Source Files
diff --git a/src/meta/acb.c b/src/meta/acb.c
new file mode 100644
index 00000000..b77ec8d8
--- /dev/null
+++ b/src/meta/acb.c
@@ -0,0 +1,566 @@
+#include "meta.h"
+#include "../coding/coding.h"
+#include "acb_utf.h"
+
+
+/* ACB (Atom Cue sheet Binary) - CRI container of memory audio, often together with a .awb wave bank */
+VGMSTREAM * init_vgmstream_acb(STREAMFILE *streamFile) {
+ VGMSTREAM *vgmstream = NULL;
+ STREAMFILE *temp_streamFile = NULL;
+ off_t subfile_offset;
+ size_t subfile_size;
+ utf_context *utf = NULL;
+
+
+ /* checks */
+ if (!check_extensions(streamFile, "acb"))
+ goto fail;
+ if (read_32bitBE(0x00,streamFile) != 0x40555446) /* "@UTF" */
+ goto fail;
+
+ /* .acb is a cue sheet that uses @UTF (CRI's generic table format) to store row/columns
+ * with complex info (cues, sequences, spatial info, etc). it can store a memory .awb
+ * (our target here), or reference external/streamed .awb (loaded elsewhere)
+ * we only want .awb with actual waves but may use .acb to get names */
+ {
+ int rows;
+ const char* name;
+ uint32_t offset = 0, size = 0;
+ uint32_t table_offset = 0x00;
+
+ utf = utf_open(streamFile, table_offset, &rows, &name);
+ if (!utf) goto fail;
+
+ if (rows != 1 || strcmp(name, "Header") != 0)
+ goto fail;
+
+ //todo acb+cpk is also possible
+
+ if (!utf_query_data(streamFile, utf, 0, "AwbFile", &offset, &size))
+ goto fail;
+
+ subfile_offset = table_offset + offset;
+ subfile_size = size;
+
+ /* column exists but can be empty */
+ if (subfile_size == 0)
+ goto fail;
+ }
+
+ //;VGM_LOG("ACB: subfile offset=%lx + %x\n", subfile_offset, subfile_size);
+
+ temp_streamFile = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, "awb");
+ if (!temp_streamFile) goto fail;
+
+ vgmstream = init_vgmstream_awb_memory(temp_streamFile, streamFile);
+ if (!vgmstream) goto fail;
+
+ /* name-loading for this for memory .awb will be called from init_vgmstream_awb_memory */
+
+ utf_close(utf);
+ close_streamfile(temp_streamFile);
+ return vgmstream;
+
+fail:
+ utf_close(utf);
+ close_streamfile(temp_streamFile);
+ close_vgmstream(vgmstream);
+ return NULL;
+}
+
+/* ************************************** */
+
+typedef struct {
+ utf_context *Header;
+ utf_context *CueNameTable;
+ utf_context *CueTable;
+ utf_context *SequenceTable;
+ utf_context *TrackTable;
+ utf_context *TrackEventTable;
+ utf_context *CommandTable;
+ utf_context *SynthTable;
+ utf_context *WaveformTable;
+
+ char name[1024];
+ int is_memory;
+ int has_TrackEventTable;
+ int has_CommandTable;
+
+ int16_t CueNameIndex;
+ const char* CueName;
+ int16_t CueIndex;
+ int16_t ReferenceIndex;
+ int8_t ReferenceType;
+
+ int16_t NumTracks;
+ uint32_t TrackIndex_offset;
+ uint32_t TrackIndex_size;
+ int16_t TrackIndex;
+ int16_t EventIndex;
+ uint32_t Command_offset;
+ uint32_t Command_size;
+ int16_t SynthIndex_count;
+ int16_t SynthIndex_list[255];
+ int16_t SynthIndex;
+
+ int8_t SynthType;
+ uint32_t ReferenceItems_offset;
+ uint32_t ReferenceItems_size;
+ int ReferenceItems_count;
+ int16_t ReferenceItems_list[255];
+
+ int16_t ReferenceItem;
+ int16_t AwbId;
+ int8_t AwbStreaming;
+
+ int is_wave_found;
+
+ int AwbName_count;
+ int16_t AwbName_list[255];
+
+} acb_header;
+
+static int load_utf_subtable(STREAMFILE *acbFile, acb_header* acb, utf_context* *Table, const char* TableName, int* rows) {
+ uint32_t offset = 0;
+
+ /* already loaded */
+ if (*Table != NULL)
+ return 1;
+
+ if (!utf_query_data(acbFile, acb->Header, 0, TableName, &offset, NULL))
+ goto fail;
+ *Table = utf_open(acbFile, offset, rows, NULL);
+ if (!*Table) goto fail;
+
+
+ ;VGM_LOG("ACB: loaded table %s\n", TableName);
+ return 1;
+fail:
+ return 0;
+}
+
+static int load_acb_cue_info(STREAMFILE *acbFile, acb_header* acb) {
+
+ /* read Cue[CueNameIndex] */
+ if (!utf_query_s16(acbFile, acb->CueNameTable, acb->CueNameIndex, "CueIndex", &acb->CueIndex))
+ goto fail;
+ if (!utf_query_string(acbFile, acb->CueNameTable, acb->CueNameIndex, "CueName", &acb->CueName))
+ goto fail;
+ ;VGM_LOG("ACB: CueName[%i]: CueIndex=%i, CueName=%s\n", acb->CueNameIndex, acb->CueIndex, acb->CueName);
+
+ /* read Cue[CueIndex] */
+ if (!load_utf_subtable(acbFile, acb, &acb->CueTable, "CueTable", NULL))
+ goto fail;
+ if (!utf_query_s8 (acbFile, acb->CueTable, acb->CueIndex, "ReferenceType", &acb->ReferenceType))
+ goto fail;
+ if (!utf_query_s16(acbFile, acb->CueTable, acb->CueIndex, "ReferenceIndex", &acb->ReferenceIndex))
+ goto fail;
+ ;VGM_LOG("ACB: Cue[%i]: ReferenceType=%i, ReferenceIndex=%i\n", acb->CueIndex, acb->ReferenceType, acb->ReferenceIndex);
+
+ return 1;
+fail:
+ return 0;
+}
+
+static int load_acb_sequence(STREAMFILE *acbFile, acb_header* acb) {
+
+ /* read Sequence[ReferenceIndex] */
+ if (!load_utf_subtable(acbFile, acb, &acb->SequenceTable, "SequenceTable", NULL))
+ goto fail;
+ if (!utf_query_s16(acbFile, acb->SequenceTable, acb->ReferenceIndex, "NumTracks", &acb->NumTracks))
+ goto fail;
+ if (!utf_query_data(acbFile, acb->SequenceTable, acb->ReferenceIndex, "TrackIndex", &acb->TrackIndex_offset, &acb->TrackIndex_size))
+ goto fail;
+ ;VGM_LOG("ACB: Sequence[%i]: NumTracks=%i, TrackIndex={%x, %x}\n", acb->ReferenceIndex, acb->NumTracks, acb->TrackIndex_offset,acb->TrackIndex_size);
+
+ if (acb->NumTracks * 0x02 > acb->TrackIndex_size) { /* padding may exist */
+ VGM_LOG("ACB: unknown TrackIndex size\n");
+ goto fail;
+ }
+
+ return 1;
+fail:
+ return 0;
+}
+
+static int load_acb_track_command(STREAMFILE *acbFile, acb_header* acb) {
+
+ /* read Track[TrackIndex] */
+ if (!load_utf_subtable(acbFile, acb, &acb->TrackTable, "TrackTable", NULL))
+ goto fail;
+ if (!utf_query_s16(acbFile, acb->TrackTable, acb->TrackIndex, "EventIndex", &acb->EventIndex))
+ goto fail;
+ ;VGM_LOG("ACB: Track[%i]: EventIndex=%i\n", acb->TrackIndex, acb->EventIndex);
+
+ /* depending on version next stuff varies a bit, check by table existence */
+ if (acb->has_TrackEventTable) {
+ /* read TrackEvent[EventIndex] */
+ if (!load_utf_subtable(acbFile, acb, &acb->TrackEventTable, "TrackEventTable", NULL))
+ goto fail;
+ if (!utf_query_data(acbFile, acb->TrackEventTable, acb->EventIndex, "Command", &acb->Command_offset, &acb->Command_size))
+ goto fail;
+ ;VGM_LOG("ACB: TrackEvent[%i]: Command={%x,%x}\n", acb->EventIndex, acb->Command_offset,acb->Command_size);
+ }
+ else if (acb->has_CommandTable) {
+ /* read Command[EventIndex] */
+ if (!load_utf_subtable(acbFile, acb, &acb->CommandTable, "CommandTable", NULL))
+ goto fail;
+ if (!utf_query_data(acbFile, acb->CommandTable, acb->EventIndex, "Command", &acb->Command_offset, &acb->Command_size))
+ goto fail;
+ ;VGM_LOG("ACB: Command[%i]: Command={%x,%x}\n", acb->EventIndex, acb->Command_offset,acb->Command_size);
+ }
+ else {
+ VGM_LOG("ACB: unknown command table\n");
+ }
+
+ /* read Command (some kind of multiple TLVs, this seems ok) */
+ {
+ uint32_t offset = acb->Command_offset;
+ uint32_t max_offset = acb->Command_offset + acb->Command_size;
+ uint16_t code, subcode, subindex;
+ uint8_t size;
+
+ acb->SynthIndex_count = 0;
+
+ while (offset < max_offset) {
+ code = read_u16be(offset + 0x00, acbFile);
+ size = read_u8 (offset + 0x02, acbFile);
+ offset += 0x03;
+
+ if (code == 0x07D0) {
+ if (size < 0x04) {
+ VGM_LOG("ACB: subcommand with unknown size\n");
+ break;
+ }
+
+ subcode = read_u16be(offset + 0x00, acbFile);
+ subindex = read_u16be(offset + 0x02, acbFile);
+
+ /* reference to Synth/Waveform like those in Synth? */
+ if (subcode != 0x02) { //todo some in Yakuza Kiwami 2 usen.acb use 03 (random? see Synth)
+ VGM_LOG("ACB: subcommand with unknown subcode\n");
+ break;
+ }
+
+ acb->SynthIndex_list[acb->SynthIndex_count] = subindex;
+ acb->SynthIndex_count++;
+ if (acb->SynthIndex_count >= 254)
+ acb->ReferenceItems_count = 254; /* ??? */
+
+ ;VGM_LOG("ACB: subcommand index %i found\n", subindex);
+ }
+
+ /* 0x07D1 comes suspiciously often paired with 0x07D0 too */
+
+ offset += size;
+ }
+ }
+
+ return 1;
+fail:
+ return 0;
+}
+
+static int load_acb_synth(STREAMFILE *acbFile, acb_header* acb) {
+ int i;
+
+ /* read Synth[SynthIndex] */
+ if (!load_utf_subtable(acbFile, acb, &acb->SynthTable, "SynthTable", NULL))
+ goto fail;
+ if (!utf_query_s8(acbFile, acb->SynthTable, acb->SynthIndex, "Type", &acb->SynthType))
+ goto fail;
+ if (!utf_query_data(acbFile, acb->SynthTable, acb->SynthIndex, "ReferenceItems", &acb->ReferenceItems_offset, &acb->ReferenceItems_size))
+ goto fail;
+ ;VGM_LOG("ACB: Synth[%i]: ReferenceItems={%x,%x}\n", acb->SynthIndex, acb->ReferenceItems_offset, acb->ReferenceItems_size);
+
+
+ acb->ReferenceItems_count = acb->ReferenceItems_size / 0x04;
+ if (acb->ReferenceItems_count >= 254)
+ acb->ReferenceItems_count = 254; /* ??? */
+
+ /* ReferenceType 2 uses Synth.Type, while 3 always sets it to 0 and uses Sequence.Type instead
+ * they probably affect which item in the reference list is picked:
+ * 0: polyphonic
+ * 1: sequential
+ * 2: shuffle
+ * 3: random
+ * 4: no repeat
+ * 5: switch game variable
+ * 6: combo sequential
+ * 7: switch selector
+ * 8: track transition by selector
+ * other: undefined?
+ */
+
+ for (i = 0; i < acb->ReferenceItems_count; i++) {
+ uint16_t type, subtype, index, subindex;
+ uint32_t suboffset, subsize;
+
+ type = read_u16be(acb->ReferenceItems_offset + i*0x04 + 0x00, acbFile);
+ index = read_u16be(acb->ReferenceItems_offset + i*0x04 + 0x02, acbFile);
+ ;VGM_LOG("ACB: Synth reference type=%x, index=%x\n", type, index);
+
+ switch(type) {
+ case 0x00: /* no reference */
+ acb->ReferenceItems_count = 0;
+ break;
+
+ case 0x01: /* Waveform reference (most common) */
+ acb->ReferenceItems_list[i] = index;
+ break;
+
+ case 0x02: /* Synth reference (rare, found in Sonic Lost World with ReferenceType 2) */
+ if (!utf_query_data(acbFile, acb->SynthTable, index, "ReferenceItems", &suboffset, &subsize))
+ goto fail;
+
+ /* assuming only 1:1 references are ok */
+ if (subsize != 0x04) {
+ VGM_LOG("ACB: unknown Synth subreference size\n");
+ break;
+ }
+
+ subtype = read_u16be(suboffset + 0x00, acbFile);
+ subindex = read_u16be(suboffset + 0x02, acbFile);
+
+ /* AtomViewer crashes if it points to another to Synth */
+ if (subtype != 0x01) {
+ VGM_LOG("ACB: unknown Synth subreference type\n");
+ break;
+ }
+
+ acb->ReferenceItems_list[i] = subindex;
+ ;VGM_LOG("ACB: Synth subreference type=%x, index=%x\n", subtype, subindex);
+ break;
+
+ case 0x03: /* random Synths with % in TrackValues (rare, found in Sonic Lost World with ReferenceType 2) */
+ //todo fix
+ /* this points to next? N Synths (in turn pointing to a Waveform), but I see no relation between
+ * index value and pointed Synths plus those Synths don't seem referenced otherwise.
+ * ex. se_phantom_asteroid.acb
+ * Synth[26] with index 7 points to Synth[27/28]
+ * Synth[26] with index 6 points to Synth[24/25]
+ * Synth[26] with index 8 points to Synth[29/30]
+ * Synth[26] with index 9 points to Synth[32/33] (Synth[30] is another random Type 3)
+ */
+ default: /* undefined/crashes AtomViewer */
+ VGM_LOG("ACB: unknown Synth reference type\n");
+ acb->ReferenceItems_count = 0;
+ break;
+ }
+ }
+
+ return 1;
+fail:
+ return 0;
+}
+
+static int load_acb_waveform(STREAMFILE *acbFile, acb_header* acb, int waveid) {
+
+ /* read Waveform[ReferenceItem] */
+ if (!load_utf_subtable(acbFile, acb, &acb->WaveformTable, "WaveformTable", NULL))
+ goto fail;
+ if (!utf_query_s8(acbFile, acb->WaveformTable, acb->ReferenceItem, "Streaming", &acb->AwbStreaming))
+ goto fail;
+ if (!utf_query_s16(acbFile, acb->WaveformTable, acb->ReferenceItem, "Id", &acb->AwbId)) { /* older versions use Id */
+ if (acb->is_memory) {
+ if (!utf_query_s16(acbFile, acb->WaveformTable, acb->ReferenceItem, "MemoryAwbId", &acb->AwbId))
+ goto fail;
+ } else {
+ if (!utf_query_s16(acbFile, acb->WaveformTable, acb->ReferenceItem, "StreamAwbId", &acb->AwbId))
+ goto fail;
+ }
+ }
+ ;VGM_LOG("ACB: Waveform[%i]: AwbId=%i, AwbStreaming=%i\n", acb->ReferenceItem, acb->AwbId, acb->AwbStreaming);
+
+ acb->is_wave_found = 0; /* reset */
+
+ if (acb->AwbId != waveid)
+ return 1;
+ /* 0=memory, 1=streaming, 2=memory (preload)+stream */
+ if ((acb->is_memory && acb->AwbStreaming == 1) || (!acb->is_memory && acb->AwbStreaming == 0))
+ return 1;
+
+ acb->is_wave_found = 1;
+
+ return 1;
+fail:
+ return 0;
+}
+
+static void add_acb_name(STREAMFILE *acbFile, acb_header* acb) {
+ //todo safe string ops
+
+ /* aaand finally get name (phew) */
+
+ /* ignore name repeats */
+ if (acb->AwbName_count) {
+ int i;
+ for (i = 0; i < acb->AwbName_count; i++) {
+ if (acb->AwbName_list[i] == acb->CueNameIndex)
+ return;
+ }
+ }
+
+ /* since waveforms can be reused by cues multiple names are a thing */
+ if (acb->AwbName_count) {
+ strcat(acb->name, "; ");
+ strcat(acb->name, acb->CueName);
+ }
+ else {
+ strcpy(acb->name, acb->CueName);
+ }
+ if (acb->AwbStreaming == 2 && acb->is_memory) {
+ strcat(acb->name, " [pre]");
+ }
+
+ acb->AwbName_list[acb->AwbName_count] = acb->CueNameIndex;
+ acb->AwbName_count++;
+ if (acb->AwbName_count >= 254)
+ acb->AwbName_count = 254; /* ??? */
+
+ ;VGM_LOG("ACB: found cue for waveid=%i: %s\n", acb->AwbId, acb->CueName);
+}
+
+
+void load_acb_wave_name(STREAMFILE *acbFile, VGMSTREAM* vgmstream, int waveid, int is_memory) {
+ acb_header acb = {0};
+ int CueName_rows, CueName_i, TrackIndex_i, ReferenceItems_i, SynthIndex_i;
+
+ if (!acbFile || !vgmstream || waveid < 0)
+ return;
+
+ /* Normally games load a .acb + .awb, and asks the .acb to play a cue by name or index.
+ * Since we only care for actual waves, to get its name we need to find which cue uses our wave.
+ * Multiple cues can use the same wave (meaning multiple names), and one cue may use multiple waves.
+ * There is no easy way to map cue name <> wave name so basically we parse the whole thing.
+ *
+ * .acb cues are created in CRI Atom Craft roughly like this:
+ * - user creates N Cues with CueName
+ * - Cues define Sequences of Tracks
+ * - depending on reference types:
+ * - Track points directly to Waveform (type 1)
+ * - Track points to Synth then to Waveform (type 2)
+ * - Track points to Commands with binary Command that points to Synth then to Waveform (type 3 <=v1.27)
+ * - Track points to TrackEvent with binary Command that points to Synth then to Waveform (type 3 >=v1.28)
+ * (games may use multiple versions and reference types)
+ * - Waveforms are audio materials encoded in some format
+ * - Waveforms are saved into .awb, that can be streamed (loaded manually) or internal,
+ * and has a checksum/hash to validate
+ * - there is a version value (sometimes written incorrectly) and string,
+ * Atom Craft may only target certain .acb versions
+ */
+
+ ;VGM_LOG("ACB: find waveid=%i\n", waveid);
+
+ acb.Header = utf_open(acbFile, 0x00, NULL, NULL);
+ if (!acb.Header) goto done;
+
+ acb.is_memory = is_memory;
+ acb.has_TrackEventTable = utf_query_data(acbFile, acb.Header, 0, "TrackEventTable", NULL,NULL);
+ acb.has_CommandTable = utf_query_data(acbFile, acb.Header, 0, "CommandTable", NULL,NULL);
+
+
+ /* read CueName[i] */
+ if (!load_utf_subtable(acbFile, &acb, &acb.CueNameTable, "CueNameTable", &CueName_rows)) goto done;
+ ;VGM_LOG("ACB: CueNames=%i\n", CueName_rows);
+
+ for (CueName_i = 0; CueName_i < CueName_rows; CueName_i++) {
+ acb.CueNameIndex = CueName_i;
+
+ if (!load_acb_cue_info(acbFile, &acb))
+ goto done;
+
+ /* meaning of Cue.ReferenceIndex */
+ switch(acb.ReferenceType) {
+
+ case 1: { /* Cue > Waveform (ex. PES 2015) */
+ acb.ReferenceItem = acb.ReferenceIndex;
+
+ if (!load_acb_waveform(acbFile, &acb, waveid))
+ goto done;
+
+ if (acb.is_wave_found)
+ add_acb_name(acbFile, &acb);
+
+ break;
+ }
+
+ case 2: { /* Cue > Synth > Waveform (ex. Ukiyo no Roushi) */
+ acb.SynthIndex = acb.ReferenceIndex;
+
+ if (!load_acb_synth(acbFile, &acb))
+ goto done;
+
+ for (ReferenceItems_i = 0; ReferenceItems_i < acb.ReferenceItems_count; ReferenceItems_i++) {
+ acb.ReferenceItem = acb.ReferenceItems_list[ReferenceItems_i];
+
+ if (!load_acb_waveform(acbFile, &acb, waveid))
+ goto done;
+
+ if (acb.is_wave_found)
+ add_acb_name(acbFile, &acb);
+ }
+
+ break;
+ }
+
+ case 3: { /* Cue > Sequence > Track > Command > Synth > Waveform (ex. Valkyrie Profile anatomia, Yakuza Kiwami 2) */
+
+ if (!load_acb_sequence(acbFile, &acb))
+ goto done;
+
+ /* read Tracks inside Sequence */
+ for (TrackIndex_i = 0; TrackIndex_i < acb.NumTracks; TrackIndex_i++) {
+ acb.TrackIndex = read_s16be(acb.TrackIndex_offset + TrackIndex_i*0x02, acbFile);
+
+ if (!load_acb_track_command(acbFile, &acb))
+ goto done;
+
+ for (SynthIndex_i = 0; SynthIndex_i < acb.SynthIndex_count; SynthIndex_i++) {
+ acb.SynthIndex = acb.SynthIndex_list[SynthIndex_i];
+
+ if (!load_acb_synth(acbFile, &acb))
+ goto done;
+
+ for (ReferenceItems_i = 0; ReferenceItems_i < acb.ReferenceItems_count; ReferenceItems_i++) {
+ acb.ReferenceItem = acb.ReferenceItems_list[ReferenceItems_i];
+
+ if (!load_acb_waveform(acbFile, &acb, waveid))
+ goto done;
+
+ if (acb.is_wave_found)
+ add_acb_name(acbFile, &acb);
+ }
+ }
+ }
+
+ break;
+ }
+
+ case 8: //todo found on Sonic Lost World (maybe not references wave?)
+ default:
+ VGM_LOG("ACB: unknown reference type\n");
+ break;
+ }
+
+ //if (acb.AwbId_count > 0)
+ // break;
+ }
+
+ /* meh copy */
+ if (acb.AwbName_count > 0) {
+ strncpy(vgmstream->stream_name, acb.name, STREAM_NAME_SIZE);
+ vgmstream->stream_name[STREAM_NAME_SIZE - 1] = '\0';
+ }
+
+done:
+ utf_close(acb.Header);
+ utf_close(acb.CueNameTable);
+ utf_close(acb.CueTable);
+ utf_close(acb.SequenceTable);
+ utf_close(acb.TrackTable);
+ utf_close(acb.TrackEventTable);
+ utf_close(acb.CommandTable);
+ utf_close(acb.SynthTable);
+ utf_close(acb.WaveformTable);
+}
diff --git a/src/meta/acb_utf.h b/src/meta/acb_utf.h
new file mode 100644
index 00000000..e081ca40
--- /dev/null
+++ b/src/meta/acb_utf.h
@@ -0,0 +1,423 @@
+#ifndef _ACB_UTF_H_
+#define _ACB_UTF_H_
+
+/* CRI @UTF (Universal Table Format?) is a generic database-like table made of
+ * rows/columns that contain numbers/strings/ binarydata, which also can be other tables.
+ *
+ * A table starts with "@UTF" and defines some values (row/data/string offsets, counts, etc)
+ * then schema (columns type+name), then rows, string table and binary data. Formats using @UTF
+ * store and read data by row number + column name. Being a generic table with no fixed schema
+ * CRI uses it for different purposes (.acf: cues, .cpk: files, .aax: bgm, .usm: video, etc).
+ *
+ * (adapted from hcs's code to do multiple querys in the same table)
+ */
+
+// todo divide into some .c file and use for other @UTF parsing
+
+/* API */
+typedef struct utf_context utf_context; /* opaque struct */
+/*static*/ utf_context* utf_open(STREAMFILE *streamfile, uint32_t table_offset, int* rows, const char* *row_name);
+/*static*/ void utf_close(utf_context *utf);
+
+/*static*/ int utf_query_s8(STREAMFILE *streamfile, utf_context *utf, int row, const char* column, int8_t* value);
+/*static*/ int utf_query_s16(STREAMFILE *streamfile, utf_context *utf, int row, const char* column, int16_t* value);
+/*static*/ int utf_query_string(STREAMFILE *streamfile, utf_context *utf, int row, const char* column, const char* *value);
+/*static*/ int utf_query_data(STREAMFILE *streamfile, utf_context *utf, int row, const char* column, uint32_t *offset, uint32_t *size);
+
+/* ************************************************* */
+/* INTERNALS */
+
+/* possibly 3b+5b from clUTF decompilation */
+#define COLUMN_BITMASK_STORAGE 0xf0
+#define COLUMN_BITMASK_TYPE 0x0f
+
+#define COLUMN_STORAGE_ZERO 0x10
+#define COLUMN_STORAGE_CONSTANT 0x30
+#define COLUMN_STORAGE_ROW 0x50
+//#define COLUMN_STORAGE_CONSTANT2 0x70 /* from vgmtoolbox */
+
+#define COLUMN_TYPE_SINT8 0x00
+#define COLUMN_TYPE_UINT8 0x01
+#define COLUMN_TYPE_SINT16 0x02
+#define COLUMN_TYPE_UINT16 0x03
+#define COLUMN_TYPE_SINT32 0x04
+#define COLUMN_TYPE_UINT32 0x05
+#define COLUMN_TYPE_SINT64 0x06
+//#define COLUMN_TYPE_UINT64 0x07
+#define COLUMN_TYPE_FLOAT 0x08
+//#define COLUMN_TYPE_DOUBLE 0x09
+#define COLUMN_TYPE_STRING 0x0a
+#define COLUMN_TYPE_DATA 0x0b
+
+
+typedef struct {
+ uint32_t offset;
+ uint32_t size;
+} utf_data_t;
+typedef struct {
+ int valid; /* table is valid */
+ int found;
+ int type; /* one of COLUMN_TYPE_* */
+ union {
+ int8_t value_s8;
+ uint8_t value_u8;
+ int16_t value_s16;
+ uint16_t value_u16;
+ int32_t value_s32;
+ uint32_t value_u32;
+ int64_t value_s64;
+ uint64_t value_u64;
+ float value_float;
+ double value_double;
+ utf_data_t value_data;
+ const char *value_string;
+ } value;
+} utf_result;
+
+
+typedef struct {
+ uint8_t flags;
+ const char *name;
+ uint32_t offset;
+} utf_column;
+
+struct utf_context {
+ uint32_t table_offset;
+
+ /* header */
+ uint32_t table_size;
+ uint16_t version;
+ uint16_t rows_offset;
+ uint32_t strings_offset;
+ uint32_t data_offset;
+ uint32_t name_offset;
+ uint16_t columns;
+ uint16_t row_width;
+ uint32_t rows;
+ /*const*/ utf_column *schema;
+
+ /* derived */
+ uint32_t schema_offset;
+ uint32_t strings_size;
+ /*const*/ char *string_table;
+ const char *table_name;
+};
+
+
+/* @UTF table reading, abridged */
+/*static*/ utf_context* utf_open(STREAMFILE *streamfile, uint32_t table_offset, int* rows, const char* *row_name) {
+ utf_context* utf = NULL;
+
+
+ utf = calloc(1, sizeof(utf_context));
+ if (!utf) goto fail;
+
+ utf->table_offset = table_offset;
+
+ /* check header */
+ if (read_32bitBE(table_offset + 0x00, streamfile) != 0x40555446) /* "@UTF" */
+ goto fail;
+
+ /* load table header */
+ utf->table_size = read_32bitBE(table_offset + 0x04, streamfile) + 0x08;
+ utf->version = read_16bitBE(table_offset + 0x08, streamfile);
+ utf->rows_offset = read_16bitBE(table_offset + 0x0a, streamfile) + 0x08;
+ utf->strings_offset = read_32bitBE(table_offset + 0x0c, streamfile) + 0x08;
+ utf->data_offset = read_32bitBE(table_offset + 0x10, streamfile) + 0x08;
+ utf->name_offset = read_32bitBE(table_offset + 0x14, streamfile); /* within string table */
+ utf->columns = read_16bitBE(table_offset + 0x18, streamfile);
+ utf->row_width = read_16bitBE(table_offset + 0x1a, streamfile);
+ utf->rows = read_32bitBE(table_offset + 0x1c, streamfile);
+
+ utf->schema_offset = 0x20;
+ utf->strings_size = utf->data_offset - utf->strings_offset;
+
+ /* 00: early (32b rows_offset?), 01: +2017 (no apparent differences) */
+ if (utf->version != 0x00 && utf->version != 0x01) {
+ VGM_LOG("@UTF: unknown version\n");
+ }
+ if (utf->table_offset + utf->table_size > get_streamfile_size(streamfile))
+ goto fail;
+ if (utf->rows == 0 || utf->rows_offset > utf->table_size || utf->data_offset > utf->table_size)
+ goto fail;
+ if (utf->name_offset > utf->strings_size)
+ goto fail;
+
+
+ /* load string table */
+ {
+ size_t read;
+
+ utf->string_table = calloc(utf->strings_size + 1, sizeof(char));
+ if (!utf->string_table) goto fail;
+
+ utf->table_name = utf->string_table + utf->name_offset;
+
+ read = read_streamfile((unsigned char*)utf->string_table, utf->table_offset + utf->strings_offset, utf->strings_size, streamfile);
+ if (utf->strings_size != read) goto fail;
+ }
+
+
+ /* load column schema */
+ {
+ int i;
+ uint32_t value_size, column_offset = 0;
+ uint32_t schema_offset = utf->table_offset + utf->schema_offset;
+
+
+ utf->schema = malloc(sizeof(utf_column) * utf->columns);
+ if (!utf->schema) goto fail;
+
+ for (i = 0; i < utf->columns; i++) {
+ uint8_t flags = read_8bit(schema_offset + 0x00, streamfile);
+ uint32_t name_offset = read_32bitBE(schema_offset + 0x01, streamfile);
+ if (name_offset > utf->strings_size)
+ goto fail;
+
+ utf->schema[i].flags = flags;
+ utf->schema[i].name = utf->string_table + name_offset;
+ schema_offset += 0x01 + 0x04;
+
+ switch (utf->schema[i].flags & COLUMN_BITMASK_TYPE) {
+ case COLUMN_TYPE_SINT8:
+ case COLUMN_TYPE_UINT8:
+ value_size = 0x01;
+ break;
+ case COLUMN_TYPE_SINT16:
+ case COLUMN_TYPE_UINT16:
+ value_size = 0x02;
+ break;
+ case COLUMN_TYPE_SINT32:
+ case COLUMN_TYPE_UINT32:
+ case COLUMN_TYPE_FLOAT:
+ case COLUMN_TYPE_STRING:
+ value_size = 0x04;
+ break;
+ case COLUMN_TYPE_SINT64:
+ //case COLUMN_TYPE_UINT64:
+ //case COLUMN_TYPE_DOUBLE:
+ case COLUMN_TYPE_DATA:
+ value_size = 0x08;
+ break;
+ default:
+ VGM_LOG("@UTF: unknown column type\n");
+ goto fail;
+ }
+
+ switch (utf->schema[i].flags & COLUMN_BITMASK_STORAGE) {
+ case COLUMN_STORAGE_ROW:
+ utf->schema[i].offset = column_offset;
+ column_offset += value_size;
+ break;
+ case COLUMN_STORAGE_CONSTANT:
+ //case COLUMN_STORAGE_CONSTANT2:
+ utf->schema[i].offset = schema_offset - (utf->table_offset + utf->schema_offset); /* relative to schema */
+ schema_offset += value_size;
+ break;
+ case COLUMN_STORAGE_ZERO:
+ utf->schema[i].offset = 0; /* ? */
+ break;
+ default:
+ VGM_LOG("@UTF: unknown column storage\n");
+ goto fail;
+ }
+ }
+ }
+
+
+ /* write info */
+ if (rows) *rows = utf->rows;
+ if (row_name) *row_name = utf->string_table + utf->name_offset;
+
+ return utf;
+fail:
+ utf_close(utf);
+ return NULL;
+}
+
+/*static*/ void utf_close(utf_context *utf) {
+ if (!utf) return;
+
+ free(utf->string_table);
+ free(utf->schema);
+ free(utf);
+}
+
+
+static int utf_query(STREAMFILE *streamfile, utf_context *utf, int row, const char* column, utf_result* result) {
+ int i;
+
+
+ /* fill in the default stuff */
+ result->valid = 0;
+ result->found = 0;
+
+ if (row >= utf->rows || row < 0)
+ goto fail;
+
+ /* find target column */
+ for (i = 0; i < utf->columns; i++) {
+ utf_column* col = &utf->schema[i];
+ uint32_t data_offset;
+
+ if (strcmp(col->name, column) != 0)
+ continue;
+
+ result->found = 1;
+ result->type = col->flags & COLUMN_BITMASK_TYPE;
+
+ switch (col->flags & COLUMN_BITMASK_STORAGE) {
+ case COLUMN_STORAGE_ROW:
+ data_offset = utf->table_offset + utf->rows_offset + row * utf->row_width + col->offset;
+ break;
+ case COLUMN_STORAGE_CONSTANT:
+ //case COLUMN_STORAGE_CONSTANT2:
+ data_offset = utf->table_offset + utf->schema_offset + col->offset;
+ break;
+ case COLUMN_STORAGE_ZERO:
+ data_offset = 0;
+ memset(&result->value, 0, sizeof(result->value));
+ break;
+ default:
+ goto fail;
+ }
+
+ /* ignore zero value */
+ if (!data_offset)
+ break;
+
+ /* read row/constant value */
+ switch (col->flags & COLUMN_BITMASK_TYPE) {
+ case COLUMN_TYPE_SINT8:
+ result->value.value_u8 = read_8bit(data_offset, streamfile);
+ break;
+ case COLUMN_TYPE_UINT8:
+ result->value.value_u8 = (uint8_t)read_8bit(data_offset, streamfile);
+ break;
+ case COLUMN_TYPE_SINT16:
+ result->value.value_s16 = read_16bitBE(data_offset, streamfile);
+ break;
+ case COLUMN_TYPE_UINT16:
+ result->value.value_u16 = (uint16_t)read_16bitBE(data_offset, streamfile);
+ break;
+ case COLUMN_TYPE_SINT32:
+ result->value.value_s32 = read_32bitBE(data_offset, streamfile);
+ break;
+ case COLUMN_TYPE_UINT32:
+ result->value.value_u32 = (uint32_t)read_32bitBE(data_offset, streamfile);
+ break;
+ case COLUMN_TYPE_SINT64:
+ result->value.value_s64 = read_64bitBE(data_offset, streamfile);
+ break;
+#if 0
+ case COLUMN_TYPE_UINT64:
+ result->value.value_u64 = read_64bitBE(data_offset, streamfile);
+ break;
+#endif
+ case COLUMN_TYPE_FLOAT: {
+ union { //todo inline function?
+ float float_value;
+ uint32_t int_value;
+ } cnv;
+
+ if (sizeof(float) != 4) {
+ VGM_LOG("@UTF: can't convert float\n");
+ goto fail;
+ }
+
+ cnv.int_value = (uint32_t)read_32bitBE(data_offset, streamfile);
+ result->value.value_float = cnv.float_value;
+ break;
+ }
+#if 0
+ case COLUMN_TYPE_DOUBLE: {
+ union {
+ double float_value;
+ uint64_t int_value;
+ } cnv;
+
+ if (sizeof(double) != 8) {
+ VGM_LOG("@UTF: can't convert double\n");
+ goto fail;
+ }
+
+ cnv.int_value = (uint64_t)read_64bitBE(data_offset, streamfile);
+ result->value.value_float = cnv.float_value;
+ break;
+ }
+#endif
+ case COLUMN_TYPE_STRING: {
+ uint32_t name_offset = read_32bitBE(data_offset, streamfile);
+ if (name_offset > utf->strings_size)
+ goto fail;
+ result->value.value_string = utf->string_table + name_offset;
+ break;
+ }
+
+ case COLUMN_TYPE_DATA:
+ result->value.value_data.offset = read_32bitBE(data_offset + 0x00, streamfile);
+ result->value.value_data.size = read_32bitBE(data_offset + 0x04, streamfile);
+ break;
+
+ default:
+ goto fail;
+ }
+
+ break; /* column found and read */
+ }
+
+ result->valid = 1;
+ return 1;
+fail:
+ return 0;
+}
+
+////////////////////////////////////////////////////////////
+
+static int utf_query_value(STREAMFILE *streamfile, utf_context *utf, int row, const char* column, void* value, int type) {
+ utf_result result = {0};
+
+ utf_query(streamfile, utf, row, column, &result);
+ if (!result.valid || !result.found || result.type != type)
+ return 0;
+
+ switch(result.type) {
+ case COLUMN_TYPE_SINT8: (*(int8_t*)value) = result.value.value_s8; break;
+ case COLUMN_TYPE_UINT8: (*(uint8_t*)value) = result.value.value_u8; break;
+ case COLUMN_TYPE_SINT16: (*(int16_t*)value) = result.value.value_s16; break;
+ case COLUMN_TYPE_UINT16: (*(uint16_t*)value) = result.value.value_u16; break;
+ case COLUMN_TYPE_SINT32: (*(int32_t*)value) = result.value.value_s32; break;
+ case COLUMN_TYPE_UINT32: (*(uint32_t*)value) = result.value.value_u32; break;
+ case COLUMN_TYPE_SINT64: (*(int64_t*)value) = result.value.value_s64; break;
+ //case COLUMN_TYPE_UINT64: (*(uint64_t*)value) = result.value.value_u64; break;
+ case COLUMN_TYPE_STRING: (*(const char**)value) = result.value.value_string; break;
+ default:
+ return 0;
+ }
+
+ return 1;
+}
+
+/*static*/ int utf_query_s8(STREAMFILE *streamfile, utf_context *utf, int row, const char* column, int8_t* value) {
+ return utf_query_value(streamfile, utf, row, column, (void*)value, COLUMN_TYPE_SINT8);
+}
+/*static*/ int utf_query_s16(STREAMFILE *streamfile, utf_context *utf, int row, const char* column, int16_t* value) {
+ return utf_query_value(streamfile, utf, row, column, (void*)value, COLUMN_TYPE_SINT16);
+}
+/*static*/ int utf_query_string(STREAMFILE *streamfile, utf_context *utf, int row, const char* column, const char* *value) {
+ return utf_query_value(streamfile, utf, row, column, (void*)value, COLUMN_TYPE_STRING);
+}
+
+/*static*/ int utf_query_data(STREAMFILE *streamfile, utf_context *utf, int row, const char* column, uint32_t *offset, uint32_t *size) {
+ utf_result result = {0};
+
+ utf_query(streamfile, utf, row, column, &result);
+ if (!result.valid || !result.found || result.type != COLUMN_TYPE_DATA)
+ return 0;
+
+ if (offset) *offset = utf->table_offset + utf->data_offset + result.value.value_data.offset;
+ if (size) *size = result.value.value_data.size;
+ return 1;
+}
+
+#endif /* _ACB_UTF_H_ */
diff --git a/src/meta/awb.c b/src/meta/awb.c
index a9e546a6..6f80d553 100644
--- a/src/meta/awb.c
+++ b/src/meta/awb.c
@@ -1,19 +1,27 @@
#include "meta.h"
#include "../coding/coding.h"
-typedef enum { ADX, HCA, AT9, VAG } awb_type;
+typedef enum { ADX, HCA, VAG, RIFF, CWAV, DSP } awb_type;
-/* CRI AFS2, container of streaming ADX or HCA, often (but not always) together with a .acb CUE */
+static void load_awb_name(STREAMFILE *streamFile, STREAMFILE *acbFile, VGMSTREAM *vgmstream, int waveid);
+
+/* AFS2/AWB (Atom Wave Bank) - CRI container of streaming audio, often together with a .acb cue sheet */
VGMSTREAM * init_vgmstream_awb(STREAMFILE *streamFile) {
+ return init_vgmstream_awb_memory(streamFile, NULL);
+}
+
+VGMSTREAM * init_vgmstream_awb_memory(STREAMFILE *streamFile, STREAMFILE *acbFile) {
VGMSTREAM *vgmstream = NULL;
STREAMFILE *temp_streamFile = NULL;
off_t offset, subfile_offset, subfile_next;
size_t subfile_size;
int total_subsongs, target_subsong = streamFile->stream_index;
//uint32_t flags;
+ uint8_t offset_size;
uint16_t alignment, subkey;
awb_type type;
char *extension = NULL;
+ int waveid;
/* checks
@@ -21,62 +29,94 @@ VGMSTREAM * init_vgmstream_awb(STREAMFILE *streamFile) {
* .afs2: sometimes [Okami HD (PS4)] */
if (!check_extensions(streamFile, "awb,afs2"))
goto fail;
- if (read_32bitBE(0x00,streamFile) != 0x41465332) /* "AFS2" */
+ if (read_u32be(0x00,streamFile) != 0x41465332) /* "AFS2" */
goto fail;
//flags = read_32bitLE(0x08,streamFile);
- total_subsongs = read_32bitLE(0x08,streamFile);
- alignment = (uint16_t)read_16bitLE(0x0c,streamFile);
- subkey = (uint16_t)read_16bitLE(0x0e,streamFile);
+ /* 0x04(1): version? 0x01=common, 0x02=2018+ (no apparent differences) */
+ offset_size = read_u8(0x05,streamFile);
+ /* 0x06(2): always 0x0002? */
+ total_subsongs = read_s32le(0x08,streamFile);
+ alignment = read_u16le(0x0c,streamFile);
+ subkey = read_u16le(0x0e,streamFile);
if (target_subsong == 0) target_subsong = 1;
if (target_subsong > total_subsongs || total_subsongs <= 0) goto fail;
offset = 0x10;
- /* id(?) table: skip */
- offset += total_subsongs * 0x02;
+ /* id(?) table: read target */
+ {
+ off_t waveid_offset = offset + (target_subsong-1) * 0x02;
- /* offset table: find target
- * offset are absolute but sometimes misaligned (specially first that just points to offset table end) */
+ waveid = read_u16le(waveid_offset,streamFile);
+
+ offset += total_subsongs * 0x02;
+ }
+
+ /* offset table: find target */
{
off_t file_size = get_streamfile_size(streamFile);
- offset += (target_subsong-1) * 0x04;
- /* last offset is always file end, so table entries = total_subsongs+1 */
- subfile_offset = read_32bitLE(offset+0x00,streamFile);
- subfile_next = read_32bitLE(offset+0x04,streamFile);
+ /* last sub-offset is always file end, so table entries = total_subsongs+1 */
+ offset += (target_subsong-1) * offset_size;
+ switch(offset_size) {
+ case 0x04: /* common */
+ subfile_offset = read_u32le(offset+0x00,streamFile);
+ subfile_next = read_u32le(offset+0x04,streamFile);
+ break;
+ case 0x02: /* mostly sfx in .acb */
+ subfile_offset = read_u16le(offset+0x00,streamFile);
+ subfile_next = read_u16le(offset+0x02,streamFile);
+ break;
+ default:
+ VGM_LOG("AWB: unknown offset size\n");
+ goto fail;
+ }
+
+ /* offset are absolute but sometimes misaligned (specially first that just points to offset table end) */
subfile_offset += (subfile_offset % alignment) ?
alignment - (subfile_offset % alignment) : 0;
subfile_next += (subfile_next % alignment) && subfile_next < file_size ?
alignment - (subfile_next % alignment) : 0;
subfile_size = subfile_next - subfile_offset;
-
- //todo: flags & 0x200 are uint16 offsets?
}
- //;VGM_LOG("TXTH: subfile offset=%lx + %x\n", subfile_offset, subfile_size);
+ ;VGM_LOG("AWB: subfile offset=%lx + %x\n", subfile_offset, subfile_size);
/* autodetect as there isn't anything, plus can mix types
* (waveid<>codec info is usually in the companion .acb) */
- if ((uint16_t)read_16bitBE(subfile_offset, streamFile) == 0x8000) { /* ADX id */
+ if (read_u16be(subfile_offset, streamFile) == 0x8000) { /* ADX id (type 0) */
type = ADX;
extension = "adx";
}
- else if (((uint32_t)read_32bitBE(subfile_offset,streamFile) & 0x7f7f7f7f) == 0x48434100) { /* "HCA\0" */
+ else if ((read_u32be(subfile_offset,streamFile) & 0x7f7f7f7f) == 0x48434100) { /* "HCA\0" (type 2=HCA, 6=HCA-MX) */
type = HCA;
extension = "hca";
}
- else if (read_32bitBE(subfile_offset,streamFile) == 0x52494646) { /* "RIFF" */
- type = AT9;
- extension = "at9";
- }
- else if (read_32bitBE(subfile_offset,streamFile) == 0x56414770) { /* "VAGp" */
+ else if (read_u32be(subfile_offset,streamFile) == 0x56414770) { /* "VAGp" (type 7=VAG, 10=HEVAG) */
type = VAG;
extension = "vag";
}
+ else if (read_u32be(subfile_offset,streamFile) == 0x52494646) { /* "RIFF" (type 8=ATRAC3, 11=ATRAC9) */
+ type = RIFF;
+ extension = "wav";
+ }
+ else if (read_u32be(subfile_offset,streamFile) == 0x43574156) { /* "CWAV" (type 9) */
+ type = CWAV;
+ extension = "bcwav";
+ }
+ else if (read_u32be(subfile_offset + 0x08,streamFile) >= 8000 &&
+ read_u32be(subfile_offset + 0x08,streamFile) <= 48000 &&
+ read_u16be(subfile_offset + 0x08,streamFile) == 0 &&
+ read_u32be(subfile_offset + 0x18,streamFile) == 2 &&
+ read_u32be(subfile_offset + 0x50,streamFile) == 0) { /* probably should call some check function (type 13) */
+ type = DSP;
+ extension = "dsp";
+ }
else {
+ VGM_LOG("AWB: unknown codec\n");
goto fail;
}
@@ -93,22 +133,31 @@ VGMSTREAM * init_vgmstream_awb(STREAMFILE *streamFile) {
vgmstream = init_vgmstream_adx(temp_streamFile);
if (!vgmstream) goto fail;
break;
- case AT9: /* Ukiyo no Roushi (Vita) */
+ case VAG: /* Ukiyo no Roushi (Vita) */
+ vgmstream = init_vgmstream_vag(temp_streamFile);
+ if (!vgmstream) goto fail;
+ break;
+ case RIFF: /* Ukiyo no Roushi (Vita) */
vgmstream = init_vgmstream_riff(temp_streamFile);
if (!vgmstream) goto fail;
break;
- case VAG: /* Ukiyo no Roushi (Vita) */
- vgmstream = init_vgmstream_vag(temp_streamFile);
+ case CWAV: /* Sonic: Lost World (3DS) */
+ vgmstream = init_vgmstream_rwsd(temp_streamFile);
+ if (!vgmstream) goto fail;
+ break;
+ case DSP: /* Sonic: Lost World (WiiU) */
+ vgmstream = init_vgmstream_ngc_dsp_std(temp_streamFile);
if (!vgmstream) goto fail;
break;
default:
goto fail;
}
- //todo: could try to get name in .acb for this waveid
-
vgmstream->num_streams = total_subsongs;
+ /* try to load cue names */
+ load_awb_name(streamFile, acbFile, vgmstream, waveid);
+
close_streamfile(temp_streamFile);
return vgmstream;
@@ -117,3 +166,55 @@ fail:
close_vgmstream(vgmstream);
return NULL;
}
+
+
+static void load_awb_name(STREAMFILE *streamFile, STREAMFILE *acbFile, VGMSTREAM* vgmstream, int waveid) {
+ int is_memory = (acbFile != NULL);
+
+ /* .acb is passed when loading memory .awb inside .acb */
+ if (!is_memory) {
+ /* load companion .acb using known pairs */ //todo improve, see xsb code
+ char filename[PATH_LIMIT];
+ int len_name, len_cmp;
+
+
+ /* try (name).awb + (name).awb */
+ acbFile = open_streamfile_by_ext(streamFile, "acb");
+
+ /* try (name)_streamfiles.awb + (name).acb */
+ if (!acbFile) {
+ char *cmp = "_streamfiles";
+ get_streamfile_basename(streamFile, filename, sizeof(filename));
+ len_name = strlen(filename);
+ len_cmp = strlen(cmp);
+
+ if (len_name > len_cmp && strcmp(filename + len_name - len_cmp, cmp) == 0) {
+ filename[len_name - len_cmp] = '\0';
+ strcat(filename, ".acb");
+ acbFile = open_streamfile_by_filename(streamFile, filename);
+ }
+ }
+
+ /* try (name)_STR.awb + (name).acb */
+ if (!acbFile) {
+ char *cmp = "_STR";
+ get_streamfile_basename(streamFile, filename, sizeof(filename));
+ len_name = strlen(filename);
+ len_cmp = strlen(cmp);
+
+ if (len_name > len_cmp && strcmp(filename + len_name - len_cmp, cmp) == 0) {
+ filename[len_name - len_cmp] = '\0';
+ strcat(filename, ".acb");
+ acbFile = open_streamfile_by_filename(streamFile, filename);
+ }
+ }
+
+ /* probably loaded */
+ load_acb_wave_name(acbFile, vgmstream, waveid, is_memory);
+
+ close_streamfile(acbFile);
+ }
+ else {
+ load_acb_wave_name(acbFile, vgmstream, waveid, is_memory);
+ }
+}
diff --git a/src/meta/hca.c b/src/meta/hca.c
index fa87c62e..3afea632 100644
--- a/src/meta/hca.c
+++ b/src/meta/hca.c
@@ -65,6 +65,13 @@ VGMSTREAM * init_vgmstream_hca_subkey(STREAMFILE *streamFile, uint16_t subkey) {
//if (vgmstream->loop_end_sample && vgmstream->num_samples > vgmstream->loop_end_sample)
// vgmstream->num_samples = vgmstream->loop_end_sample;
+ /* this can happen in preloading HCA from memory AWB */
+ if (hca_data->info.blockCount * hca_data->info.blockSize > get_streamfile_size(streamFile)) {
+ unsigned int max_block = get_streamfile_size(streamFile) / hca_data->info.blockSize;
+ vgmstream->num_samples = max_block * hca_data->info.samplesPerBlock -
+ hca_data->info.encoderDelay - hca_data->info.encoderPadding;
+ }
+
vgmstream->coding_type = coding_CRI_HCA;
vgmstream->layout_type = layout_none;
vgmstream->codec_data = hca_data;
diff --git a/src/meta/meta.h b/src/meta/meta.h
index 7789dd09..2e690216 100644
--- a/src/meta/meta.h
+++ b/src/meta/meta.h
@@ -860,6 +860,10 @@ VGMSTREAM * init_vgmstream_bwav(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_opus_prototype(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_awb(STREAMFILE * streamFile);
+VGMSTREAM * init_vgmstream_awb_memory(STREAMFILE * streamFile, STREAMFILE *acbFile);
+
+VGMSTREAM * init_vgmstream_acb(STREAMFILE * streamFile);
+void load_acb_wave_name(STREAMFILE *acbFile, VGMSTREAM* vgmstream, int waveid, int is_memory);
VGMSTREAM * init_vgmstream_rad(STREAMFILE * streamFile);
diff --git a/src/vgmstream.c b/src/vgmstream.c
index 5fbfb041..2e31a760 100644
--- a/src/vgmstream.c
+++ b/src/vgmstream.c
@@ -483,6 +483,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_bwav,
init_vgmstream_opus_prototype,
init_vgmstream_awb,
+ init_vgmstream_acb,
init_vgmstream_rad,
/* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */