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
|
|
|
|
|
2019-12-15 19:37:52 +01:00
|
|
|
if (!utf_query_data(utf, 0, "AwbFile", &offset, &size))
|
2019-07-14 21:24:28 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ************************************** */
|
|
|
|
|
2019-10-19 11:11:09 +02:00
|
|
|
//todo maybe use reopen sf? since internal buffer is going to be read
|
2019-10-06 17:54:54 +02:00
|
|
|
#define ACB_TABLE_BUFFER_SIZE 0x4000
|
|
|
|
|
2019-10-19 11:11:09 +02:00
|
|
|
STREAMFILE* setup_acb_streamfile(STREAMFILE *sf, size_t buffer_size) {
|
|
|
|
STREAMFILE *new_sf = NULL;
|
2019-10-06 17:54:54 +02:00
|
|
|
|
2019-10-19 11:11:09 +02:00
|
|
|
new_sf = open_wrap_streamfile(sf);
|
|
|
|
new_sf = open_buffer_streamfile_f(new_sf, buffer_size);
|
|
|
|
return new_sf;
|
2019-10-06 17:54:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-07-14 21:24:28 +02:00
|
|
|
typedef struct {
|
2019-10-06 17:54:54 +02:00
|
|
|
STREAMFILE *acbFile; /* original reference, don't close */
|
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
/* keep track of these tables so they can be closed when done */
|
2019-07-14 21:24:28 +02:00
|
|
|
utf_context *Header;
|
2019-10-06 17:54:54 +02:00
|
|
|
|
2019-07-14 21:24:28 +02:00
|
|
|
utf_context *CueNameTable;
|
|
|
|
utf_context *CueTable;
|
2019-09-02 20:41:04 +02:00
|
|
|
utf_context *BlockTable;
|
2019-07-14 21:24:28 +02:00
|
|
|
utf_context *SequenceTable;
|
|
|
|
utf_context *TrackTable;
|
2019-10-06 17:54:54 +02:00
|
|
|
utf_context *TrackCommandTable;
|
2019-07-14 21:24:28 +02:00
|
|
|
utf_context *SynthTable;
|
|
|
|
utf_context *WaveformTable;
|
|
|
|
|
2019-10-06 17:54:54 +02:00
|
|
|
STREAMFILE *CueNameSf;
|
|
|
|
STREAMFILE *CueSf;
|
|
|
|
STREAMFILE *BlockSf;
|
|
|
|
STREAMFILE *SequenceSf;
|
|
|
|
STREAMFILE *TrackSf;
|
|
|
|
STREAMFILE *TrackCommandSf;
|
|
|
|
STREAMFILE *SynthSf;
|
|
|
|
STREAMFILE *WaveformSf;
|
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
/* config */
|
2019-07-14 21:24:28 +02:00
|
|
|
int is_memory;
|
2019-09-02 20:41:04 +02:00
|
|
|
int target_waveid;
|
2019-07-14 21:24:28 +02:00
|
|
|
int has_TrackEventTable;
|
|
|
|
int has_CommandTable;
|
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
/* to avoid infinite/circular references (AtomViewer crashes otherwise) */
|
|
|
|
int synth_depth;
|
|
|
|
int sequence_depth;
|
|
|
|
|
|
|
|
/* name stuff */
|
|
|
|
int16_t cuename_index;
|
|
|
|
const char * cuename_name;
|
|
|
|
int awbname_count;
|
|
|
|
int16_t awbname_list[255];
|
|
|
|
char name[1024];
|
2019-07-14 21:24:28 +02:00
|
|
|
|
|
|
|
} acb_header;
|
|
|
|
|
2019-10-06 17:54:54 +02:00
|
|
|
static int open_utf_subtable(acb_header* acb, STREAMFILE* *TableSf, utf_context* *Table, const char* TableName, int* rows) {
|
2019-07-14 21:24:28 +02:00
|
|
|
uint32_t offset = 0;
|
|
|
|
|
|
|
|
/* already loaded */
|
|
|
|
if (*Table != NULL)
|
|
|
|
return 1;
|
|
|
|
|
2019-12-15 19:37:52 +01:00
|
|
|
if (!utf_query_data(acb->Header, 0, TableName, &offset, NULL))
|
2019-07-14 21:24:28 +02:00
|
|
|
goto fail;
|
2019-10-06 17:54:54 +02:00
|
|
|
|
|
|
|
/* open a buffered streamfile to avoid so much IO back and forth between all the tables */
|
|
|
|
*TableSf = setup_acb_streamfile(acb->acbFile, ACB_TABLE_BUFFER_SIZE);
|
|
|
|
if (!*TableSf) goto fail;
|
|
|
|
|
|
|
|
*Table = utf_open(*TableSf, offset, rows, NULL);
|
2019-07-14 21:24:28 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-10-06 17:54:54 +02:00
|
|
|
static void add_acb_name(acb_header* acb, int8_t Waveform_Streaming) {
|
2019-09-02 20:41:04 +02:00
|
|
|
//todo safe string ops
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
/* ignore name repeats */
|
|
|
|
if (acb->awbname_count) {
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < acb->awbname_count; i++) {
|
|
|
|
if (acb->awbname_list[i] == acb->cuename_index)
|
|
|
|
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_name);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
strcpy(acb->name, acb->cuename_name);
|
|
|
|
}
|
|
|
|
if (Waveform_Streaming == 2 && acb->is_memory) {
|
|
|
|
strcat(acb->name, " [pre]");
|
|
|
|
}
|
|
|
|
|
|
|
|
acb->awbname_list[acb->awbname_count] = acb->cuename_index;
|
|
|
|
acb->awbname_count++;
|
|
|
|
if (acb->awbname_count >= 254)
|
|
|
|
acb->awbname_count = 254; /* ??? */
|
|
|
|
|
|
|
|
//;VGM_LOG("ACB: found cue for waveid=%i: %s\n", acb->target_waveid, acb->cuename_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-10-06 17:54:54 +02:00
|
|
|
static int load_acb_waveform(acb_header* acb, int16_t Index) {
|
2019-09-02 20:41:04 +02:00
|
|
|
int16_t Waveform_Id;
|
|
|
|
int8_t Waveform_Streaming;
|
|
|
|
|
|
|
|
/* read Waveform[Index] */
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!open_utf_subtable(acb, &acb->WaveformSf, &acb->WaveformTable, "WaveformTable", NULL))
|
2019-07-14 21:24:28 +02:00
|
|
|
goto fail;
|
2019-12-15 19:37:52 +01:00
|
|
|
if (!utf_query_s16(acb->WaveformTable, Index, "Id", &Waveform_Id)) { /* older versions use Id */
|
2019-09-02 20:41:04 +02:00
|
|
|
if (acb->is_memory) {
|
2019-12-15 19:37:52 +01:00
|
|
|
if (!utf_query_s16(acb->WaveformTable, Index, "MemoryAwbId", &Waveform_Id))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
|
|
|
} else {
|
2019-12-15 19:37:52 +01:00
|
|
|
if (!utf_query_s16(acb->WaveformTable, Index, "StreamAwbId", &Waveform_Id))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
}
|
2019-12-15 19:37:52 +01:00
|
|
|
if (!utf_query_s8(acb->WaveformTable, Index, "Streaming", &Waveform_Streaming))
|
2019-07-14 21:24:28 +02:00
|
|
|
goto fail;
|
2019-09-02 20:41:04 +02:00
|
|
|
//;VGM_LOG("ACB: Waveform[%i]: Id=%i, Streaming=%i\n", Index, Waveform_Id, Waveform_Streaming);
|
|
|
|
|
|
|
|
/* not found but valid */
|
|
|
|
if (Waveform_Id != acb->target_waveid)
|
|
|
|
return 1;
|
|
|
|
/* must match our target's (0=memory, 1=streaming, 2=memory (prefetch)+stream) */
|
|
|
|
if ((acb->is_memory && Waveform_Streaming == 1) || (!acb->is_memory && Waveform_Streaming == 0))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
/* aaand finally get name (phew) */
|
2019-10-06 17:54:54 +02:00
|
|
|
add_acb_name(acb, Waveform_Streaming);
|
2019-07-14 21:24:28 +02:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
fail:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
/* define here for Synths pointing to Sequences */
|
2019-10-06 17:54:54 +02:00
|
|
|
static int load_acb_sequence(acb_header* acb, int16_t Index);
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-10-06 17:54:54 +02:00
|
|
|
static int load_acb_synth(acb_header* acb, int16_t Index) {
|
2019-09-02 20:41:04 +02:00
|
|
|
int i, count;
|
|
|
|
int8_t Synth_Type;
|
|
|
|
uint32_t Synth_ReferenceItems_offset;
|
|
|
|
uint32_t Synth_ReferenceItems_size;
|
|
|
|
|
|
|
|
|
|
|
|
/* read Synth[Index] */
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!open_utf_subtable(acb, &acb->SynthSf, &acb->SynthTable, "SynthTable", NULL))
|
2019-07-14 21:24:28 +02:00
|
|
|
goto fail;
|
2019-12-15 19:37:52 +01:00
|
|
|
if (!utf_query_s8(acb->SynthTable, Index, "Type", &Synth_Type))
|
2019-07-14 21:24:28 +02:00
|
|
|
goto fail;
|
2019-12-15 19:37:52 +01:00
|
|
|
if (!utf_query_data(acb->SynthTable, Index, "ReferenceItems", &Synth_ReferenceItems_offset, &Synth_ReferenceItems_size))
|
2019-07-14 21:24:28 +02:00
|
|
|
goto fail;
|
2019-09-02 20:41:04 +02:00
|
|
|
//;VGM_LOG("ACB: Synth[%i]: Type=%x, ReferenceItems={%x,%x}\n", Index, Synth_Type, Synth_ReferenceItems_offset, Synth_ReferenceItems_size);
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
acb->synth_depth++;
|
|
|
|
|
|
|
|
if (acb->synth_depth > 2) {
|
|
|
|
VGM_LOG("ACB: Synth depth too high\n");
|
|
|
|
goto fail; /* max Synth > Synth > Waveform (ex. Yakuza 6) */
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Cue.ReferenceType 2 uses Synth.Type, while 3 always sets it to 0 and uses Sequence.Type instead
|
|
|
|
* Both look the same and probably affect which item in the ReferenceItems list is picked:
|
|
|
|
* - 0: polyphonic (1 item)
|
|
|
|
* - 1: sequential (1 to N?)
|
|
|
|
* - 2: shuffle (1 from N?)
|
|
|
|
* - 3: random (1 from N?)
|
|
|
|
* - 4: no repeat
|
|
|
|
* - 5: switch game variable
|
|
|
|
* - 6: combo sequential
|
|
|
|
* - 7: switch selector
|
|
|
|
* - 8: track transition by selector
|
|
|
|
* - other: undefined?
|
|
|
|
* Since we want to find all possible Waveforms that could match our id, we ignore Type and just parse all ReferenceItems.
|
|
|
|
*/
|
|
|
|
|
|
|
|
count = Synth_ReferenceItems_size / 0x04;
|
|
|
|
for (i = 0; i < count; i++) {
|
2019-10-06 17:54:54 +02:00
|
|
|
uint16_t Synth_ReferenceItem_type = read_u16be(Synth_ReferenceItems_offset + i*0x04 + 0x00, acb->SynthSf);
|
|
|
|
uint16_t Synth_ReferenceItem_index = read_u16be(Synth_ReferenceItems_offset + i*0x04 + 0x02, acb->SynthSf);
|
2019-09-02 20:41:04 +02:00
|
|
|
//;VGM_LOG("ACB: Synth.ReferenceItem: type=%x, index=%x\n", Synth_ReferenceItem_type, Synth_ReferenceItem_index);
|
|
|
|
|
|
|
|
switch(Synth_ReferenceItem_type) {
|
|
|
|
case 0x00: /* no reference */
|
|
|
|
count = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x01: /* Waveform (most common) */
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!load_acb_waveform(acb, Synth_ReferenceItem_index))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x02: /* Synth, possibly random (rare, found in Sonic Lost World with ReferenceType 2) */
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!load_acb_synth(acb, Synth_ReferenceItem_index))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x03: /* Sequence of Synths w/ % in Synth.TrackValues (rare, found in Sonic Lost World with ReferenceType 2) */
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!load_acb_sequence(acb, Synth_ReferenceItem_index))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x06: /* this seems to point to Synth but results don't make sense (rare, from Sonic Lost World) */
|
|
|
|
default: /* undefined/crashes AtomViewer */
|
|
|
|
VGM_LOG("ACB: unknown Synth.ReferenceItem type %x at %x + %x\n", Synth_ReferenceItem_type, Synth_ReferenceItems_offset, Synth_ReferenceItems_size);
|
|
|
|
count = 0; /* force end without failing */
|
|
|
|
break;
|
|
|
|
}
|
2019-07-14 21:24:28 +02:00
|
|
|
}
|
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
acb->synth_depth--;
|
|
|
|
|
2019-07-14 21:24:28 +02:00
|
|
|
return 1;
|
|
|
|
fail:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-10-06 17:54:54 +02:00
|
|
|
static int load_acb_track_event_command(acb_header* acb, int16_t Index) {
|
2019-09-02 20:41:04 +02:00
|
|
|
int16_t Track_EventIndex;
|
|
|
|
uint32_t Track_Command_offset;
|
|
|
|
uint32_t Track_Command_size;
|
|
|
|
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
/* read Track[Index] */
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!open_utf_subtable(acb, &acb->TrackSf, &acb->TrackTable, "TrackTable", NULL))
|
2019-07-14 21:24:28 +02:00
|
|
|
goto fail;
|
2019-12-15 19:37:52 +01:00
|
|
|
if (!utf_query_s16(acb->TrackTable, Index, "EventIndex", &Track_EventIndex))
|
2019-07-14 21:24:28 +02:00
|
|
|
goto fail;
|
2019-09-02 20:41:04 +02:00
|
|
|
//;VGM_LOG("ACB: Track[%i]: EventIndex=%i\n", Index, Track_EventIndex);
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
/* next link varies with version, check by table existence */
|
|
|
|
if (acb->has_CommandTable) { /* <=v1.27 */
|
|
|
|
/* read Command[EventIndex] */
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!open_utf_subtable(acb, &acb->TrackCommandSf, &acb->TrackCommandTable, "CommandTable", NULL))
|
2019-07-14 21:24:28 +02:00
|
|
|
goto fail;
|
2019-12-15 19:37:52 +01:00
|
|
|
if (!utf_query_data(acb->TrackCommandTable, Track_EventIndex, "Command", &Track_Command_offset, &Track_Command_size))
|
2019-07-14 21:24:28 +02:00
|
|
|
goto fail;
|
2019-09-02 20:41:04 +02:00
|
|
|
//;VGM_LOG("ACB: Command[%i]: Command={%x,%x}\n", Track_EventIndex, Track_Command_offset,Track_Command_size);
|
2019-07-14 21:24:28 +02:00
|
|
|
}
|
2019-09-02 20:41:04 +02:00
|
|
|
else if (acb->has_TrackEventTable) { /* >=v1.28 */
|
|
|
|
/* read TrackEvent[EventIndex] */
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!open_utf_subtable(acb, &acb->TrackCommandSf, &acb->TrackCommandTable, "TrackEventTable", NULL))
|
2019-07-14 21:24:28 +02:00
|
|
|
goto fail;
|
2019-12-15 19:37:52 +01:00
|
|
|
if (!utf_query_data(acb->TrackCommandTable, Track_EventIndex, "Command", &Track_Command_offset, &Track_Command_size))
|
2019-07-14 21:24:28 +02:00
|
|
|
goto fail;
|
2019-09-02 20:41:04 +02:00
|
|
|
//;VGM_LOG("ACB: TrackEvent[%i]: Command={%x,%x}\n", Track_EventIndex, Track_Command_offset,Track_Command_size);
|
2019-07-14 21:24:28 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
VGM_LOG("ACB: unknown command table\n");
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
2019-07-14 21:24:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* read Command (some kind of multiple TLVs, this seems ok) */
|
|
|
|
{
|
2019-09-02 20:41:04 +02:00
|
|
|
uint32_t offset = Track_Command_offset;
|
|
|
|
uint32_t max_offset = Track_Command_offset + Track_Command_size;
|
|
|
|
uint16_t tlv_code, tlv_type, tlv_index;
|
|
|
|
uint8_t tlv_size;
|
2019-07-14 21:24:28 +02:00
|
|
|
|
|
|
|
|
|
|
|
while (offset < max_offset) {
|
2019-10-06 17:54:54 +02:00
|
|
|
tlv_code = read_u16be(offset + 0x00, acb->TrackCommandSf);
|
|
|
|
tlv_size = read_u8 (offset + 0x02, acb->TrackCommandSf);
|
2019-07-14 21:24:28 +02:00
|
|
|
offset += 0x03;
|
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
if (tlv_code == 0x07D0) {
|
|
|
|
if (tlv_size < 0x04) {
|
|
|
|
VGM_LOG("ACB: TLV with unknown size\n");
|
2019-07-14 21:24:28 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-10-06 17:54:54 +02:00
|
|
|
tlv_type = read_u16be(offset + 0x00, acb->TrackCommandSf);
|
|
|
|
tlv_index = read_u16be(offset + 0x02, acb->TrackCommandSf);
|
2019-09-02 20:41:04 +02:00
|
|
|
//;VGM_LOG("ACB: TLV at %x: type %x, index=%x\n", offset, tlv_type, tlv_index);
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
/* probably same as Synth_ReferenceItem_type */
|
|
|
|
switch(tlv_type) {
|
|
|
|
|
|
|
|
case 0x02: /* Synth (common) */
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!load_acb_synth(acb, tlv_index))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
|
|
|
break;
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
case 0x03: /* Sequence of Synths (common, ex. Yakuza 6, Yakuza Kiwami 2) */
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!load_acb_sequence(acb, tlv_index))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
|
|
|
break;
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
default:
|
|
|
|
VGM_LOG("ACB: unknown TLV type %x at %x + %x\n", tlv_type, offset, tlv_size);
|
|
|
|
max_offset = 0; /* force end without failing */
|
|
|
|
break;
|
|
|
|
}
|
2019-07-14 21:24:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 0x07D1 comes suspiciously often paired with 0x07D0 too */
|
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
offset += tlv_size;
|
2019-07-14 21:24:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
fail:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-10-06 17:54:54 +02:00
|
|
|
static int load_acb_sequence(acb_header* acb, int16_t Index) {
|
2019-07-14 21:24:28 +02:00
|
|
|
int i;
|
2019-09-02 20:41:04 +02:00
|
|
|
int16_t Sequence_NumTracks;
|
|
|
|
uint32_t Sequence_TrackIndex_offset;
|
|
|
|
uint32_t Sequence_TrackIndex_size;
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
|
|
|
|
/* read Sequence[Index] */
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!open_utf_subtable(acb, &acb->SequenceSf, &acb->SequenceTable, "SequenceTable", NULL))
|
2019-07-14 21:24:28 +02:00
|
|
|
goto fail;
|
2019-12-15 19:37:52 +01:00
|
|
|
if (!utf_query_s16(acb->SequenceTable, Index, "NumTracks", &Sequence_NumTracks))
|
2019-07-14 21:24:28 +02:00
|
|
|
goto fail;
|
2019-12-15 19:37:52 +01:00
|
|
|
if (!utf_query_data(acb->SequenceTable, Index, "TrackIndex", &Sequence_TrackIndex_offset, &Sequence_TrackIndex_size))
|
2019-07-14 21:24:28 +02:00
|
|
|
goto fail;
|
2019-09-02 20:41:04 +02:00
|
|
|
//;VGM_LOG("ACB: Sequence[%i]: NumTracks=%i, TrackIndex={%x, %x}\n", Index, Sequence_NumTracks, Sequence_TrackIndex_offset,Sequence_TrackIndex_size);
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
acb->sequence_depth++;
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
if (acb->sequence_depth > 3) {
|
|
|
|
VGM_LOG("ACB: Sequence depth too high\n");
|
|
|
|
goto fail; /* max Sequence > Sequence > Sequence > Synth > Waveform (ex. Yakuza 6) */
|
|
|
|
}
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
if (Sequence_NumTracks * 0x02 > Sequence_TrackIndex_size) { /* padding may exist */
|
|
|
|
VGM_LOG("ACB: wrong Sequence.TrackIndex size\n");
|
|
|
|
goto fail;
|
|
|
|
}
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
/* read Tracks inside Sequence */
|
|
|
|
for (i = 0; i < Sequence_NumTracks; i++) {
|
2019-10-06 17:54:54 +02:00
|
|
|
int16_t Sequence_TrackIndex_index = read_s16be(Sequence_TrackIndex_offset + i*0x02, acb->SequenceSf);
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!load_acb_track_event_command(acb, Sequence_TrackIndex_index))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
|
|
|
}
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
acb->sequence_depth--;
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
return 1;
|
|
|
|
fail:
|
|
|
|
return 0;
|
|
|
|
}
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-10-06 17:54:54 +02:00
|
|
|
static int load_acb_block(acb_header* acb, int16_t Index) {
|
2019-09-02 20:41:04 +02:00
|
|
|
int i;
|
|
|
|
int16_t Block_NumTracks;
|
|
|
|
uint32_t Block_TrackIndex_offset;
|
|
|
|
uint32_t Block_TrackIndex_size;
|
2019-07-14 21:24:28 +02:00
|
|
|
|
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
/* read Block[Index] */
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!open_utf_subtable(acb, &acb->BlockSf, &acb->BlockTable, "BlockTable", NULL))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
2019-12-15 19:37:52 +01:00
|
|
|
if (!utf_query_s16(acb->BlockTable, Index, "NumTracks", &Block_NumTracks))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
2019-12-15 19:37:52 +01:00
|
|
|
if (!utf_query_data(acb->BlockTable, Index, "TrackIndex", &Block_TrackIndex_offset, &Block_TrackIndex_size))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
|
|
|
//;VGM_LOG("ACB: Block[%i]: NumTracks=%i, TrackIndex={%x, %x}\n", Index, Block_NumTracks, Block_TrackIndex_offset,Block_TrackIndex_size);
|
|
|
|
|
|
|
|
if (Block_NumTracks * 0x02 > Block_TrackIndex_size) { /* padding may exist */
|
|
|
|
VGM_LOG("ACB: wrong Block.TrackIndex size\n");
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* read Tracks inside Block */
|
|
|
|
for (i = 0; i < Block_NumTracks; i++) {
|
2019-10-06 17:54:54 +02:00
|
|
|
int16_t Block_TrackIndex_index = read_s16be(Block_TrackIndex_offset + i*0x02, acb->BlockSf);
|
2019-09-02 20:41:04 +02:00
|
|
|
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!load_acb_track_event_command(acb, Block_TrackIndex_index))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
2019-07-14 21:24:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
fail:
|
|
|
|
return 0;
|
2019-09-02 20:41:04 +02:00
|
|
|
|
2019-07-14 21:24:28 +02:00
|
|
|
}
|
|
|
|
|
2019-10-06 17:54:54 +02:00
|
|
|
static int load_acb_cue(acb_header* acb, int16_t Index) {
|
2019-09-02 20:41:04 +02:00
|
|
|
int8_t Cue_ReferenceType;
|
|
|
|
int16_t Cue_ReferenceIndex;
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
|
|
|
|
/* read Cue[Index] */
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!open_utf_subtable(acb, &acb->CueSf, &acb->CueTable, "CueTable", NULL))
|
2019-07-14 21:24:28 +02:00
|
|
|
goto fail;
|
2019-12-15 19:37:52 +01:00
|
|
|
if (!utf_query_s8 (acb->CueTable, Index, "ReferenceType", &Cue_ReferenceType))
|
2019-07-14 21:24:28 +02:00
|
|
|
goto fail;
|
2019-12-15 19:37:52 +01:00
|
|
|
if (!utf_query_s16(acb->CueTable, Index, "ReferenceIndex", &Cue_ReferenceIndex))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
|
|
|
//;VGM_LOG("ACB: Cue[%i]: ReferenceType=%i, ReferenceIndex=%i\n", Index, Cue_ReferenceType, Cue_ReferenceIndex);
|
|
|
|
|
|
|
|
|
|
|
|
/* usually older games use older references but not necessarily */
|
|
|
|
switch(Cue_ReferenceType) {
|
|
|
|
|
|
|
|
case 1: /* Cue > Waveform (ex. PES 2015) */
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!load_acb_waveform(acb, Cue_ReferenceIndex))
|
2019-07-14 21:24:28 +02:00
|
|
|
goto fail;
|
2019-09-02 20:41:04 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 2: /* Cue > Synth > Waveform (ex. Ukiyo no Roushi) */
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!load_acb_synth(acb, Cue_ReferenceIndex))
|
2019-07-14 21:24:28 +02:00
|
|
|
goto fail;
|
2019-09-02 20:41:04 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 3: /* Cue > Sequence > Track > Command > Synth > Waveform (ex. Valkyrie Profile anatomia, Yakuza Kiwami 2) */
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!load_acb_sequence(acb, Cue_ReferenceIndex))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
|
|
|
break;
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
case 8: /* Cue > Block > Track > Command > Synth > Waveform (ex. Sonic Lost World, rare) */
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!load_acb_block(acb, Cue_ReferenceIndex))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
|
|
|
break;
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-12-15 19:37:52 +01:00
|
|
|
case 6: /* Cue > (Waveform/Synth/Command)? (ex. PES 2014) */
|
|
|
|
case 7: /* Cue > (Waveform/Synth/Command)? (ex. PES 2014) */
|
2019-09-02 20:41:04 +02:00
|
|
|
default:
|
|
|
|
VGM_LOG("ACB: unknown Cue.ReferenceType=%x, Cue.ReferenceIndex=%x\n", Cue_ReferenceType, Cue_ReferenceIndex);
|
|
|
|
break; /* ignore and continue */
|
|
|
|
}
|
2019-07-14 21:24:28 +02:00
|
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
fail:
|
|
|
|
return 0;
|
2019-09-02 20:41:04 +02:00
|
|
|
|
2019-07-14 21:24:28 +02:00
|
|
|
}
|
|
|
|
|
2019-10-06 17:54:54 +02:00
|
|
|
static int load_acb_cuename(acb_header* acb, int16_t Index) {
|
2019-09-02 20:41:04 +02:00
|
|
|
int16_t CueName_CueIndex;
|
|
|
|
const char* CueName_CueName;
|
2019-07-14 21:24:28 +02:00
|
|
|
|
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
/* read CueName[Index] */
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!open_utf_subtable(acb, &acb->CueNameSf, &acb->CueNameTable, "CueNameTable", NULL))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
2019-12-15 19:37:52 +01:00
|
|
|
if (!utf_query_s16(acb->CueNameTable, Index, "CueIndex", &CueName_CueIndex))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
2019-12-15 19:37:52 +01:00
|
|
|
if (!utf_query_string(acb->CueNameTable, Index, "CueName", &CueName_CueName))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
|
|
|
//;VGM_LOG("ACB: CueName[%i]: CueIndex=%i, CueName=%s\n", Index, CueName_CueIndex, CueName_CueName);
|
2019-07-14 21:24:28 +02:00
|
|
|
|
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
/* save as will be needed if references waveform */
|
|
|
|
acb->cuename_index = Index;
|
|
|
|
acb->cuename_name = CueName_CueName;
|
|
|
|
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!load_acb_cue(acb, CueName_CueIndex))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
return 1;
|
|
|
|
fail:
|
|
|
|
return 0;
|
2019-07-14 21:24:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-10-06 17:54:54 +02:00
|
|
|
void load_acb_wave_name(STREAMFILE *streamFile, VGMSTREAM* vgmstream, int waveid, int is_memory) {
|
2019-07-14 21:24:28 +02:00
|
|
|
acb_header acb = {0};
|
2019-09-02 20:41:04 +02:00
|
|
|
int i, CueName_rows;
|
|
|
|
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!streamFile || !vgmstream || waveid < 0)
|
2019-07-14 21:24:28 +02:00
|
|
|
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.
|
|
|
|
*
|
2019-09-02 20:41:04 +02:00
|
|
|
* .acb are created in CRI Atom Craft, where user defines N Cues with CueName each, then link somehow
|
|
|
|
* to a Waveform (.awb=streamed or memory .acb=internal, data 'material' encoded in some format),
|
|
|
|
* depending on reference types. Typical links are:
|
|
|
|
* - CueName > Cue > Waveform (type 1)
|
|
|
|
* - CueName > Cue > Synth > Waveform (type 2)
|
|
|
|
* - CueName > Cue > Sequence > Track > Command > Synth > Waveform (type 3, <=v1.27)
|
|
|
|
* - CueName > Cue > Sequence > Track > Command > Synth > Synth > Waveform (type 3, <=v1.27)
|
|
|
|
* - CueName > Cue > Sequence > Track > TrackEvent > Command > Synth > Waveform (type 3, >=v1.28)
|
|
|
|
* - CueName > Cue > Sequence > Track > TrackEvent > Command > Synth > Synth > Waveform (type 3, >=v1.28)
|
|
|
|
* - CueName > Cue > Sequence > Track > TrackEvent > Command > Sequence > (...) > Synth > Waveform (type 3, >=v1.28)
|
|
|
|
* - CueName > Cue > Block > Track > Command > Synth > Synth > Waveform (type 8)
|
|
|
|
* - others should be possible but haven't been observed
|
|
|
|
* Atom Craft may only target certain .acb versions so some links are later removed
|
|
|
|
* Not all cues to point to though Waveforms, as some are just config events/commands.
|
|
|
|
* .acb link to .awb by name (loaded manually), though they have a checksum/hash to validate.
|
2019-07-14 21:24:28 +02:00
|
|
|
*/
|
|
|
|
|
2019-07-21 13:11:25 +02:00
|
|
|
//;VGM_LOG("ACB: find waveid=%i\n", waveid);
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-10-06 17:54:54 +02:00
|
|
|
acb.acbFile = streamFile;
|
|
|
|
|
|
|
|
acb.Header = utf_open(acb.acbFile, 0x00, NULL, NULL);
|
2019-09-02 20:41:04 +02:00
|
|
|
if (!acb.Header) goto fail;
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
acb.target_waveid = waveid;
|
2019-07-14 21:24:28 +02:00
|
|
|
acb.is_memory = is_memory;
|
2019-12-15 19:37:52 +01:00
|
|
|
acb.has_TrackEventTable = utf_query_data(acb.Header, 0, "TrackEventTable", NULL,NULL);
|
|
|
|
acb.has_CommandTable = utf_query_data(acb.Header, 0, "CommandTable", NULL,NULL);
|
2019-07-14 21:24:28 +02:00
|
|
|
|
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
/* read all possible cue names and find which waveids are referenced by it */
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!open_utf_subtable(&acb, &acb.CueNameSf, &acb.CueNameTable, "CueNameTable", &CueName_rows))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
|
|
|
for (i = 0; i < CueName_rows; i++) {
|
2019-07-14 21:24:28 +02:00
|
|
|
|
2019-10-06 17:54:54 +02:00
|
|
|
if (!load_acb_cuename(&acb, i))
|
2019-09-02 20:41:04 +02:00
|
|
|
goto fail;
|
2019-07-14 21:24:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* meh copy */
|
2019-09-02 20:41:04 +02:00
|
|
|
if (acb.awbname_count > 0) {
|
2019-07-14 21:24:28 +02:00
|
|
|
strncpy(vgmstream->stream_name, acb.name, STREAM_NAME_SIZE);
|
|
|
|
vgmstream->stream_name[STREAM_NAME_SIZE - 1] = '\0';
|
|
|
|
}
|
|
|
|
|
2019-09-02 20:41:04 +02:00
|
|
|
fail:
|
2019-07-14 21:24:28 +02:00
|
|
|
utf_close(acb.Header);
|
2019-10-06 17:54:54 +02:00
|
|
|
|
2019-07-14 21:24:28 +02:00
|
|
|
utf_close(acb.CueNameTable);
|
|
|
|
utf_close(acb.CueTable);
|
|
|
|
utf_close(acb.SequenceTable);
|
|
|
|
utf_close(acb.TrackTable);
|
2019-10-06 17:54:54 +02:00
|
|
|
utf_close(acb.TrackCommandTable);
|
2019-07-14 21:24:28 +02:00
|
|
|
utf_close(acb.SynthTable);
|
|
|
|
utf_close(acb.WaveformTable);
|
2019-10-06 17:54:54 +02:00
|
|
|
|
|
|
|
close_streamfile(acb.CueNameSf);
|
|
|
|
close_streamfile(acb.CueSf);
|
|
|
|
close_streamfile(acb.SequenceSf);
|
|
|
|
close_streamfile(acb.TrackSf);
|
|
|
|
close_streamfile(acb.TrackCommandSf);
|
|
|
|
close_streamfile(acb.SynthSf);
|
|
|
|
close_streamfile(acb.WaveformSf);
|
2019-07-14 21:24:28 +02:00
|
|
|
}
|