Add .acb with internal .awb and names, fix some .awb

This commit is contained in:
bnnm 2019-07-14 21:24:28 +02:00
parent a2d54810d3
commit ac1048d52f
10 changed files with 1148 additions and 29 deletions

View File

@ -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)]

View File

@ -232,6 +232,10 @@
RelativePath=".\meta\aax_utf.h"
>
</File>
<File
RelativePath=".\meta\acb_utf.h"
>
</File>
<File
RelativePath=".\meta\9tav_streamfile.h"
>
@ -396,6 +400,10 @@
RelativePath=".\meta\aax.c"
>
</File>
<File
RelativePath=".\meta\acb.c"
>
</File>
<File
RelativePath=".\meta\acm.c"
>

View File

@ -101,6 +101,7 @@
<ClInclude Include="vgmstream.h" />
<ClInclude Include="meta\adx_keys.h" />
<ClInclude Include="meta\aax_utf.h" />
<ClInclude Include="meta\acb_utf.h" />
<ClInclude Include="meta\9tav_streamfile.h" />
<ClInclude Include="meta\aix_streamfile.h" />
<ClInclude Include="meta\awc_xma_streamfile.h" />
@ -231,6 +232,7 @@
<ClCompile Include="meta\wv2.c" />
<ClCompile Include="meta\xau_konami.c" />
<ClCompile Include="meta\aax.c" />
<ClCompile Include="meta\acb.c" />
<ClCompile Include="meta\acm.c" />
<ClCompile Include="meta\adpcm_capcom.c" />
<ClCompile Include="meta\ads.c" />

View File

@ -71,6 +71,9 @@
<ClInclude Include="meta\aax_utf.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
<ClInclude Include="meta\acb_utf.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
<ClInclude Include="meta\9tav_streamfile.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
@ -259,6 +262,9 @@
<ClCompile Include="meta\aax.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\acb.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\acm.c">
<Filter>meta\Source Files</Filter>
</ClCompile>

566
src/meta/acb.c Normal file
View File

@ -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);
}

423
src/meta/acb_utf.h Normal file
View File

@ -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_ */

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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);

View File

@ -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) */