2019-07-14 21:24:28 +02:00
|
|
|
#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;
|
|
|
|
|
2019-07-21 13:11:25 +02:00
|
|
|
//;VGM_LOG("ACB: loaded table %s\n", TableName);
|
2019-07-14 21:24:28 +02:00
|
|
|
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;
|
2019-07-21 13:11:25 +02:00
|
|
|
//;VGM_LOG("ACB: CueName[%i]: CueIndex=%i, CueName=%s\n", acb->CueNameIndex, acb->CueIndex, acb->CueName);
|
2019-07-14 21:24:28 +02:00
|
|
|
|
|
|
|
/* 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;
|
2019-07-21 13:11:25 +02:00
|
|
|
//;VGM_LOG("ACB: Cue[%i]: ReferenceType=%i, ReferenceIndex=%i\n", acb->CueIndex, acb->ReferenceType, acb->ReferenceIndex);
|
2019-07-14 21:24:28 +02:00
|
|
|
|
|
|
|
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;
|
2019-07-21 13:11:25 +02:00
|
|
|
//;VGM_LOG("ACB: Sequence[%i]: NumTracks=%i, TrackIndex={%x, %x}\n", acb->ReferenceIndex, acb->NumTracks, acb->TrackIndex_offset,acb->TrackIndex_size);
|
2019-07-14 21:24:28 +02:00
|
|
|
|
|
|
|
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;
|
2019-07-21 13:11:25 +02:00
|
|
|
//;VGM_LOG("ACB: Track[%i]: EventIndex=%i\n", acb->TrackIndex, acb->EventIndex);
|
2019-07-14 21:24:28 +02:00
|
|
|
|
|
|
|
/* 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;
|
2019-07-21 13:11:25 +02:00
|
|
|
//;VGM_LOG("ACB: TrackEvent[%i]: Command={%x,%x}\n", acb->EventIndex, acb->Command_offset,acb->Command_size);
|
2019-07-14 21:24:28 +02:00
|
|
|
}
|
|
|
|
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;
|
2019-07-21 13:11:25 +02:00
|
|
|
//;VGM_LOG("ACB: Command[%i]: Command={%x,%x}\n", acb->EventIndex, acb->Command_offset,acb->Command_size);
|
2019-07-14 21:24:28 +02:00
|
|
|
}
|
|
|
|
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? */
|
2019-07-21 13:11:25 +02:00
|
|
|
if (subcode != 0x02) {
|
|
|
|
//todo some like Yakuza Kiwami 2 usen.acb/Yakuza 6 haichi_amb_siren.acb use 0x03
|
|
|
|
// ('random' type pointing to Sequence, see Synth)
|
|
|
|
VGM_LOG("ACB: subcommand with unknown subcode at %x\n", offset);
|
2019-07-14 21:24:28 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
acb->SynthIndex_list[acb->SynthIndex_count] = subindex;
|
|
|
|
acb->SynthIndex_count++;
|
|
|
|
if (acb->SynthIndex_count >= 254)
|
|
|
|
acb->ReferenceItems_count = 254; /* ??? */
|
|
|
|
|
2019-07-21 13:11:25 +02:00
|
|
|
//;VGM_LOG("ACB: subcommand index %i found\n", subindex);
|
2019-07-14 21:24:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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;
|
2019-07-21 13:11:25 +02:00
|
|
|
//;VGM_LOG("ACB: Synth[%i]: ReferenceItems={%x,%x}\n", acb->SynthIndex, acb->ReferenceItems_offset, acb->ReferenceItems_size);
|
2019-07-14 21:24:28 +02:00
|
|
|
|
|
|
|
|
|
|
|
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);
|
2019-07-21 13:11:25 +02:00
|
|
|
//;VGM_LOG("ACB: Synth reference type=%x, index=%x\n", type, index);
|
2019-07-14 21:24:28 +02:00
|
|
|
|
|
|
|
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;
|
2019-07-21 13:11:25 +02:00
|
|
|
//;VGM_LOG("ACB: Synth subreference type=%x, index=%x\n", subtype, subindex);
|
2019-07-14 21:24:28 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x03: /* random Synths with % in TrackValues (rare, found in Sonic Lost World with ReferenceType 2) */
|
2019-07-21 13:11:25 +02:00
|
|
|
//todo fix: points to N Sequences (in turn pointing to Tracks > Synths) ex. se_phantom_asteroid.acb
|
2019-07-14 21:24:28 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2019-07-21 13:11:25 +02:00
|
|
|
//;VGM_LOG("ACB: Waveform[%i]: AwbId=%i, AwbStreaming=%i\n", acb->ReferenceItem, acb->AwbId, acb->AwbStreaming);
|
2019-07-14 21:24:28 +02:00
|
|
|
|
|
|
|
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; /* ??? */
|
|
|
|
|
2019-07-21 13:11:25 +02:00
|
|
|
//;VGM_LOG("ACB: found cue for waveid=%i: %s\n", acb->AwbId, acb->CueName);
|
2019-07-14 21:24:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
|
2019-07-21 13:11:25 +02:00
|
|
|
//;VGM_LOG("ACB: find waveid=%i\n", waveid);
|
2019-07-14 21:24:28 +02:00
|
|
|
|
|
|
|
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;
|
2019-07-21 13:11:25 +02:00
|
|
|
//;VGM_LOG("ACB: CueNames=%i\n", CueName_rows);
|
2019-07-14 21:24:28 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|