diff --git a/doc/USAGE.md b/doc/USAGE.md index 78e7bfb2..e8c61ee3 100644 --- a/doc/USAGE.md +++ b/doc/USAGE.md @@ -463,7 +463,7 @@ You can also choose which channels to play using *TXTP*. For example, create a file named `song.adx#C1,2.txtp` to play only channels 1 and 2 from `song.adx`. *TXTP* also has command to set how files are downmixed. -### Logged errors and unplayable supported files +## Logged errors and unplayable supported files Some formats should normally play, but somehow don't. In those cases plugins can print vgmstream's error info to console (for example, `.fsb` with an unknown codec, `.hca/awb` with missing decryption key, bank has no audio, `.txth` is diff --git a/fb2k/foo_streamfile.cpp b/fb2k/foo_streamfile.cpp index 6a00f3ea..fa2ea0d4 100644 --- a/fb2k/foo_streamfile.cpp +++ b/fb2k/foo_streamfile.cpp @@ -23,8 +23,9 @@ typedef struct { bool m_file_opened; /* if foobar IO service opened the file */ service_ptr_t m_file; /* foobar IO service */ - abort_callback * p_abort; /* foobar error stuff */ - char* name; /* IO filename */ + abort_callback* p_abort; /* foobar error stuff */ + /*const*/ char* name; /* IO filename */ + int name_len; /* cache */ offv_t offset; /* last read offset (info) */ offv_t buf_offset; /* current buffer data start */ uint8_t* buf; /* data buffer */ @@ -117,14 +118,21 @@ static offv_t foo_get_offset(FOO_STREAMFILE* sf) { return sf->offset; } static void foo_get_name(FOO_STREAMFILE* sf, char* name, size_t name_size) { - /* Most crap only cares about the filename itself */ - size_t ourlen = strlen(sf->name); - if (ourlen > name_size) { - if (name_size) strcpy(name, sf->name + ourlen - name_size + 1); - } - else { + int copy_size = sf->name_len + 1; + if (copy_size > name_size) + copy_size = name_size; + + memcpy(name, sf->name, copy_size); + name[copy_size - 1] = '\0'; + + /* + //TODO: check again (looks like a truncate-from-the-end copy, probably a useless remnant of olden times) + if (sf->name_len > name_size) { + strcpy(name, sf->name + sf->name_len - name_size + 1); + } else { strcpy(name, sf->name); } + */ } static void foo_close(FOO_STREAMFILE* sf) { sf->m_file.release(); //release alloc'ed ptr @@ -178,8 +186,11 @@ static STREAMFILE* open_foo_streamfile_buffer_by_file(service_ptr_t m_file this_sf->buf_size = buf_size; this_sf->buf = buf; + //TODO: foobar filenames look like "file://C:\path\to\file.adx" + // maybe should hide the internal protocol and restore on open? this_sf->name = strdup(filename); if (!this_sf->name) goto fail; + this_sf->name_len = strlen(this_sf->name); /* cache file_size */ if (this_sf->m_file_opened) diff --git a/src/coding/ffmpeg_decoder_custom_opus.c b/src/coding/ffmpeg_decoder_custom_opus.c index d5d97a3d..80f676e1 100644 --- a/src/coding/ffmpeg_decoder_custom_opus.c +++ b/src/coding/ffmpeg_decoder_custom_opus.c @@ -456,7 +456,7 @@ static size_t make_oggs_page(uint8_t* buf, int buf_size, size_t data_size, int p } segment_count = (int)(data_size / 0xFF + 1); - put_u32be(buf+0x00, 0x4F676753); /* capture pattern ("OggS") */ + put_u32be(buf+0x00, get_id32be("OggS")); /* capture pattern */ put_u8 (buf+0x04, 0); /* stream structure version, fixed */ put_u8 (buf+0x05, header_type_flag); /* bitflags (0: normal, continued = 1, first = 2, last = 4) */ put_u32le(buf+0x06, (uint32_t)(absolute_granule >> 0 & 0xFFFFFFFF)); /* lower */ @@ -517,8 +517,8 @@ static size_t make_opus_header(uint8_t* buf, int buf_size, opus_config *cfg) { goto fail; } - put_u32be(buf+0x00, 0x4F707573); /* "Opus" header magic */ - put_u32be(buf+0x04, 0x48656164); /* "Head" header magic */ + put_u32be(buf+0x00, get_id32be("Opus")); + put_u32be(buf+0x04, get_id32be("Head")); put_u8 (buf+0x08, 1); /* version */ put_u8 (buf+0x09, cfg->channels); put_s16le(buf+0x0A, cfg->skip); @@ -575,19 +575,23 @@ fail: static size_t make_oggs_first(uint8_t* buf, int buf_size, opus_config* cfg) { int buf_done = 0; size_t bytes; + size_t page_size = 0x1c; /* fixed for header page */ if (buf_size < 0x100) /* approx */ goto fail; - /* make header */ - bytes = make_opus_header(buf+buf_done + 0x1c,buf_size, cfg); - make_oggs_page(buf+buf_done + 0x00,buf_size, bytes, 0, 0); - buf_done += 0x1c + bytes; + /* make header (first data, then page for checksum) */ + bytes = make_opus_header(buf + page_size, buf_size - page_size, cfg); + make_oggs_page(buf, buf_size, bytes, 0, 0); + buf_done += (page_size + bytes); + + buf += buf_done; + buf_size -= buf_done; /* make comment */ - bytes = make_opus_comment(buf+buf_done + 0x1c,buf_size); - make_oggs_page(buf+buf_done + 0x00,buf_size, bytes, 1, 0); - buf_done += 0x1c + bytes; + bytes = make_opus_comment(buf + page_size, buf_size - page_size); + make_oggs_page(buf, buf_size, bytes, 1, 0); + buf_done += (page_size + bytes); return buf_done; fail: diff --git a/src/layout/segmented.c b/src/layout/segmented.c index e9bc492f..649a3063 100644 --- a/src/layout/segmented.c +++ b/src/layout/segmented.c @@ -177,9 +177,6 @@ int setup_layout_segmented(segmented_layout_data* data) { for (i = 0; i < data->segment_count; i++) { int segment_input_channels, segment_output_channels; - /* allow config if set for fine-tuned parts (usually TXTP only) */ - data->segments[i]->config_enabled = data->segments[i]->config.config_set; - if (data->segments[i] == NULL) { VGM_LOG("SEGMENTED: no vgmstream in segment %i\n", i); goto fail; @@ -190,6 +187,9 @@ int setup_layout_segmented(segmented_layout_data* data) { goto fail; } + /* allow config if set for fine-tuned parts (usually TXTP only) */ + data->segments[i]->config_enabled = data->segments[i]->config.config_set; + /* disable so that looping is controlled by render_vgmstream_segmented */ if (data->segments[i]->loop_flag != 0) { VGM_LOG("SEGMENTED: segment %i is looped\n", i); diff --git a/src/meta/acb.c b/src/meta/acb.c index 12f5eb78..4929633b 100644 --- a/src/meta/acb.c +++ b/src/meta/acb.c @@ -79,37 +79,101 @@ fail: /* extra config for .acb with lots of sounds, since there is a lot of IO back and forth, * ex. +7000 acb+awb subsongs in Ultra Despair Girls (PC) */ //TODO: could pre-load all sections first, but needs cache for multiple subsongs (+semaphs, if multiple read the same thing) -#define ACB_TABLE_BUFFER_CUENAME 0x8000 -#define ACB_TABLE_BUFFER_CUE 0x40000 +#define ACB_TABLE_BUFFER_CUENAME 0x4000 +#define ACB_TABLE_BUFFER_CUE 0x2000 #define ACB_TABLE_BUFFER_BLOCKSEQUENCE 0x8000 #define ACB_TABLE_BUFFER_BLOCK 0x8000 -#define ACB_TABLE_BUFFER_SEQUENCE 0x40000 -#define ACB_TABLE_BUFFER_TRACK 0x10000 -#define ACB_TABLE_BUFFER_TRACKCOMMAND 0x20000 -#define ACB_TABLE_BUFFER_SYNTH 0x40000 -#define ACB_TABLE_BUFFER_WAVEFORM 0x20000 +#define ACB_TABLE_BUFFER_SEQUENCE 0x4000 +#define ACB_TABLE_BUFFER_TRACK 0x1000 +#define ACB_TABLE_BUFFER_TRACKCOMMAND 0x2000 +#define ACB_TABLE_BUFFER_SYNTH 0x4000 +#define ACB_TABLE_BUFFER_WAVEFORM 0x4000 #define ACB_MAX_NAMELIST 255 #define ACB_MAX_NAME 1024 /* even more is possible in rare cases [Senran Kagura Burst Re:Newal (PC)] */ +#define ACB_MAX_BUFFER 0x8000 +#define ACB_PRELOAD 1 //todo: remove non-preloading code static STREAMFILE* setup_acb_streamfile(STREAMFILE* sf, size_t buffer_size) { STREAMFILE* new_sf = NULL; +#ifndef ACB_PRELOAD /* buffer seems better than reopening when opening multiple subsongs at the same time with STDIO, * even though there is more buffer trashing, maybe concurrent IO is slower */ new_sf = open_wrap_streamfile(sf); new_sf = open_buffer_streamfile_f(new_sf, buffer_size); - //new_sf = reopen_streamfile(sf, buffer_size); +#else + new_sf = reopen_streamfile(sf, buffer_size); +#endif return new_sf; } +typedef struct { + uint16_t CueIndex; + const char* CueName; +} CueName_t; + +typedef struct { + uint8_t ReferenceType; + uint16_t ReferenceIndex; +} Cue_t; + +typedef struct { + uint16_t NumTracks; + uint32_t TrackIndex_offset; + uint32_t TrackIndex_size; + uint16_t NumBlocks; + uint32_t BlockIndex_offset; + uint32_t BlockIndex_size; +} BlockSequence_t; + +typedef struct { + uint16_t NumTracks; + uint32_t TrackIndex_offset; + uint32_t TrackIndex_size; +} Block_t; + +typedef struct { + uint16_t NumTracks; + uint32_t TrackIndex_offset; + uint32_t TrackIndex_size; + uint16_t ActionTrackStartIndex; + uint16_t NumActionTracks; + uint32_t TrackValues_offset; + uint32_t TrackValues_size; + uint8_t Type; +} Sequence_t; + +typedef struct { + uint16_t EventIndex; +} Track_t; + +typedef struct { + uint32_t Command_offset; + uint32_t Command_size; +} TrackCommand_t; + +typedef struct { + uint8_t Type; + uint32_t ReferenceItems_offset; + uint32_t ReferenceItems_size; +} Synth_t; + +typedef struct { + uint16_t Id; + uint16_t PortNo; + uint8_t Streaming; +} Waveform_t; + + typedef struct { STREAMFILE* acbFile; /* original reference, don't close */ /* keep track of these tables so they can be closed when done */ utf_context* Header; + utf_context* TempTable; utf_context* CueNameTable; utf_context* CueTable; @@ -121,6 +185,7 @@ typedef struct { utf_context* SynthTable; utf_context* WaveformTable; + STREAMFILE* TempSf; STREAMFILE* CueNameSf; STREAMFILE* CueSf; STREAMFILE* BlockSequenceSf; @@ -131,12 +196,37 @@ typedef struct { STREAMFILE* SynthSf; STREAMFILE* WaveformSf; + Cue_t* Cue; + CueName_t* CueName; + BlockSequence_t* BlockSequence; + Block_t* Block; + Sequence_t* Sequence; + Track_t* Track; + TrackCommand_t* TrackCommand; + Synth_t* Synth; + Waveform_t* Waveform; + + int Cue_rows; + int CueName_rows; + int BlockSequence_rows; + int Block_rows; + int Sequence_rows; + int Track_rows; + int TrackCommand_rows; + int Synth_rows; + int Waveform_rows; + + uint8_t* buf; + int buf_size; + /* config */ int is_memory; int target_waveid; int target_port; + //todo remove int has_TrackEventTable; int has_CommandTable; + int is_preload; /* to avoid infinite/circular references (AtomViewer crashes otherwise) */ int synth_depth; @@ -149,8 +239,34 @@ typedef struct { int16_t awbname_list[ACB_MAX_NAMELIST]; char name[ACB_MAX_NAME]; + } acb_header; + +static int read_buffer(acb_header* acb, uint32_t offset, uint32_t size, STREAMFILE* sf) { + int bytes; + + if (acb->buf_size < size) { + if (size > ACB_MAX_BUFFER) { + VGM_LOG("ACB: buffer too big: %x\n", size); + goto fail; + } + /* could realloc and stuff but... */ + acb->buf_size = ACB_MAX_BUFFER; + acb->buf = malloc(acb->buf_size * sizeof(uint8_t)); + if (!acb->buf) goto fail; + } + + + bytes = read_streamfile(acb->buf, offset, size, sf); + if (bytes != size) goto fail; + + return 1; +fail: + VGM_LOG("ACB: failed buffer"); + return 0; +} + static int open_utf_subtable(acb_header* acb, STREAMFILE* *TableSf, utf_context* *Table, const char* TableName, int* rows, int buffer) { uint32_t offset = 0; @@ -189,6 +305,10 @@ static void acb_cpy(char* dst, int dst_max, const char* src) { } static void add_acb_name(acb_header* acb, int8_t Streaming) { + if (!acb->cuename_name) { + VGM_LOG("ACB: no name\n"); + return; + } /* ignore name repeats */ if (acb->awbname_count) { @@ -220,73 +340,175 @@ static void add_acb_name(acb_header* acb, int8_t Streaming) { } -/*******************************************************************************/ +/*****************************************************************************/ /* OBJECT HANDLERS */ -static int load_acb_waveform(acb_header* acb, int16_t Index) { - uint16_t Id, PortNo; - uint8_t Streaming; +static int preload_acb_waveform(acb_header* acb) { + utf_context* Table = acb->WaveformTable; + int* p_rows = &acb->Waveform_rows; + int i, c_Id, c_MemoryAwbId, c_StreamAwbId, c_StreamAwbPortNo, c_Streaming; - /* read Waveform[Index] */ - if (!open_utf_subtable(acb, &acb->WaveformSf, &acb->WaveformTable, "WaveformTable", NULL, ACB_TABLE_BUFFER_WAVEFORM)) + if (*p_rows) + return 1; + if (!open_utf_subtable(acb, &acb->WaveformSf, &Table, "WaveformTable", p_rows, ACB_TABLE_BUFFER_WAVEFORM)) goto fail; - if (!utf_query_u16(acb->WaveformTable, Index, "Id", &Id)) { /* older versions use Id */ - if (acb->is_memory) { - if (!utf_query_u16(acb->WaveformTable, Index, "MemoryAwbId", &Id)) - goto fail; - PortNo = 0xFFFF; - } else { - if (!utf_query_u16(acb->WaveformTable, Index, "StreamAwbId", &Id)) - goto fail; - if (!utf_query_u16(acb->WaveformTable, Index, "StreamAwbPortNo", &PortNo)) - PortNo = 0; /* assumed */ + if (!*p_rows) + return 1; + ;VGM_LOG("acb: preload Waveform=%i\n", *p_rows); + + acb->Waveform = malloc(*p_rows * sizeof(Waveform_t)); + if (!acb->Waveform) goto fail; + + c_Id = utf_get_column(Table, "Id"); + c_MemoryAwbId = utf_get_column(Table, "MemoryAwbId"); + c_StreamAwbId = utf_get_column(Table, "StreamAwbId"); + c_StreamAwbPortNo = utf_get_column(Table, "StreamAwbPortNo"); + c_Streaming = utf_get_column(Table, "Streaming"); + + for (i = 0; i < *p_rows; i++) { + Waveform_t* r = &acb->Waveform[i]; + + if (!utf_query_col_u16(Table, i, c_Id, &r->Id)) { /* older versions use Id */ + if (acb->is_memory) { + utf_query_col_u16(Table, i, c_MemoryAwbId, &r->Id); + r->PortNo = 0xFFFF; + } else { + utf_query_col_u16(Table, i, c_StreamAwbId, &r->Id); + utf_query_col_u16(Table, i, c_StreamAwbPortNo, &r->PortNo); /* assumed default 0 if doesn't exist */ + } } - } - else { - PortNo = 0xFFFF; + else { + r->PortNo = 0xFFFF; + } + utf_query_col_u8(Table, i, c_Streaming, &r->Streaming); } - if (!utf_query_u8(acb->WaveformTable, Index, "Streaming", &Streaming)) - goto fail; - //;VGM_LOG("ACB: Waveform[%i]: Id=%i, PortNo=%i, Streaming=%i\n", Index, Id, PortNo, Streaming); - - /* not found but valid */ - if (Id != acb->target_waveid) - return 1; - - /* correct AWB port (check ignored if set to -1) */ - if (acb->target_port >= 0 && PortNo != 0xFFFF && PortNo != acb->target_port) - return 1; - - /* must match our target's (0=memory, 1=streaming, 2=memory (prefetch)+stream) */ - if ((acb->is_memory && Streaming == 1) || (!acb->is_memory && Streaming == 0)) - return 1; - - /* aaand finally get name (phew) */ - add_acb_name(acb, Streaming); - + ;VGM_LOG("acb: preload Waveform done\n"); return 1; fail: + VGM_LOG("ACB: failed Waveform preload\n"); return 0; } -/* define here for Synths pointing to Sequences */ -static int load_acb_sequence(acb_header* acb, int16_t Index); +static int load_acb_waveform(acb_header* acb, uint16_t Index) { + Waveform_t* r; + Waveform_t tmp; -static int load_acb_synth(acb_header* acb, int16_t Index) { + /* read Waveform[Index] */ + if (acb->is_preload) { + if (!preload_acb_waveform(acb)) goto fail; + if (Index > acb->Waveform_rows) goto fail; + r = &acb->Waveform[Index]; + } + else { + r = &tmp; + if (!open_utf_subtable(acb, &acb->WaveformSf, &acb->WaveformTable, "WaveformTable", NULL, ACB_TABLE_BUFFER_WAVEFORM)) + goto fail; + + if (!utf_query_u16(acb->WaveformTable, Index, "Id", &r->Id)) { /* older versions use Id */ + if (acb->is_memory) { + if (!utf_query_u16(acb->WaveformTable, Index, "MemoryAwbId", &r->Id)) + goto fail; + r->PortNo = 0xFFFF; + } else { + if (!utf_query_u16(acb->WaveformTable, Index, "StreamAwbId", &r->Id)) + goto fail; + if (!utf_query_u16(acb->WaveformTable, Index, "StreamAwbPortNo", &r->PortNo)) + r->PortNo = 0; /* assumed */ + } + } + else { + r->PortNo = 0xFFFF; + } + if (!utf_query_u8(acb->WaveformTable, Index, "Streaming", &r->Streaming)) + goto fail; + //;VGM_LOG("ACB: Waveform[%i]: Id=%i, PortNo=%i, Streaming=%i\n", Index, r->Id, r->PortNo, r->Streaming); + } + + /* not found but valid */ + if (r->Id != acb->target_waveid) + return 1; + + /* correct AWB port (check ignored if set to -1) */ + if (acb->target_port >= 0 && r->PortNo != 0xFFFF && r->PortNo != acb->target_port) + return 1; + + /* must match our target's (0=memory, 1=streaming, 2=memory (prefetch)+stream) */ + if ((acb->is_memory && r->Streaming == 1) || (!acb->is_memory && r->Streaming == 0)) + return 1; + + /* aaand finally get name (phew) */ + add_acb_name(acb, r->Streaming); + + return 1; +fail: + VGM_LOG("ACB: failed Waveform %i\n", Index); + return 0; +} + +/*****************************************************************************/ + +/* define here for Synths pointing to Sequences */ +static int load_acb_sequence(acb_header* acb, uint16_t Index); + +static int preload_acb_synth(acb_header* acb) { + utf_context* Table = acb->SynthTable; + int* p_rows = &acb->Synth_rows; + int i, c_Type, c_ReferenceItems; + + if (*p_rows) + return 1; + if (!open_utf_subtable(acb, &acb->SynthSf, &Table, "SynthTable", p_rows, ACB_TABLE_BUFFER_SYNTH)) + goto fail; + if (!*p_rows) + return 1; + ;VGM_LOG("acb: preload Synth=%i\n", *p_rows); + + acb->Synth = malloc(*p_rows * sizeof(Synth_t)); + if (!acb->Synth) goto fail; + + c_Type = utf_get_column(Table, "Type"); + c_ReferenceItems = utf_get_column(Table, "ReferenceItems"); + + for (i = 0; i < *p_rows; i++) { + Synth_t* r = &acb->Synth[i]; + + utf_query_col_u8(Table, i, c_Type, &r->Type); + utf_query_col_data(Table, i, c_ReferenceItems, &r->ReferenceItems_offset, &r->ReferenceItems_size); + //;VGM_LOG("ACB: Synth[%i]: Type=%x, ReferenceItems={%x,%x}\n", Index, r->Type, r->ReferenceItems_offset, r->ReferenceItems_size); + } + + ;VGM_LOG("acb: preload Synth done\n"); + return 1; +fail: + VGM_LOG("ACB: failed Synth preload\n"); + return 0; +} + + +static int load_acb_synth(acb_header* acb, uint16_t Index) { int i, count; - uint8_t Type; - uint32_t ReferenceItems_offset, ReferenceItems_size; + Synth_t* r; + Synth_t tmp; /* read Synth[Index] */ - if (!open_utf_subtable(acb, &acb->SynthSf, &acb->SynthTable, "SynthTable", NULL, ACB_TABLE_BUFFER_SYNTH)) - goto fail; - if (!utf_query_u8(acb->SynthTable, Index, "Type", &Type)) - goto fail; - if (!utf_query_data(acb->SynthTable, Index, "ReferenceItems", &ReferenceItems_offset, &ReferenceItems_size)) - goto fail; - //;VGM_LOG("ACB: Synth[%i]: Type=%x, ReferenceItems={%x,%x}\n", Index, Type, ReferenceItems_offset, ReferenceItems_size); + if (acb->is_preload) { + if (!preload_acb_synth(acb)) goto fail; + if (Index > acb->Synth_rows) goto fail; + r = &acb->Synth[Index]; + } + else { + r = &tmp; + if (!open_utf_subtable(acb, &acb->SynthSf, &acb->SynthTable, "SynthTable", NULL, ACB_TABLE_BUFFER_SYNTH)) + goto fail; + + if (!utf_query_u8(acb->SynthTable, Index, "Type", &r->Type)) + goto fail; + if (!utf_query_data(acb->SynthTable, Index, "ReferenceItems", &r->ReferenceItems_offset, &r->ReferenceItems_size)) + goto fail; + //;VGM_LOG("ACB: Synth[%i]: Type=%x, ReferenceItems={%x,%x}\n", Index, r->Type, r->ReferenceItems_offset, r->ReferenceItems_size); + } acb->synth_depth++; @@ -314,10 +536,13 @@ static int load_acb_synth(acb_header* acb, int16_t Index) { * Since we want to find all possible Waveforms that could match our id, we ignore Type and just parse all ReferenceItems. */ - count = ReferenceItems_size / 0x04; + if (!read_buffer(acb, r->ReferenceItems_offset, r->ReferenceItems_size, acb->SynthSf)) + goto fail; + + count = r->ReferenceItems_size / 0x04; for (i = 0; i < count; i++) { - uint16_t item_type = read_u16be(ReferenceItems_offset + i*0x04 + 0x00, acb->SynthSf); - uint16_t item_index = read_u16be(ReferenceItems_offset + i*0x04 + 0x02, acb->SynthSf); + uint16_t item_type = get_u16be(acb->buf + i*0x04 + 0x00); + uint16_t item_index = get_u16be(acb->buf + i*0x04 + 0x02); //;VGM_LOG("ACB: Synth.ReferenceItem: type=%x, index=%x\n", item_type, item_index); switch(item_type) { @@ -344,7 +569,7 @@ static int load_acb_synth(acb_header* acb, int16_t Index) { 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", item_type, ReferenceItems_offset, ReferenceItems_size); + VGM_LOG("ACB: unknown Synth.ReferenceItem type %x at %x + %x\n", item_type, r->ReferenceItems_offset, r->ReferenceItems_size); count = 0; /* force end without failing */ break; } @@ -354,23 +579,26 @@ static int load_acb_synth(acb_header* acb, int16_t Index) { return 1; fail: + VGM_LOG("ACB: failed Synth %i\n", Index); return 0; } +/*****************************************************************************/ static int load_acb_command_tlvs(acb_header* acb, STREAMFILE* sf, uint32_t Command_offset, uint32_t Command_size) { - uint32_t offset = Command_offset; - uint32_t max_offset = Command_offset + Command_size; uint16_t tlv_code, tlv_type, tlv_index; uint8_t tlv_size; + uint32_t pos = 0; + uint32_t max_pos = Command_size; - //todo read full offsets + if (!read_buffer(acb, Command_offset, Command_size, sf)) + goto fail; /* read a (name)Command multiple TLV data */ - while (offset < max_offset) { - tlv_code = read_u16be(offset + 0x00, sf); - tlv_size = read_u8 (offset + 0x02, sf); - offset += 0x03; + while (pos < max_pos) { + tlv_code = get_u16be(acb->buf + pos + 0x00); + tlv_size = get_u8 (acb->buf + pos + 0x02); + pos += 0x03; /* There are around 160 codes (some unused), with things like set volume, pan, stop, mute, and so on. * Multiple commands are linked and only "note on" seems to point so other objects, so maybe others @@ -383,8 +611,8 @@ static int load_acb_command_tlvs(acb_header* acb, STREAMFILE* sf, uint32_t Comma break; } - tlv_type = read_u16be(offset + 0x00, sf); /* ReferenceItem */ - tlv_index = read_u16be(offset + 0x02, sf); + tlv_type = get_u16be(acb->buf + pos + 0x00); /* ReferenceItem */ + tlv_index = get_u16be(acb->buf + pos + 0x02); //;VGM_LOG("ACB: TLV at %x: type %x, index=%x\n", offset, tlv_type, tlv_index); /* same as Synth's ReferenceItem type? */ @@ -400,8 +628,8 @@ static int load_acb_command_tlvs(acb_header* acb, STREAMFILE* sf, uint32_t Comma break; default: - VGM_LOG("ACB: unknown TLV type %x at %x + %x\n", tlv_type, offset, tlv_size); - max_offset = 0; /* force end without failing */ + VGM_LOG("ACB: unknown TLV type %x at %x + %x\n", tlv_type, Command_offset + pos, tlv_size); + max_pos = 0; /* force end without failing */ break; } break; @@ -433,79 +661,232 @@ static int load_acb_command_tlvs(acb_header* acb, STREAMFILE* sf, uint32_t Comma break; } - offset += tlv_size; + pos += tlv_size; } return 1; fail: + VGM_LOG("ACB: failed Command TLVs\n"); return 0; } -static int load_acb_track_event_command(acb_header* acb, int16_t Index) { - uint16_t EventIndex; - uint32_t Command_offset, Command_size; +/*****************************************************************************/ + +static int preload_acb_trackcommand(acb_header* acb) { + utf_context* Table = acb->TrackCommandTable; + int* p_rows = &acb->TrackCommand_rows; + int i, c_Command; + + + if (*p_rows) + return 1; + /* load either TrackEvent (>=v1.28) or Command () <=v1.27 */ + if (!open_utf_subtable(acb, &acb->TrackCommandSf, &Table, "TrackEventTable", p_rows, ACB_TABLE_BUFFER_TRACKCOMMAND)) { + if (!open_utf_subtable(acb, &acb->TrackCommandSf, &Table, "CommandTable", p_rows, ACB_TABLE_BUFFER_TRACKCOMMAND)) + goto fail; + } + if (!*p_rows) + return 1; + ;VGM_LOG("acb: preload TrackEvent/Command=%i\n", *p_rows); + + acb->TrackCommand = malloc(*p_rows * sizeof(TrackCommand_t)); + if (!acb->TrackCommand) goto fail; + + c_Command = utf_get_column(Table, "Command"); + + for (i = 0; i < *p_rows; i++) { + TrackCommand_t* r = &acb->TrackCommand[i]; + + utf_query_col_data(Table, i, c_Command, &r->Command_offset, &r->Command_size); + //;VGM_LOG("ACB: TrackEvent/Command[%i]: Command={%x,%x}\n", i, r->Command_offset, r->Command_size); + } + + ;VGM_LOG("acb: preload TrackEvent/Command done\n"); + return 1; +fail: + VGM_LOG("ACB: failed TrackEvent/Command preload\n"); + return 0; +} + +static int load_acb_trackcommand(acb_header* acb, uint16_t Index) { + TrackCommand_t* r; + TrackCommand_t tmp; + + + /* read TrackEvent/Command[Index] */ + if (acb->is_preload) { + if (!preload_acb_trackcommand(acb)) goto fail; + if (Index > acb->TrackCommand_rows) goto fail; + r = &acb->TrackCommand[Index]; + } + else { + r = &tmp; + if (acb->has_CommandTable) { /* <=v1.27 */ + if (!open_utf_subtable(acb, &acb->TrackCommandSf, &acb->TrackCommandTable, "CommandTable", NULL, ACB_TABLE_BUFFER_TRACKCOMMAND)) + goto fail; + } + else if (acb->has_TrackEventTable) { /* >=v1.28 */ + if (!open_utf_subtable(acb, &acb->TrackCommandSf, &acb->TrackCommandTable, "TrackEventTable", NULL, ACB_TABLE_BUFFER_TRACKCOMMAND)) + goto fail; + } + else { + VGM_LOG("ACB: unknown command table\n"); + goto fail; + } + if (!utf_query_data(acb->TrackCommandTable, Index, "Command", &r->Command_offset, &r->Command_size)) + goto fail; + //;VGM_LOG("ACB: TrackEvent/Command[%i]: Command={%x,%x}\n", Index, r->Command_offset, r->Command_size); + } + + + /* read Command's TLVs */ + if (!load_acb_command_tlvs(acb, acb->TrackCommandSf, r->Command_offset, r->Command_size)) + goto fail; + + return 1; +fail: + VGM_LOG("ACB: failed TrackCommand %i\n", Index); + return 0; +} + +/*****************************************************************************/ + +static int preload_acb_track(acb_header* acb) { + utf_context* Table = acb->TrackTable; + int* p_rows = &acb->Track_rows; + int i, c_EventIndex; + + if (*p_rows) + return 1; + if (!open_utf_subtable(acb, &acb->TrackSf, &Table, "TrackTable", p_rows, ACB_TABLE_BUFFER_CUE)) + goto fail; + if (!*p_rows) + return 1; + ;VGM_LOG("acb: preload Track=%i\n", *p_rows); + + acb->Track = malloc(*p_rows * sizeof(Track_t)); + if (!acb->Track) goto fail; + + c_EventIndex = utf_get_column(Table, "EventIndex"); + + for (i = 0; i < *p_rows; i++) { + Track_t* r = &acb->Track[i]; + + if (!utf_query_col_u16(Table, i, c_EventIndex, &r->EventIndex)) + goto fail; + //;VGM_LOG("ACB: Track[%i]: EventIndex=%i\n", i, r->EventIndex); + } + + ;VGM_LOG("acb: preload Track done\n"); + return 1; +fail: + VGM_LOG("ACB: failed Track preload\n"); + return 0; +} + +static int load_acb_track(acb_header* acb, uint16_t Index) { + Track_t* r; + Track_t tmp; /* read Track[Index] */ - if (!open_utf_subtable(acb, &acb->TrackSf, &acb->TrackTable, "TrackTable", NULL, ACB_TABLE_BUFFER_TRACK )) - goto fail; - if (!utf_query_u16(acb->TrackTable, Index, "EventIndex", &EventIndex)) - goto fail; - //;VGM_LOG("ACB: Track[%i]: EventIndex=%i\n", Index, EventIndex); + if (acb->is_preload) { + if (!preload_acb_track(acb)) goto fail; + if (Index > acb->Track_rows) goto fail; + r = &acb->Track[Index]; + } + else { + r = &tmp; + if (!open_utf_subtable(acb, &acb->TrackSf, &acb->TrackTable, "TrackTable", NULL, ACB_TABLE_BUFFER_TRACK)) + goto fail; + if (!utf_query_u16(acb->TrackTable, Index, "EventIndex", &r->EventIndex)) + goto fail; + } //todo CommandIndex? /* happens with some odd track without anything useful */ - if (EventIndex == 65535) + if (r->EventIndex == 65535) return 1; - /* next link varies with version, check by table existence */ - if (acb->has_CommandTable) { /* <=v1.27 */ - /* read Command[EventIndex] */ - if (!open_utf_subtable(acb, &acb->TrackCommandSf, &acb->TrackCommandTable, "CommandTable", NULL, ACB_TABLE_BUFFER_TRACKCOMMAND)) - goto fail; - if (!utf_query_data(acb->TrackCommandTable, EventIndex, "Command", &Command_offset, &Command_size)) - goto fail; - //;VGM_LOG("ACB: Command[%i]: Command={%x,%x}\n", EventIndex, Command_offset,Command_size); - } - else if (acb->has_TrackEventTable) { /* >=v1.28 */ - /* read TrackEvent[EventIndex] */ - if (!open_utf_subtable(acb, &acb->TrackCommandSf, &acb->TrackCommandTable, "TrackEventTable", NULL, ACB_TABLE_BUFFER_TRACKCOMMAND)) - goto fail; - if (!utf_query_data(acb->TrackCommandTable, EventIndex, "Command", &Command_offset, &Command_size)) - goto fail; - //;VGM_LOG("ACB: TrackEvent[%i]: Command={%x,%x}\n", EventIndex, Command_offset,Command_size); - } - else { - VGM_LOG("ACB: unknown command table\n"); - goto fail; - } - - /* read Command's TLVs */ - if (!load_acb_command_tlvs(acb, acb->TrackCommandSf, Command_offset, Command_size)) + if (!load_acb_trackcommand(acb, r->EventIndex)) goto fail; return 1; fail: + VGM_LOG("ACB: failed Track %i\n", Index); return 0; } -static int load_acb_sequence(acb_header* acb, int16_t Index) { +/*****************************************************************************/ + +static int preload_acb_sequence(acb_header* acb) { + utf_context* Table = acb->SequenceTable; + int* p_rows = &acb->Sequence_rows; + int i, c_NumTracks, c_TrackIndex, c_ActionTrackStartIndex, c_NumActionTracks, c_TrackValues, c_Type; + + if (*p_rows) + return 1; + if (!open_utf_subtable(acb, &acb->SequenceSf, &Table, "SequenceTable", p_rows, ACB_TABLE_BUFFER_SEQUENCE)) + goto fail; + if (!*p_rows) + return 1; + ;VGM_LOG("acb: preload Sequence=%i\n", *p_rows); + + acb->Sequence = malloc(*p_rows * sizeof(Sequence_t)); + if (!acb->Sequence) goto fail; + + c_NumTracks = utf_get_column(Table, "NumTracks"); + c_TrackIndex = utf_get_column(Table, "TrackIndex"); + c_ActionTrackStartIndex = utf_get_column(Table, "ActionTrackStartIndex"); + c_NumActionTracks = utf_get_column(Table, "NumActionTracks"); + c_TrackValues = utf_get_column(Table, "TrackValues"); + c_Type = utf_get_column(Table, "Type"); + + for (i = 0; i < *p_rows; i++) { + Sequence_t* r = &acb->Sequence[i]; + + utf_query_col_u16(Table, i, c_NumTracks, &r->NumTracks); + utf_query_col_data(Table, i, c_TrackIndex, &r->TrackIndex_offset, &r->TrackIndex_size); + utf_query_col_u16(Table, i, c_ActionTrackStartIndex, &r->ActionTrackStartIndex); + utf_query_col_u16(Table, i, c_NumActionTracks, &r->NumActionTracks); + utf_query_col_data(Table, i, c_TrackValues, &r->TrackValues_offset, &r->TrackValues_size); + utf_query_col_u8(Table, i, c_Type, &r->Type); + //;VGM_LOG("ACB: Sequence[%i]: NumTracks=%i, TrackIndex={%x, %x}, TrackIndex={%x, %x}, Type=%x\n", i, r->NumTracks, r->TrackIndex_offset, r->TrackIndex_size, r->TrackValues_offset, r->TrackValues_size, r->Type); + } + + ;VGM_LOG("acb: preload Sequence done\n"); + return 1; +fail: + VGM_LOG("ACB: failed Sequence preload\n"); + return 0; +} + +static int load_acb_sequence(acb_header* acb, uint16_t Index) { int i; - uint16_t NumTracks; - uint32_t TrackIndex_offset, TrackIndex_size; + Sequence_t* r; + Sequence_t tmp; /* read Sequence[Index] */ - if (!open_utf_subtable(acb, &acb->SequenceSf, &acb->SequenceTable, "SequenceTable", NULL, ACB_TABLE_BUFFER_SEQUENCE)) - goto fail; - if (!utf_query_u16(acb->SequenceTable, Index, "NumTracks", &NumTracks)) - goto fail; - if (!utf_query_data(acb->SequenceTable, Index, "TrackIndex", &TrackIndex_offset, &TrackIndex_size)) - goto fail; - //;VGM_LOG("ACB: Sequence[%i]: NumTracks=%i, TrackIndex={%x, %x}\n", Index, NumTracks, TrackIndex_offset,TrackIndex_size); + if (acb->is_preload) { + if (!preload_acb_sequence(acb)) goto fail; + if (Index > acb->Sequence_rows) goto fail; + r = &acb->Sequence[Index]; + } + else { + r = &tmp; + if (!open_utf_subtable(acb, &acb->SequenceSf, &acb->SequenceTable, "SequenceTable", NULL, ACB_TABLE_BUFFER_SEQUENCE)) + goto fail; + + if (!utf_query_u16(acb->SequenceTable, Index, "NumTracks", &r->NumTracks)) + goto fail; + if (!utf_query_data(acb->SequenceTable, Index, "TrackIndex", &r->TrackIndex_offset, &r->TrackIndex_size)) + goto fail; + } //todo .CommandIndex > SequenceCommand? + // most unknown types can be found in Ultra Despair Girls (PC) acb->sequence_depth++; @@ -514,106 +895,227 @@ static int load_acb_sequence(acb_header* acb, int16_t Index) { goto fail; /* max Sequence > Sequence > Sequence > Synth > Waveform (ex. Yakuza 6) */ } - if (NumTracks * 0x02 > TrackIndex_size) { /* padding may exist */ - VGM_LOG("ACB: wrong Sequence.TrackIndex size\n"); - goto fail; - } /* read Tracks inside Sequence */ - for (i = 0; i < NumTracks; i++) { - int16_t TrackIndex_index = read_s16be(TrackIndex_offset + i*0x02, acb->SequenceSf); - - if (!load_acb_track_event_command(acb, TrackIndex_index)) + if (r->NumActionTracks) { + VGM_LOG_ONCE("ACB: ignored ActionTrack[%i~%i]\n", r->ActionTrackStartIndex, r->NumActionTracks); + } + else { + if (r->NumTracks * 0x02 > r->TrackIndex_size) { /* padding may exist */ + VGM_LOG("ACB: wrong Sequence.TrackIndex size\n"); goto fail; + } + + switch(r->Type) { + case 0: /* common */ + if (!read_buffer(acb, r->TrackIndex_offset, r->TrackIndex_size, acb->SequenceSf)) + goto fail; + + for (i = 0; i < r->NumTracks; i++) { + int16_t TrackIndex_index = get_s16be(acb->buf + i*0x02); + + if (!load_acb_track(acb, TrackIndex_index)) + goto fail; + } + break; + + case 1: /* TrackIndex only, similar to 0? (rare) */ + VGM_LOG_ONCE("ACB: unknown Sequence.Type=%x\n", r->Type); + break; + case 3: /* TrackIndex + TrackValues */ + case 4: /* same */ + default: + VGM_LOG_ONCE("ACB: unknown Sequence.Type=%x\n", r->Type); + break; + } } acb->sequence_depth--; return 1; fail: + VGM_LOG("ACB: failed Sequence %i\n", Index); return 0; } -static int load_acb_block(acb_header* acb, int16_t Index) { +/*****************************************************************************/ + +static int preload_acb_block(acb_header* acb) { + utf_context* Table = acb->BlockTable; + int* p_rows = &acb->Block_rows; + int i, c_NumTracks, c_TrackIndex; + + if (*p_rows) + return 1; + if (!open_utf_subtable(acb, &acb->BlockSf, &Table, "BlockTable", p_rows, ACB_TABLE_BUFFER_BLOCK)) + goto fail; + if (!*p_rows) + return 1; + ;VGM_LOG("acb: preload Block=%i\n", *p_rows); + + acb->Block = malloc(*p_rows * sizeof(Block_t)); + if (!acb->Block) goto fail; + + c_NumTracks = utf_get_column(Table, "NumTracks"); + c_TrackIndex = utf_get_column(Table, "TrackIndex"); + + for (i = 0; i < *p_rows; i++) { + Block_t* r = &acb->Block[i]; + + utf_query_col_u16(Table, i, c_NumTracks, &r->NumTracks); + utf_query_col_data(Table, i, c_TrackIndex, &r->TrackIndex_offset, &r->TrackIndex_size); + //;VGM_LOG("ACB: Block[%i]: NumTracks=%i, TrackIndex={%x, %x}\n", i, r->NumTracks, r->TrackIndex_offset, r->TrackIndex_size); + } + + ;VGM_LOG("acb: preload Block done\n"); + return 1; +fail: + VGM_LOG("ACB: failed Block preload\n"); + return 0; +} + +static int load_acb_block(acb_header* acb, uint16_t Index) { int i; - uint16_t NumTracks; - uint32_t TrackIndex_offset, TrackIndex_size; + Block_t* r; + Block_t tmp; /* read Block[Index] */ - if (!open_utf_subtable(acb, &acb->BlockSf, &acb->BlockTable, "BlockTable", NULL, ACB_TABLE_BUFFER_BLOCK)) - goto fail; - if (!utf_query_u16(acb->BlockTable, Index, "NumTracks", &NumTracks)) - goto fail; - if (!utf_query_data(acb->BlockTable, Index, "TrackIndex", &TrackIndex_offset, &TrackIndex_size)) - goto fail; - //;VGM_LOG("ACB: Block[%i]: NumTracks=%i, TrackIndex={%x, %x}\n", Index, NumTracks, TrackIndex_offset,TrackIndex_size); + if (acb->is_preload) { + if (!preload_acb_block(acb)) goto fail; + if (Index > acb->Block_rows) goto fail; + r = &acb->Block[Index]; + } + else { + r = &tmp; + if (!open_utf_subtable(acb, &acb->BlockSf, &acb->BlockTable, "BlockTable", NULL, ACB_TABLE_BUFFER_BLOCK)) + goto fail; - if (NumTracks * 0x02 > TrackIndex_size) { /* padding may exist */ + if (!utf_query_u16(acb->BlockTable, Index, "NumTracks", &r->NumTracks)) + goto fail; + if (!utf_query_data(acb->BlockTable, Index, "TrackIndex", &r->TrackIndex_offset, &r->TrackIndex_size)) + goto fail; + } + + if (r->NumTracks * 0x02 > r->TrackIndex_size) { /* padding may exist */ VGM_LOG("ACB: wrong Block.TrackIndex size\n"); goto fail; } //todo .ActionTrackStartIndex/NumActionTracks > ? - /* read Tracks inside Block */ - for (i = 0; i < NumTracks; i++) { - int16_t TrackIndex_index = read_s16be(TrackIndex_offset + i*0x02, acb->BlockSf); + if (!read_buffer(acb, r->TrackIndex_offset, r->TrackIndex_size, acb->BlockSf)) + goto fail; - if (!load_acb_track_event_command(acb, TrackIndex_index)) + /* read Tracks inside Block */ + for (i = 0; i < r->NumTracks; i++) { + int16_t TrackIndex_index = get_s16be(acb->buf + i*0x02); + + if (!load_acb_track(acb, TrackIndex_index)) goto fail; } return 1; fail: + VGM_LOG("ACB: failed Block %i\n", Index); return 0; } -static int load_acb_blocksequence(acb_header* acb, int16_t Index) { - int i; +/*****************************************************************************/ - uint16_t NumTracks; - uint32_t TrackIndex_offset, TrackIndex_size; - uint16_t NumBlocks; - uint32_t BlockIndex_offset, BlockIndex_size; +static int preload_acb_blocksequence(acb_header* acb) { + utf_context* Table = acb->BlockSequenceTable; + int* p_rows = &acb->BlockSequence_rows; + int i, c_NumTracks, c_TrackIndex, c_NumBlocks, c_BlockIndex; + + if (*p_rows) + return 1; + if (!open_utf_subtable(acb, &acb->BlockSequenceSf, &Table, "BlockSequenceTable", p_rows, ACB_TABLE_BUFFER_BLOCKSEQUENCE)) + goto fail; + if (!*p_rows) + return 1; + ;VGM_LOG("acb: preload BlockSequence=%i\n", *p_rows); + + acb->BlockSequence = malloc(*p_rows * sizeof(BlockSequence_t)); + if (!acb->BlockSequence) goto fail; + + c_NumTracks = utf_get_column(Table, "NumTracks"); + c_TrackIndex = utf_get_column(Table, "TrackIndex"); + c_NumBlocks = utf_get_column(Table, "NumBlocks"); + c_BlockIndex = utf_get_column(Table, "BlockIndex"); + + for (i = 0; i < *p_rows; i++) { + BlockSequence_t* r = &acb->BlockSequence[i]; + + utf_query_col_u16(Table, i, c_NumTracks, &r->NumTracks); + utf_query_col_data(Table, i, c_TrackIndex, &r->TrackIndex_offset, &r->TrackIndex_size); + utf_query_col_u16(Table, i, c_NumBlocks, &r->NumBlocks); + utf_query_col_data(Table, i, c_BlockIndex, &r->BlockIndex_offset, &r->BlockIndex_size); + } + + ;VGM_LOG("acb: preload BlockSequence done\n"); + return 1; +fail: + VGM_LOG("ACB: failed BlockSequence preload\n"); + return 0; +} + +static int load_acb_blocksequence(acb_header* acb, uint16_t Index) { + int i; + BlockSequence_t* r; + BlockSequence_t tmp; /* read BlockSequence[Index] */ - if (!open_utf_subtable(acb, &acb->BlockSequenceSf, &acb->BlockSequenceTable, "BlockSequenceTable", NULL, ACB_TABLE_BUFFER_BLOCKSEQUENCE)) - goto fail; + if (acb->is_preload) { + if (!preload_acb_blocksequence(acb)) goto fail; + if (Index > acb->BlockSequence_rows) goto fail; + r = &acb->BlockSequence[Index]; + } + else { + r = &tmp; + if (!open_utf_subtable(acb, &acb->BlockSequenceSf, &acb->BlockSequenceTable, "BlockSequenceTable", NULL, ACB_TABLE_BUFFER_BLOCKSEQUENCE)) + goto fail; - if (!utf_query_u16(acb->BlockSequenceTable, Index, "NumTracks", &NumTracks)) - goto fail; - if (!utf_query_data(acb->BlockSequenceTable, Index, "TrackIndex", &TrackIndex_offset, &TrackIndex_size)) - goto fail; - if (!utf_query_u16(acb->BlockSequenceTable, Index, "NumBlocks", &NumBlocks)) - goto fail; - if (!utf_query_data(acb->BlockSequenceTable, Index, "BlockIndex", &BlockIndex_offset, &BlockIndex_size)) - goto fail; - //;VGM_LOG("ACB: BlockSequence[%i]: NumTracks=%i, TrackIndex={%x, %x}, NumBlocks=%i, BlockIndex={%x, %x}\n", Index, NumTracks, TrackIndex_offset,TrackIndex_size, NumBlocks, BlockIndex_offset,BlockIndex_size); + if (!utf_query_u16(acb->BlockSequenceTable, Index, "NumTracks", &r->NumTracks)) + goto fail; + if (!utf_query_data(acb->BlockSequenceTable, Index, "TrackIndex", &r->TrackIndex_offset, &r->TrackIndex_size)) + goto fail; + if (!utf_query_u16(acb->BlockSequenceTable, Index, "NumBlocks", &r->NumBlocks)) + goto fail; + if (!utf_query_data(acb->BlockSequenceTable, Index, "BlockIndex", &r->BlockIndex_offset, &r->BlockIndex_size)) + goto fail; + //;VGM_LOG("ACB: BlockSequence[%i]: NumTracks=%i, TrackIndex={%x, %x}, NumBlocks=%i, BlockIndex={%x, %x}\n", Index, r->NumTracks, r->TrackIndex_offset,TrackIndex_size, r->NumBlocks, r->BlockIndex_offset, r->BlockIndex_size); + } - if (NumTracks * 0x02 > TrackIndex_size) { /* padding may exist */ + if (r->NumTracks * 0x02 > r->TrackIndex_size) { /* padding may exist */ VGM_LOG("ACB: wrong BlockSequence.TrackIndex size\n"); goto fail; } - /* read Tracks inside BlockSequence */ - for (i = 0; i < NumTracks; i++) { - int16_t TrackIndex_index = read_s16be(TrackIndex_offset + i*0x02, acb->BlockSequenceSf); + if (!read_buffer(acb, r->TrackIndex_offset, r->TrackIndex_size, acb->BlockSequenceSf)) + goto fail; - if (!load_acb_track_event_command(acb, TrackIndex_index)) + /* read Tracks inside BlockSequence */ + for (i = 0; i < r->NumTracks; i++) { + int16_t TrackIndex_index = get_s16be(acb->buf + i*0x02); + + if (!load_acb_track(acb, TrackIndex_index)) goto fail; } - if (NumBlocks * 0x02 > BlockIndex_size) { + if (r->NumBlocks * 0x02 > r->BlockIndex_size) { VGM_LOG("ACB: wrong BlockSequence.BlockIndex size\n"); goto fail; } + if (!read_buffer(acb, r->BlockIndex_offset, r->BlockIndex_size, acb->BlockSequenceSf)) + goto fail; + /* read Blocks inside BlockSequence */ - for (i = 0; i < NumBlocks; i++) { - int16_t BlockIndex_index = read_s16be(BlockIndex_offset + i*0x02, acb->BlockSequenceSf); + for (i = 0; i < r->NumBlocks; i++) { + int16_t BlockIndex_index = get_s16be(acb->buf + i*0x02); if (!load_acb_block(acb, BlockIndex_index)) goto fail; @@ -621,44 +1123,87 @@ static int load_acb_blocksequence(acb_header* acb, int16_t Index) { return 1; fail: + VGM_LOG("ACB: failed BlockSequence %i\n", Index); return 0; } -static int load_acb_cue(acb_header* acb, int16_t Index) { - uint8_t ReferenceType; - uint16_t ReferenceIndex; +/*****************************************************************************/ +static int preload_acb_cue(acb_header* acb) { + utf_context* Table = acb->CueTable; + int* p_rows = &acb->Cue_rows; + int i, c_ReferenceType, c_ReferenceIndex; + + if (*p_rows) + return 1; + if (!open_utf_subtable(acb, &acb->CueSf, &Table, "CueTable", p_rows, ACB_TABLE_BUFFER_CUE)) + goto fail; + if (!*p_rows) + return 1; + ;VGM_LOG("acb: preload Cue=%i\n", *p_rows); + + acb->Cue = malloc(*p_rows * sizeof(Cue_t)); + if (!acb->Cue) goto fail; + + c_ReferenceType = utf_get_column(Table, "ReferenceType"); + c_ReferenceIndex = utf_get_column(Table, "ReferenceIndex"); + + for (i = 0; i < *p_rows; i++) { + Cue_t* r = &acb->Cue[i]; + + utf_query_col_u8(Table, i, c_ReferenceType, &r->ReferenceType); + utf_query_col_u16(Table, i, c_ReferenceIndex, &r->ReferenceIndex); + //;VGM_LOG("ACB: Cue[%i]: ReferenceType=%i, ReferenceIndex=%i\n", i, r->ReferenceType, r->ReferenceIndex); + } + + ;VGM_LOG("acb: preload Cue done\n"); + return 1; +fail: + VGM_LOG("ACB: failed Cue preload\n"); + return 0; +} + +static int load_acb_cue(acb_header* acb, uint16_t Index) { + Cue_t* r; + Cue_t tmp; /* read Cue[Index] */ - if (!open_utf_subtable(acb, &acb->CueSf, &acb->CueTable, "CueTable", NULL, ACB_TABLE_BUFFER_CUE)) - goto fail; - if (!utf_query_u8(acb->CueTable, Index, "ReferenceType", &ReferenceType)) - goto fail; - if (!utf_query_u16(acb->CueTable, Index, "ReferenceIndex", &ReferenceIndex)) - goto fail; - //;VGM_LOG("ACB: Cue[%i]: ReferenceType=%i, ReferenceIndex=%i\n", Index, ReferenceType, ReferenceIndex); + if (acb->is_preload) { + if (!preload_acb_cue(acb)) goto fail; + if (Index > acb->Cue_rows) goto fail; + r = &acb->Cue[Index]; + } + else { + if (!open_utf_subtable(acb, &acb->CueSf, &acb->CueTable, "CueTable", NULL, ACB_TABLE_BUFFER_CUE)) + goto fail; + r = &tmp; + if (!utf_query_u8(acb->CueTable, Index, "ReferenceType", &r->ReferenceType)) + goto fail; + if (!utf_query_u16(acb->CueTable, Index, "ReferenceIndex", &r->ReferenceIndex)) + goto fail; + } /* usually older games use older references but not necessarily */ - switch(ReferenceType) { + switch(r->ReferenceType) { case 0x01: /* Cue > Waveform (ex. PES 2015) */ - if (!load_acb_waveform(acb, ReferenceIndex)) + if (!load_acb_waveform(acb, r->ReferenceIndex)) goto fail; break; case 0x02: /* Cue > Synth > Waveform (ex. Ukiyo no Roushi) */ - if (!load_acb_synth(acb, ReferenceIndex)) + if (!load_acb_synth(acb, r->ReferenceIndex)) goto fail; break; case 0x03: /* Cue > Sequence > Track > Command > Synth > Waveform (ex. Valkyrie Profile anatomia, Yakuza Kiwami 2) */ - if (!load_acb_sequence(acb, ReferenceIndex)) + if (!load_acb_sequence(acb, r->ReferenceIndex)) goto fail; break; case 0x08: /* Cue > BlockSequence > Track / Block > Track > Command > Synth > Waveform (ex. Sonic Lost World, Kandagawa Jet Girls, rare) */ - if (!load_acb_blocksequence(acb, ReferenceIndex)) + if (!load_acb_blocksequence(acb, r->ReferenceIndex)) goto fail; break; @@ -671,43 +1216,89 @@ static int load_acb_cue(acb_header* acb, int16_t Index) { case 0x0a: /* "eventCue_UnUse" */ case 0x0b: /* "soundGenerator" */ default: - VGM_LOG("ACB: unknown Cue.ReferenceType=%x, Cue.ReferenceIndex=%x\n", ReferenceType, ReferenceIndex); + VGM_LOG_ONCE("ACB: unknown Cue.ReferenceType=%x, Cue.ReferenceIndex=%x\n", r->ReferenceType, r->ReferenceIndex); break; /* ignore and continue */ } return 1; fail: + VGM_LOG("ACB: failed Cue %i\n", Index); return 0; } -static int load_acb_cuename(acb_header* acb, int16_t Index) { - uint16_t CueIndex; - const char* CueName; +/*****************************************************************************/ +static int preload_acb_cuename(acb_header* acb) { + utf_context* Table = acb->CueNameTable; + int* p_rows = &acb->CueName_rows; + int i, c_CueIndex, c_CueName; + + + if (*p_rows) + return 1; + if (!open_utf_subtable(acb, &acb->CueNameSf, &Table, "CueNameTable", p_rows, ACB_TABLE_BUFFER_CUENAME)) + goto fail; + if (!*p_rows) + return 1; + ;VGM_LOG("acb: preload CueName=%i\n", *p_rows); + + acb->CueName = malloc(*p_rows * sizeof(CueName_t)); + if (!acb->CueName) goto fail; + + c_CueIndex = utf_get_column(Table, "CueIndex"); + c_CueName = utf_get_column(Table, "CueName"); + + for (i = 0; i < *p_rows; i++) { + CueName_t* r = &acb->CueName[i]; + + utf_query_col_u16(Table, i, c_CueIndex, &r->CueIndex); + utf_query_col_string(Table, i, c_CueName, &r->CueName); + //;VGM_LOG("ACB: CueName[%i]: CueIndex=%i, CueName=%s\n", i, r->CueIndex, r->CueName); + } + + ;VGM_LOG("acb: preload CueName done\n"); + return 1; +fail: + VGM_LOG("ACB: failed CueName preload\n"); + return 0; +} + +static int load_acb_cuename(acb_header* acb, uint16_t Index) { + CueName_t* r; + CueName_t tmp; /* read CueName[Index] */ - if (!open_utf_subtable(acb, &acb->CueNameSf, &acb->CueNameTable, "CueNameTable", NULL, ACB_TABLE_BUFFER_CUENAME)) - goto fail; - if (!utf_query_u16(acb->CueNameTable, Index, "CueIndex", &CueIndex)) - goto fail; - if (!utf_query_string(acb->CueNameTable, Index, "CueName", &CueName)) - goto fail; - //;VGM_LOG("ACB: CueName[%i]: CueIndex=%i, CueName=%s\n", Index, CueIndex, CueName); + if (acb->is_preload) { + if (!preload_acb_cuename(acb)) goto fail; + if (Index > acb->CueName_rows) goto fail; + r = &acb->CueName[Index]; + } + else { + if (!open_utf_subtable(acb, &acb->CueNameSf, &acb->CueNameTable, "CueNameTable", NULL, ACB_TABLE_BUFFER_CUENAME)) + goto fail; + r = &tmp; + if (!utf_query_u16(acb->CueNameTable, Index, "CueIndex", &r->CueIndex)) + goto fail; + if (!utf_query_string(acb->CueNameTable, Index, "CueName", &r->CueName)) + goto fail; + } /* save as will be needed if references waveform */ acb->cuename_index = Index; - acb->cuename_name = CueName; + acb->cuename_name = r->CueName; - if (!load_acb_cue(acb, CueIndex)) + if (!load_acb_cue(acb, r->CueIndex)) goto fail; return 1; fail: + VGM_LOG("ACB: failed CueName %i\n", Index); return 0; } +/*****************************************************************************/ void load_acb_wave_name(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int port, int is_memory) { acb_header acb = {0}; @@ -740,6 +1331,9 @@ void load_acb_wave_name(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int po * * .acb can contain info for multiple .awb, that are loaded sequentially and assigned "port numbers" (0 to N). * Both Wave ID and port number must be passed externally to find appropriate song name. + * + * To improve performance we pre-read each table objects's useful fields. Extra complex files may include +8000 objects, + * per table, meaning it uses a decent chunk of memory, but having to re-read with streamfiles is much slower. */ //;VGM_LOG("ACB: find waveid=%i, port=%i\n", waveid, port); @@ -752,15 +1346,20 @@ void load_acb_wave_name(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int po acb.target_waveid = waveid; acb.target_port = port; acb.is_memory = is_memory; + + +#ifdef ACB_PRELOAD + acb.is_preload = 1; +#else acb.has_TrackEventTable = utf_query_data(acb.Header, 0, "TrackEventTable", NULL,NULL); acb.has_CommandTable = utf_query_data(acb.Header, 0, "CommandTable", NULL,NULL); +#endif - + //todo preload cuename table /* read all possible cue names and find which waveids are referenced by it */ - if (!open_utf_subtable(&acb, &acb.CueNameSf, &acb.CueNameTable, "CueNameTable", &CueName_rows, ACB_TABLE_BUFFER_CUENAME)) + if (!open_utf_subtable(&acb, &acb.TempSf, &acb.TempTable, "CueNameTable", &CueName_rows, ACB_TABLE_BUFFER_CUENAME)) goto fail; for (i = 0; i < CueName_rows; i++) { - if (!load_acb_cuename(&acb, i)) goto fail; } @@ -774,6 +1373,9 @@ void load_acb_wave_name(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int po fail: utf_close(acb.Header); + utf_close(acb.TempTable); + close_streamfile(acb.TempSf); + utf_close(acb.CueNameTable); utf_close(acb.CueTable); utf_close(acb.BlockSequenceTable); @@ -793,4 +1395,16 @@ fail: close_streamfile(acb.TrackCommandSf); close_streamfile(acb.SynthSf); close_streamfile(acb.WaveformSf); + + free(acb.buf); + + free(acb.CueName); + free(acb.Cue); + free(acb.BlockSequence); + free(acb.Block); + free(acb.Sequence); + free(acb.Track); + free(acb.TrackCommand); + free(acb.Synth); + free(acb.Waveform); } diff --git a/src/meta/awb.c b/src/meta/awb.c index 834fe274..7cff3061 100644 --- a/src/meta/awb.c +++ b/src/meta/awb.c @@ -1,7 +1,7 @@ #include "meta.h" #include "../coding/coding.h" -typedef enum { ADX, HCA, VAG, RIFF, CWAV, DSP, CWAC, M4A } awb_type; +//typedef enum { ADX, HCA, VAG, RIFF, CWAV, DSP, CWAC, M4A } awb_type_t; static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid); @@ -13,26 +13,21 @@ VGMSTREAM* init_vgmstream_awb(STREAMFILE* sf) { VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) { VGMSTREAM* vgmstream = NULL; STREAMFILE* temp_sf = NULL; - off_t offset, subfile_offset, subfile_next; - size_t subfile_size; + uint32_t offset, subfile_offset, subfile_next, subfile_size; int total_subsongs, target_subsong = sf->stream_index; - //uint32_t flags; uint8_t offset_size; uint16_t alignment, subkey; - awb_type type; - const char* extension = NULL; int waveid; - /* checks - * .awb: standard + /* checks */ + if (!is_id32be(0x00,sf, "AFS2")) + goto fail; + /* .awb: standard * .afs2: sometimes [Okami HD (PS4)] */ if (!check_extensions(sf, "awb,afs2")) goto fail; - if (read_u32be(0x00,sf) != 0x41465332) /* "AFS2" */ - goto fail; - //flags = read_32bitLE(0x08,sf); /* 0x04(1): version? 0x01=common, 0x02=2018+ (no apparent differences) */ offset_size = read_u8(0x05,sf); /* 0x06(2): always 0x0002? */ @@ -45,9 +40,9 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) { offset = 0x10; - /* id(?) table: read target */ + /* id table: read target */ { - off_t waveid_offset = offset + (target_subsong-1) * 0x02; + uint32_t waveid_offset = offset + (target_subsong-1) * 0x02; waveid = read_u16le(waveid_offset,sf); @@ -56,7 +51,7 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) { /* offset table: find target */ { - off_t file_size = get_streamfile_size(sf); + uint32_t file_size = get_streamfile_size(sf); /* last sub-offset is always file end, so table entries = total_subsongs+1 */ offset += (target_subsong-1) * offset_size; @@ -71,7 +66,7 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) { subfile_next = read_u16le(offset+0x02,sf); break; default: - VGM_LOG("AWB: unknown offset size\n"); + vgm_logi("AWB: unknown offset size (report)\n"); goto fail; } @@ -83,97 +78,70 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) { subfile_size = subfile_next - subfile_offset; } - //;VGM_LOG("AWB: subfile offset=%lx + %x\n", subfile_offset, subfile_size); + //;VGM_LOG("awb: subfile offset=%x + %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 (read_u16be(subfile_offset, sf) == 0x8000) { /* ADX id (type 0) */ - type = ADX; - extension = "adx"; - } - else if ((read_u32be(subfile_offset,sf) & 0x7f7f7f7f) == 0x48434100) { /* "HCA\0" (type 2=HCA, 6=HCA-MX) */ - type = HCA; - extension = "hca"; - } - else if (read_u32be(subfile_offset,sf) == 0x56414770) { /* "VAGp" (type 7=VAG, 10=HEVAG) */ - type = VAG; - extension = "vag"; - } - else if (read_u32be(subfile_offset,sf) == 0x52494646) { /* "RIFF" (type 8=ATRAC3, 11=ATRAC9) */ - type = RIFF; - extension = "wav"; - subfile_size = read_u32le(subfile_offset + 0x04,sf) + 0x08; /* rough size, use RIFF's */ - } - else if (read_u32be(subfile_offset,sf) == 0x43574156) { /* "CWAV" (type 9) */ - type = CWAV; - extension = "bcwav"; - } - else if (read_u32be(subfile_offset + 0x08,sf) >= 8000 && - read_u32be(subfile_offset + 0x08,sf) <= 48000 && - read_u16be(subfile_offset + 0x0e,sf) == 0 && - read_u32be(subfile_offset + 0x18,sf) == 2 && - read_u32be(subfile_offset + 0x50,sf) == 0) { /* probably should call some check function (type 13) */ - type = DSP; - extension = "dsp"; - } - else if (is_id32be(subfile_offset,sf, "CWAC")) { /* type 13 again */ - type = CWAC; - extension = "dsp"; - } - else if (read_u32be(subfile_offset+0x00,sf) == 0x00000018 && - read_u32be(subfile_offset+0x04,sf) == 0x66747970) { /* chunk size + "ftyp" (type 19) */ - type = M4A; - extension = "m4a"; - } - else { - VGM_LOG("AWB: unknown codec\n"); - goto fail; - } + { + VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf) = NULL; + VGMSTREAM* (*init_vgmstream_subkey)(STREAMFILE* sf, uint16_t subkey) = NULL; + const char* extension = NULL; - - temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, extension); - if (!temp_sf) goto fail; - - switch(type) { - case HCA: /* most common */ - vgmstream = init_vgmstream_hca_subkey(temp_sf, subkey); - if (!vgmstream) goto fail; - break; - case ADX: /* Okami HD (PS4) */ - vgmstream = init_vgmstream_adx_subkey(temp_sf, subkey); - if (!vgmstream) goto fail; - break; - case VAG: /* Ukiyo no Roushi (Vita) */ - vgmstream = init_vgmstream_vag(temp_sf); - if (!vgmstream) goto fail; - break; - case RIFF: /* Ukiyo no Roushi (Vita) */ - vgmstream = init_vgmstream_riff(temp_sf); - if (!vgmstream) goto fail; - break; - case CWAV: /* Sonic: Lost World (3DS) */ - vgmstream = init_vgmstream_rwsd(temp_sf); - if (!vgmstream) goto fail; - break; - case DSP: /* Sonic: Lost World (WiiU) */ - vgmstream = init_vgmstream_ngc_dsp_std(temp_sf); - if (!vgmstream) goto fail; - break; - case CWAC: /* Mario & Sonic at the Rio 2016 Olympic Games (WiiU) */ - vgmstream = init_vgmstream_dsp_cwac(temp_sf); - if (!vgmstream) goto fail; - break; + if (read_u16be(subfile_offset, sf) == 0x8000) { /* (type 0=ADX) */ + init_vgmstream_subkey = init_vgmstream_adx_subkey; /* Okami HD (PS4) */ + extension = "adx"; + } + else if ((read_u32be(subfile_offset,sf) & 0x7f7f7f7f) == get_id32be("HCA\0")) { /* (type 2=HCA, 6=HCA-MX) */ + init_vgmstream_subkey = init_vgmstream_hca_subkey; /* most common */ + extension = "hca"; + } + else if (is_id32be(subfile_offset,sf, "VAGp") == 0x56414770) { /* (type 7=VAG, 10=HEVAG) */ + init_vgmstream = init_vgmstream_vag; /* Ukiyo no Roushi (Vita) */ + extension = "vag"; + } + else if (is_id32be(subfile_offset,sf, "RIFF")) { /* (type 8=ATRAC3, 11=ATRAC9) */ + init_vgmstream = init_vgmstream_riff; /* Ukiyo no Roushi (Vita) */ + extension = "wav"; + subfile_size = read_u32le(subfile_offset + 0x04,sf) + 0x08; /* padded size, use RIFF's */ + } + else if (is_id32be(subfile_offset,sf, "CWAV")) { /* (type 9=CWAV) */ + init_vgmstream = init_vgmstream_rwsd; /* Sonic: Lost World (3DS) */ + extension = "bcwav"; + } + else if (read_u32be(subfile_offset + 0x08,sf) >= 8000 && read_u32be(subfile_offset + 0x08,sf) <= 48000 && + read_u16be(subfile_offset + 0x0e,sf) == 0 && + read_u32be(subfile_offset + 0x18,sf) == 2 && + read_u32be(subfile_offset + 0x50,sf) == 0) { /* (type 13=DSP), probably should call some check function */ + init_vgmstream = init_vgmstream_ngc_dsp_std; /* Sonic: Lost World (WiiU) */ + extension = "dsp"; + } + else if (is_id32be(subfile_offset,sf, "CWAC")) { /* (type 13=DSP, again) */ + init_vgmstream = init_vgmstream_dsp_cwac; /* Mario & Sonic at the Rio 2016 Olympic Games (WiiU) */ + extension = "dsp"; + } #ifdef VGM_USE_FFMPEG - case M4A: /* Imperial SaGa Eclipse (Browser) */ - vgmstream = init_vgmstream_mp4_aac_ffmpeg(temp_sf); - if (!vgmstream) goto fail; - break; + else if (read_u32be(subfile_offset+0x00,sf) == 0x00000018 && is_id32be(subfile_offset+0x04,sf, "ftyp")) { /* (type 19=M4A) */ + init_vgmstream = init_vgmstream_mp4_aac_ffmpeg; /* Imperial SaGa Eclipse (Browser) */ + extension = "m4a"; + } #endif - default: + else { + vgm_logi("AWB: unknown codec (report)\n"); goto fail; - } + } - vgmstream->num_streams = total_subsongs; + + temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, extension); + if (!temp_sf) goto fail; + + if (init_vgmstream_subkey) + vgmstream = init_vgmstream_subkey(temp_sf, subkey); + else + vgmstream = init_vgmstream(temp_sf); + if (!vgmstream) goto fail; + + vgmstream->num_streams = total_subsongs; + } /* try to load cue names */ load_awb_name(sf, sf_acb, vgmstream, waveid); diff --git a/src/meta/cri_utf.c b/src/meta/cri_utf.c index e2192916..a2919bec 100644 --- a/src/meta/cri_utf.c +++ b/src/meta/cri_utf.c @@ -1,14 +1,15 @@ #include "cri_utf.h" #include "../util/log.h" +#define UTF_MAX_SCHEMA_SIZE 0x8000 /* arbitrary max */ #define COLUMN_BITMASK_FLAG 0xf0 #define COLUMN_BITMASK_TYPE 0x0f enum columna_flag_t { - COLUMN_FLAG_NAME = 0x10, - COLUMN_FLAG_DEFAULT = 0x20, - COLUMN_FLAG_ROW = 0x40, - COLUMN_FLAG_UNDEFINED = 0x80 /* shouldn't exist */ + COLUMN_FLAG_NAME = 0x10, /* column has name (may be empty) */ + COLUMN_FLAG_DEFAULT = 0x20, /* data is found relative to schema start (typically constant value for all rows) */ + COLUMN_FLAG_ROW = 0x40, /* data is found relative to row start */ + COLUMN_FLAG_UNDEFINED = 0x80 /* shouldn't exist */ }; enum column_type_t { @@ -28,34 +29,8 @@ enum column_type_t { COLUMN_TYPE_UNDEFINED = -1 }; -typedef struct { - int found; - enum column_type_t 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; - struct utf_data_t { - uint32_t offset; - uint32_t size; - } value_data; - //struct utf_u128_t { - // uint64_t hi; - // uint64_t lo; - //} value_u128; - const char *value_string; - } value; -} utf_result_t; - struct utf_context { - STREAMFILE *sf; + STREAMFILE* sf; uint32_t table_offset; /* header */ @@ -68,25 +43,31 @@ struct utf_context { uint16_t columns; uint16_t row_width; uint32_t rows; + + uint8_t* schema_buf; struct utf_column_t { uint8_t flag; uint8_t type; - const char *name; + const char* name; uint32_t offset; } *schema; /* derived */ uint32_t schema_offset; + uint32_t schema_size; + uint32_t rows_size; + uint32_t data_size; uint32_t strings_size; - char *string_table; - const char *table_name; + char* string_table; + const char* table_name; }; /* @UTF table context creation */ utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const char** p_row_name) { utf_context* utf = NULL; - + uint8_t buf[0x20]; + int bytes; utf = calloc(1, sizeof(utf_context)); if (!utf) goto fail; @@ -94,27 +75,33 @@ utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const utf->sf = sf; utf->table_offset = table_offset; - /* check header */ - if (read_u32be(table_offset + 0x00, sf) != 0x40555446) /* "@UTF" */ - goto fail; + bytes = read_streamfile(buf, table_offset, sizeof(buf), sf); + if (bytes != sizeof(buf)) goto fail; /* load table header */ - utf->table_size = read_u32be(table_offset + 0x04, sf) + 0x08; - utf->version = read_u16be(table_offset + 0x08, sf); - utf->rows_offset = read_u16be(table_offset + 0x0a, sf) + 0x08; - utf->strings_offset = read_u32be(table_offset + 0x0c, sf) + 0x08; - utf->data_offset = read_u32be(table_offset + 0x10, sf) + 0x08; - utf->name_offset = read_u32be(table_offset + 0x14, sf); /* within string table */ - utf->columns = read_u16be(table_offset + 0x18, sf); - utf->row_width = read_u16be(table_offset + 0x1a, sf); - utf->rows = read_u32be(table_offset + 0x1c, sf); + if (get_u32be(buf + 0x00) != get_id32be("@UTF")) + goto fail; + utf->table_size = get_u32be(buf + 0x04) + 0x08; + utf->version = get_u16be(buf + 0x08); + utf->rows_offset = get_u16be(buf + 0x0a) + 0x08; + utf->strings_offset = get_u32be(buf + 0x0c) + 0x08; + utf->data_offset = get_u32be(buf + 0x10) + 0x08; + utf->name_offset = get_u32be(buf + 0x14); /* within string table */ + utf->columns = get_u16be(buf + 0x18); + utf->row_width = get_u16be(buf + 0x1a); + utf->rows = get_u32be(buf + 0x1c); + + utf->schema_offset = 0x20; + utf->schema_size = utf->rows_offset - utf->schema_offset; + utf->rows_size = utf->strings_offset - utf->rows_offset; + utf->strings_size = utf->data_offset - utf->strings_offset; + utf->data_size = utf->table_size - utf->data_offset; - 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_logi("@UTF: unknown version\n"); + goto fail; } if (utf->table_offset + utf->table_size > get_streamfile_size(sf)) goto fail; @@ -125,39 +112,48 @@ utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const /* no rows is possible for empty tables (have schema and columns names but no data) [PES 2013 (PC)] */ if (utf->columns <= 0 /*|| utf->rows <= 0 || utf->rows_width <= 0*/) goto fail; + if (utf->schema_size >= UTF_MAX_SCHEMA_SIZE) + goto fail; - - /* load string table */ + /* load sections linearly (to optimize stream) */ { - size_t read; + /* schema section: small so keep it around (useful to avoid re-reads on column values) */ + utf->schema_buf = malloc(utf->schema_size); + if (!utf->schema_buf) goto fail; + bytes = read_streamfile(utf->schema_buf, utf->table_offset + utf->schema_offset, utf->schema_size, sf); + if (bytes != utf->schema_size) goto fail; + + /* row section: skip, mid to big (0x10000~0x50000) so not preloaded for now */ + + /* string section: low to mid size but used to return c-strings */ 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; + bytes = read_streamfile((unsigned char*)utf->string_table, utf->table_offset + utf->strings_offset, utf->strings_size, sf); + if (bytes != utf->strings_size) goto fail; - read = read_streamfile((unsigned char*)utf->string_table, utf->table_offset + utf->strings_offset, utf->strings_size, sf); - if (utf->strings_size != read) goto fail; + /* data section: skip (may be big with memory AWB) */ } - /* load column schema */ { int i; uint32_t value_size, column_offset = 0; - uint32_t schema_offset = utf->table_offset + utf->schema_offset; + int schema_pos = 0; + utf->table_name = utf->string_table + utf->name_offset; - utf->schema = malloc(sizeof(struct utf_column_t) * utf->columns); + utf->schema = malloc(utf->columns * sizeof(struct utf_column_t)); if (!utf->schema) goto fail; for (i = 0; i < utf->columns; i++) { - uint8_t info = read_u8(schema_offset + 0x00, sf); - uint32_t name_offset = read_u32be(schema_offset + 0x01, sf); + uint8_t info = get_u8(utf->schema_buf + schema_pos + 0x00); + uint32_t name_offset = get_u32be(utf->schema_buf + schema_pos + 0x01); + if (name_offset > utf->strings_size) goto fail; - schema_offset += 0x01 + 0x04; - + schema_pos += 0x01 + 0x04; utf->schema[i].flag = info & COLUMN_BITMASK_FLAG; utf->schema[i].type = info & COLUMN_BITMASK_TYPE; @@ -165,7 +161,7 @@ utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const utf->schema[i].offset = 0; /* known flags are name+default or name+row, but name+default+row is mentioned in VGMToolbox - * even though isn't possible in CRI's craft utils, and no name is apparently possible */ + * even though isn't possible in CRI's craft utils (meaningless), and no name is apparently possible */ if ( (utf->schema[i].flag == 0) || !(utf->schema[i].flag & COLUMN_FLAG_NAME) || ((utf->schema[i].flag & COLUMN_FLAG_DEFAULT) && (utf->schema[i].flag & COLUMN_FLAG_ROW)) || @@ -207,20 +203,25 @@ utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const } if (utf->schema[i].flag & COLUMN_FLAG_DEFAULT) { - /* data is found relative to schema start */ - utf->schema[i].offset = schema_offset - (utf->table_offset + utf->schema_offset); - schema_offset += value_size; + utf->schema[i].offset = schema_pos; + schema_pos += value_size; } if (utf->schema[i].flag & COLUMN_FLAG_ROW) { - /* data is found relative to row start */ utf->schema[i].offset = column_offset; column_offset += value_size; } } } - /* next section is row and variable length data (pointed above) then end of table */ +#if 0 + VGM_LOG("- %s\n", utf->table_name); + VGM_LOG("utf_o=%08x (%x)\n", utf->table_offset, utf->table_size); + VGM_LOG(" sch_o=%08x (%x), c=%i\n", utf->table_offset + utf->schema_offset, utf->schema_size, utf->columns); + VGM_LOG(" row_o=%08x (%x), r=%i\n", utf->table_offset + utf->rows_offset, utf->rows_size, utf->rows); + VGM_LOG(" str_o=%08x (%x)\n", utf->table_offset + utf->strings_offset, utf->strings_size); + VGM_LOG(" dat_o=%08x (%x))\n", utf->table_offset + utf->data_offset, utf->data_size); +#endif /* write info */ if (p_rows) *p_rows = utf->rows; @@ -237,107 +238,139 @@ void utf_close(utf_context* utf) { if (!utf) return; free(utf->string_table); + free(utf->schema_buf); free(utf->schema); free(utf); } -static int utf_query(utf_context* utf, int row, const char* column, utf_result_t* result) { +int utf_get_column(utf_context* utf, const char* column) { int i; - - result->found = 0; - - if (row >= utf->rows || row < 0) - goto fail; - /* find target column */ for (i = 0; i < utf->columns; i++) { - struct utf_column_t *col = &utf->schema[i]; - uint32_t data_offset; + struct utf_column_t* col = &utf->schema[i]; if (col->name == NULL || strcmp(col->name, column) != 0) continue; + return i; + } + + return -1; +} + +typedef struct { + enum column_type_t type; + union { + int8_t s8; + uint8_t u8; + int16_t s16; + uint16_t u16; + int32_t s32; + uint32_t u32; + int64_t s64; + uint64_t u64; + float flt; + double dbl; + struct utf_data_t { + uint32_t offset; + uint32_t size; + } data; +#if 0 + struct utf_u128_t { + uint64_t hi; + uint64_t lo; + } value_u128; +#endif + const char* str; + } value; +} utf_result_t; + +static int utf_query(utf_context* utf, int row, int column, utf_result_t* result) { + + if (row >= utf->rows || row < 0) + goto fail; + if (column >= utf->columns || column < 0) + goto fail; + + /* get target column */ + { + struct utf_column_t* col = &utf->schema[column]; + uint32_t data_offset = 0; + uint8_t* buf = NULL; - result->found = 1; result->type = col->type; if (col->flag & COLUMN_FLAG_DEFAULT) { - data_offset = utf->table_offset + utf->schema_offset + col->offset; + if (utf->schema_buf) + buf = utf->schema_buf + col->offset; + else + data_offset = utf->table_offset + utf->schema_offset + col->offset; } else if (col->flag & COLUMN_FLAG_ROW) { data_offset = utf->table_offset + utf->rows_offset + row * utf->row_width + col->offset; } else { - data_offset = 0; + /* shouldn't happen */ + memset(&result->value, 0, sizeof(result->value)); + return 1; /* ??? */ } - /* ignore zero value */ - if (data_offset == 0) { - memset(&result->value, 0, sizeof(result->value)); /* just in case... */ - break; - } - /* read row/constant value */ + /* read row/constant value (use buf if available) */ switch (col->type) { case COLUMN_TYPE_UINT8: - result->value.value_u8 = read_u8(data_offset, utf->sf); + result->value.u8 = buf ? get_u8(buf) : read_u8(data_offset, utf->sf); break; case COLUMN_TYPE_SINT8: - result->value.value_s8 = read_s8(data_offset, utf->sf); + result->value.s8 = buf ? get_s8(buf) : read_s8(data_offset, utf->sf); break; case COLUMN_TYPE_UINT16: - result->value.value_u16 = read_u16be(data_offset, utf->sf); + result->value.u16 = buf ? get_u16be(buf) : read_u16be(data_offset, utf->sf); break; case COLUMN_TYPE_SINT16: - result->value.value_s16 = read_s16be(data_offset, utf->sf); + result->value.s16 = buf ? get_s16be(buf) : read_s16be(data_offset, utf->sf); break; case COLUMN_TYPE_UINT32: - result->value.value_u32 = read_u32be(data_offset, utf->sf); + result->value.u32 = buf ? get_u32be(buf) : read_u32be(data_offset, utf->sf); break; case COLUMN_TYPE_SINT32: - result->value.value_s32 = read_s32be(data_offset, utf->sf); + result->value.s32 = buf ? get_s32be(buf) : read_s32be(data_offset, utf->sf); break; case COLUMN_TYPE_UINT64: - result->value.value_u64 = read_u64be(data_offset, utf->sf); + result->value.u64 = buf ? get_u64be(buf) : read_u64be(data_offset, utf->sf); break; case COLUMN_TYPE_SINT64: - result->value.value_s64 = read_s64be(data_offset, utf->sf); + result->value.s64 = buf ? get_s64be(buf) : read_s64be(data_offset, utf->sf); break; - case COLUMN_TYPE_FLOAT: { - result->value.value_float = read_f32be(data_offset, utf->sf); + case COLUMN_TYPE_FLOAT: + result->value.flt = buf ? get_f32be(buf) : read_f32be(data_offset, utf->sf); break; - } #if 0 - case COLUMN_TYPE_DOUBLE: { - result->value.value_double = read_d64be(data_offset, utf->sf); + case COLUMN_TYPE_DOUBLE: + result->value.dbl = buf ? get_d64be(buf) : read_d64be(data_offset, utf->sf); break; - } #endif case COLUMN_TYPE_STRING: { - uint32_t name_offset = read_u32be(data_offset, utf->sf); + uint32_t name_offset = buf ? get_u32be(buf) : read_u32be(data_offset, utf->sf); if (name_offset > utf->strings_size) goto fail; - result->value.value_string = utf->string_table + name_offset; + result->value.str = utf->string_table + name_offset; break; } - case COLUMN_TYPE_VLDATA: - result->value.value_data.offset = read_u32be(data_offset + 0x00, utf->sf); - result->value.value_data.size = read_u32be(data_offset + 0x04, utf->sf); + result->value.data.offset = buf ? get_u32be(buf + 0x0) : read_u32be(data_offset + 0x00, utf->sf); + result->value.data.size = buf ? get_u32be(buf + 0x4) : read_u32be(data_offset + 0x04, utf->sf); break; #if 0 - case COLUMN_TYPE_UINT128: { - result->value.value_u128.hi = read_u64be(data_offset + 0x00, utf->sf); - result->value.value_u128.lo = read_u64be(data_offset + 0x08, utf->sf); + case COLUMN_TYPE_UINT128: + result->value.value_u128.hi = buf ? get_u32be(buf + 0x0) : read_u64be(data_offset + 0x00, utf->sf); + result->value.value_u128.lo = buf ? get_u32be(buf + 0x4) : read_u64be(data_offset + 0x08, utf->sf); break; - } #endif default: goto fail; } - - break; /* column found and read */ } return 1; @@ -345,24 +378,24 @@ fail: return 0; } -static int utf_query_value(utf_context* utf, int row, const char* column, void* value, enum column_type_t type) { +static int utf_query_value(utf_context* utf, int row, int column, void* value, enum column_type_t type) { utf_result_t result = {0}; int valid; valid = utf_query(utf, row, column, &result); - if (!valid || !result.found || result.type != type) + if (!valid || result.type != type) return 0; switch(result.type) { - case COLUMN_TYPE_UINT8: (*(uint8_t*)value) = result.value.value_u8; break; - case COLUMN_TYPE_SINT8: (*(int8_t*)value) = result.value.value_s8; break; - case COLUMN_TYPE_UINT16: (*(uint16_t*)value) = result.value.value_u16; break; - case COLUMN_TYPE_SINT16: (*(int16_t*)value) = result.value.value_s16; break; - case COLUMN_TYPE_UINT32: (*(uint32_t*)value) = result.value.value_u32; break; - case COLUMN_TYPE_SINT32: (*(int32_t*)value) = result.value.value_s32; break; - case COLUMN_TYPE_UINT64: (*(uint64_t*)value) = result.value.value_u64; break; - case COLUMN_TYPE_SINT64: (*(int64_t*)value) = result.value.value_s64; break; - case COLUMN_TYPE_STRING: (*(const char**)value) = result.value.value_string; break; + case COLUMN_TYPE_UINT8: (*(uint8_t*)value) = result.value.u8; break; + case COLUMN_TYPE_SINT8: (*(int8_t*)value) = result.value.s8; break; + case COLUMN_TYPE_UINT16: (*(uint16_t*)value) = result.value.u16; break; + case COLUMN_TYPE_SINT16: (*(int16_t*)value) = result.value.s16; break; + case COLUMN_TYPE_UINT32: (*(uint32_t*)value) = result.value.u32; break; + case COLUMN_TYPE_SINT32: (*(int32_t*)value) = result.value.s32; break; + case COLUMN_TYPE_UINT64: (*(uint64_t*)value) = result.value.u64; break; + case COLUMN_TYPE_SINT64: (*(int64_t*)value) = result.value.s64; break; + case COLUMN_TYPE_STRING: (*(const char**)value) = result.value.str; break; default: return 0; } @@ -370,43 +403,76 @@ static int utf_query_value(utf_context* utf, int row, const char* column, void* return 1; } -int utf_query_s8(utf_context* utf, int row, const char* column, int8_t* value) { +int utf_query_col_s8(utf_context* utf, int row, int column, int8_t* value) { return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_SINT8); } -int utf_query_u8(utf_context* utf, int row, const char* column, uint8_t* value) { +int utf_query_col_u8(utf_context* utf, int row, int column, uint8_t* value) { return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_UINT8); } -int utf_query_s16(utf_context* utf, int row, const char* column, int16_t* value) { +int utf_query_col_s16(utf_context* utf, int row, int column, int16_t* value) { return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_SINT16); } -int utf_query_u16(utf_context* utf, int row, const char* column, uint16_t* value) { +int utf_query_col_u16(utf_context* utf, int row, int column, uint16_t* value) { return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_UINT16); } -int utf_query_s32(utf_context* utf, int row, const char* column, int32_t* value) { +int utf_query_col_s32(utf_context* utf, int row, int column, int32_t* value) { return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_SINT32); } -int utf_query_u32(utf_context* utf, int row, const char* column, uint32_t* value) { +int utf_query_col_u32(utf_context* utf, int row, int column, uint32_t* value) { return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_UINT32); } -int utf_query_s64(utf_context* utf, int row, const char* column, int64_t* value) { +int utf_query_col_s64(utf_context* utf, int row, int column, int64_t* value) { return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_SINT64); } -int utf_query_u64(utf_context* utf, int row, const char* column, uint64_t* value) { +int utf_query_col_u64(utf_context* utf, int row, int column, uint64_t* value) { return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_UINT64); } -int utf_query_string(utf_context* utf, int row, const char* column, const char** value) { +int utf_query_col_string(utf_context* utf, int row, int column, const char** value) { return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_STRING); } -int utf_query_data(utf_context* utf, int row, const char* column, uint32_t* p_offset, uint32_t* p_size) { +int utf_query_col_data(utf_context* utf, int row, int column, uint32_t* p_offset, uint32_t* p_size) { utf_result_t result = {0}; int valid; valid = utf_query(utf, row, column, &result); - if (!valid || !result.found || result.type != COLUMN_TYPE_VLDATA) + if (!valid || result.type != COLUMN_TYPE_VLDATA) return 0; - if (p_offset) *p_offset = utf->table_offset + utf->data_offset + result.value.value_data.offset; - if (p_size) *p_size = result.value.value_data.size; + if (p_offset) *p_offset = utf->table_offset + utf->data_offset + result.value.data.offset; + if (p_size) *p_size = result.value.data.size; return 1; } + + +int utf_query_s8(utf_context* utf, int row, const char* column_name, int8_t* value) { + return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_SINT8); +} +int utf_query_u8(utf_context* utf, int row, const char* column_name, uint8_t* value) { + return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_UINT8); +} +int utf_query_s16(utf_context* utf, int row, const char* column_name, int16_t* value) { + return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_SINT16); +} +int utf_query_u16(utf_context* utf, int row, const char* column_name, uint16_t* value) { + return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_UINT16); +} +int utf_query_s32(utf_context* utf, int row, const char* column_name, int32_t* value) { + return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_SINT32); +} +int utf_query_u32(utf_context* utf, int row, const char* column_name, uint32_t* value) { + return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_UINT32); +} +int utf_query_s64(utf_context* utf, int row, const char* column_name, int64_t* value) { + return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_SINT64); +} +int utf_query_u64(utf_context* utf, int row, const char* column_name, uint64_t* value) { + return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_UINT64); +} +int utf_query_string(utf_context* utf, int row, const char* column_name, const char** value) { + return utf_query_value(utf, row, utf_get_column(utf, column_name), (void*)value, COLUMN_TYPE_STRING); +} + +int utf_query_data(utf_context* utf, int row, const char* column_name, uint32_t* p_offset, uint32_t* p_size) { + return utf_query_col_data(utf, row, utf_get_column(utf, column_name), p_offset, p_size); +} diff --git a/src/meta/cri_utf.h b/src/meta/cri_utf.h index 6e7ec59d..3f73afc3 100644 --- a/src/meta/cri_utf.h +++ b/src/meta/cri_utf.h @@ -23,16 +23,30 @@ typedef struct utf_context utf_context; /* open a CRI UTF table at offset, returning table name and rows. Passed streamfile is used internally for next calls */ utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const char** p_row_name); void utf_close(utf_context* utf); -/* query calls */ -int utf_query_s8(utf_context* utf, int row, const char* column, int8_t* value); -int utf_query_u8(utf_context* utf, int row, const char* column, uint8_t* value); -int utf_query_s16(utf_context* utf, int row, const char* column, int16_t* value); -int utf_query_u16(utf_context* utf, int row, const char* column, uint16_t* value); -int utf_query_s32(utf_context* utf, int row, const char* column, int32_t* value); -int utf_query_u32(utf_context* utf, int row, const char* column, uint32_t* value); -int utf_query_s64(utf_context* utf, int row, const char* column, int64_t* value); -int utf_query_u64(utf_context* utf, int row, const char* column, uint64_t* value); -int utf_query_string(utf_context* utf, int row, const char* column, const char** value); -int utf_query_data(utf_context* utf, int row, const char* column, uint32_t* offset, uint32_t* size); + +int utf_get_column(utf_context* utf, const char* column); + +/* query calls (passing column index is faster, when you have to read lots of rows) */ +int utf_query_col_s8(utf_context* utf, int row, int column, int8_t* value); +int utf_query_col_u8(utf_context* utf, int row, int column, uint8_t* value); +int utf_query_col_s16(utf_context* utf, int row, int column, int16_t* value); +int utf_query_col_u16(utf_context* utf, int row, int column, uint16_t* value); +int utf_query_col_s32(utf_context* utf, int row, int column, int32_t* value); +int utf_query_col_u32(utf_context* utf, int row, int column, uint32_t* value); +int utf_query_col_s64(utf_context* utf, int row, int column, int64_t* value); +int utf_query_col_u64(utf_context* utf, int row, int column, uint64_t* value); +int utf_query_col_string(utf_context* utf, int row, int column, const char** value); +int utf_query_col_data(utf_context* utf, int row, int column, uint32_t* offset, uint32_t* size); + +int utf_query_s8(utf_context* utf, int row, const char* column_name, int8_t* value); +int utf_query_u8(utf_context* utf, int row, const char* column_name, uint8_t* value); +int utf_query_s16(utf_context* utf, int row, const char* column_name, int16_t* value); +int utf_query_u16(utf_context* utf, int row, const char* column_name, uint16_t* value); +int utf_query_s32(utf_context* utf, int row, const char* column_name, int32_t* value); +int utf_query_u32(utf_context* utf, int row, const char* column_name, uint32_t* value); +int utf_query_s64(utf_context* utf, int row, const char* column_name, int64_t* value); +int utf_query_u64(utf_context* utf, int row, const char* column_name, uint64_t* value); +int utf_query_string(utf_context* utf, int row, const char* column_name, const char** value); +int utf_query_data(utf_context* utf, int row, const char* column_name, uint32_t* offset, uint32_t* size); #endif /* _CRI_UTF_H_ */ diff --git a/src/meta/ktsr.c b/src/meta/ktsr.c index acecbf5f..fff43b92 100644 --- a/src/meta/ktsr.c +++ b/src/meta/ktsr.c @@ -19,15 +19,15 @@ typedef struct { int32_t num_samples; int32_t loop_start; int loop_flag; - off_t extra_offset; + uint32_t extra_offset; uint32_t channel_layout; int is_external; uint32_t stream_offsets[MAX_CHANNELS]; uint32_t stream_sizes[MAX_CHANNELS]; - off_t sound_name_offset; - off_t config_name_offset; + uint32_t sound_name_offset; + uint32_t config_name_offset; char name[255+1]; } ktsr_header; @@ -266,12 +266,12 @@ static int parse_codec(ktsr_header* ktsr) { return 1; fail: - VGM_LOG("KTSR: unknown codec combo: ext=%x, fmt=%x, ptf=%x\n", ktsr->is_external, ktsr->format, ktsr->platform); + VGM_LOG("ktsr: unknown codec combo: ext=%x, fmt=%x, ptf=%x\n", ktsr->is_external, ktsr->format, ktsr->platform); return 0; } -static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, off_t offset) { - off_t suboffset, starts_offset, sizes_offset; +static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, uint32_t offset) { + uint32_t suboffset, starts_offset, sizes_offset; int i; uint32_t type; @@ -318,7 +318,7 @@ static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, off_t offset) { ktsr->is_external = 1; if (ktsr->format != 0x05) { - VGM_LOG("KTSR: unknown subcodec at %lx\n", offset); + VGM_LOG("ktsr: unknown subcodec at %x\n", offset); goto fail; } @@ -362,7 +362,7 @@ static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, off_t offset) { suboffset = offset + 0x30; if (ktsr->channels > MAX_CHANNELS) { - VGM_LOG("KTSR: max channels found\n"); + VGM_LOG("ktsr: max channels found\n"); goto fail; } @@ -379,7 +379,7 @@ static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, off_t offset) { default: /* streams also have their own chunks like 0x09D4F415, not needed here */ - VGM_LOG("KTSR: unknown subheader at %lx\n", offset); + VGM_LOG("ktsr: unknown subheader at %x\n", offset); goto fail; } @@ -388,7 +388,7 @@ static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, off_t offset) { return 1; fail: - VGM_LOG("KTSR: error parsing subheader\n"); + VGM_LOG("ktsr: error parsing subheader\n"); return 0; } @@ -419,7 +419,7 @@ static void build_name(ktsr_header* ktsr, STREAMFILE* sf) { static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf, uint32_t target_id) { /* more configs than sounds is possible so we need target_id first */ - off_t offset, end, name_offset; + uint32_t offset, end, name_offset; uint32_t stream_id; offset = 0x40; @@ -447,7 +447,7 @@ static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf, uint32_t target_id } static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) { - off_t offset, end, header_offset, name_offset; + uint32_t offset, end, header_offset, name_offset; uint32_t stream_id = 0, stream_count; /* 00: KTSR @@ -486,7 +486,6 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) { break; case 0xC5CCCB70: /* sound (internal data or external stream) */ - //VGM_LOG("info at %lx\n", offset); ktsr->total_subsongs++; /* sound table: @@ -503,13 +502,12 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) { if (ktsr->total_subsongs == ktsr->target_subsong) { - //;VGM_LOG("KTSR: target at %lx\n", offset); stream_id = read_u32be(offset + 0x08,sf); //ktsr->is_external = read_u16le(offset + 0x0e,sf); stream_count = read_u32le(offset + 0x10,sf); if (stream_count != 1) { - VGM_LOG("KTSR: unknown stream count\n"); + VGM_LOG("ktsr: unknown stream count\n"); goto fail; } @@ -527,7 +525,7 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) { default: /* streams also have their own chunks like 0x09D4F415, not needed here */ - VGM_LOG("KTSR: unknown chunk at %lx\n", offset); + VGM_LOG("ktsr: unknown chunk at %x\n", offset); goto fail; } @@ -542,5 +540,6 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) { return 1; fail: + vgm_logi("KTSR: unknown variation (report)\n"); return 0; } diff --git a/src/meta/psb.c b/src/meta/psb.c index 0c9246e9..bba607ce 100644 --- a/src/meta/psb.c +++ b/src/meta/psb.c @@ -1,10 +1,10 @@ #include "meta.h" #include "../coding/coding.h" #include "../util/m2_psb.h" +#include "../layout/layout.h" -//todo prepare multichannel -#define PSB_MAX_LAYERS 1 +#define PSB_MAX_LAYERS 2 typedef enum { PCM, RIFF_AT3, XMA2, MSADPCM, XWMA, DSP, OPUSNX, RIFF_AT9, VAG } psb_codec_t; typedef struct { @@ -25,8 +25,10 @@ typedef struct { int target_subsong; /* chunks references */ - uint32_t stream_offset; - uint32_t stream_size; + uint32_t stream_offset[PSB_MAX_LAYERS]; + uint32_t stream_size[PSB_MAX_LAYERS]; + uint32_t body_offset; + uint32_t body_size; uint32_t intro_offset; uint32_t intro_size; uint32_t fmt_offset; @@ -43,6 +45,7 @@ typedef struct { int bps; int32_t num_samples; + int32_t body_samples; int32_t intro_samples; int32_t skip_samples; int loop_flag; @@ -55,6 +58,10 @@ typedef struct { static int parse_psb(STREAMFILE* sf, psb_header_t* psb); +static segmented_layout_data* build_segmented_psb_opus(STREAMFILE* sf, psb_header_t* psb); +static layered_layout_data* build_layered_psb(STREAMFILE* sf, psb_header_t* psb); + + /* PSB - M2 container [Sega Vintage Collection (multi), Legend of Mana (multi)] */ VGMSTREAM* init_vgmstream_psb(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; @@ -82,7 +89,7 @@ VGMSTREAM* init_vgmstream_psb(STREAMFILE* sf) { init_vgmstream = init_vgmstream_riff; break; - case VAG: /* Plastic Memories (Vita) */ + case VAG: /* Plastic Memories (Vita), Judgment (PS4) */ ext = "vag"; init_vgmstream = init_vgmstream_vag; break; @@ -97,7 +104,7 @@ VGMSTREAM* init_vgmstream_psb(STREAMFILE* sf) { } if (init_vgmstream != NULL) { - STREAMFILE* temp_sf = setup_subfile_streamfile(sf, psb.stream_offset, psb.stream_size, ext); + STREAMFILE* temp_sf = setup_subfile_streamfile(sf, psb.stream_offset[0], psb.stream_size[0], ext); if (!temp_sf) goto fail; vgmstream = init_vgmstream(temp_sf); @@ -121,21 +128,19 @@ VGMSTREAM* init_vgmstream_psb(STREAMFILE* sf) { vgmstream->loop_start_sample = psb.loop_start; vgmstream->loop_end_sample = psb.loop_end; vgmstream->num_streams = psb.total_subsongs; - vgmstream->stream_size = psb.stream_size; + vgmstream->stream_size = psb.stream_size[0]; switch(psb.codec) { case PCM: switch(psb.bps) { case 16: vgmstream->coding_type = coding_PCM16LE; break; /* Legend of Mana (PC), Namco Museum Archives Vol.1 (PC) */ case 24: vgmstream->coding_type = coding_PCM24LE; break; /* Legend of Mana (PC) */ - default: - vgm_logi("PSB: unknown bps %i (report)\n", psb.bps); - goto fail; + default: goto fail; } vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = psb.block_size / psb.channels; if (!vgmstream->num_samples) - vgmstream->num_samples = pcm_bytes_to_samples(psb.stream_size, psb.channels, psb.bps); + vgmstream->num_samples = pcm_bytes_to_samples(psb.stream_size[0], psb.channels, psb.bps); break; case MSADPCM: /* [Senxin Aleste (AC)] */ @@ -143,12 +148,12 @@ VGMSTREAM* init_vgmstream_psb(STREAMFILE* sf) { vgmstream->layout_type = layout_none; vgmstream->frame_size = psb.block_size; if (!vgmstream->num_samples) - vgmstream->num_samples = msadpcm_bytes_to_samples(psb.stream_size, psb.block_size, psb.channels); + vgmstream->num_samples = msadpcm_bytes_to_samples(psb.stream_size[0], psb.block_size, psb.channels); break; #ifdef VGM_USE_FFMPEG case XWMA: { /* [Senxin Aleste (AC)] */ - vgmstream->codec_data = init_ffmpeg_xwma(sf, psb.stream_offset, psb.stream_size, psb.format, psb.channels, psb.sample_rate, psb.avg_bitrate, psb.block_size); + vgmstream->codec_data = init_ffmpeg_xwma(sf, psb.stream_offset[0], psb.stream_size[0], psb.format, psb.channels, psb.sample_rate, psb.avg_bitrate, psb.block_size); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; @@ -164,27 +169,52 @@ VGMSTREAM* init_vgmstream_psb(STREAMFILE* sf) { uint8_t buf[0x100]; size_t bytes; - bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, sizeof(buf), psb.fmt_offset, psb.fmt_size, psb.stream_size, sf, 1); - vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf, bytes, psb.stream_offset, psb.stream_size); + bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, sizeof(buf), psb.fmt_offset, psb.fmt_size, psb.stream_size[0], sf, 1); + vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf, bytes, psb.stream_offset[0], psb.stream_size[0]); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; - xma_fix_raw_samples(vgmstream, sf, psb.stream_offset, psb.stream_size, psb.fmt_offset, 1,1); + xma_fix_raw_samples(vgmstream, sf, psb.stream_offset[0], psb.stream_size[0], psb.fmt_offset, 1,1); + break; + } + + case OPUSNX: { /* Legend of Mana (Switch) */ + vgmstream->layout_data = build_segmented_psb_opus(sf, &psb); + if (!vgmstream->layout_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_segmented; break; } #endif case DSP: /* Legend of Mana (Switch) */ - case OPUSNX: /* Legend of Mana (Switch) */ + /* standard DSP resources */ + if (psb.layers > 1) { + /* somehow R offset can go before L, use layered */ + vgmstream->layout_data = build_layered_psb(sf, &psb); + if (!vgmstream->layout_data) goto fail; + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_layered; + } + else { + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_none; + + dsp_read_coefs_le(vgmstream,sf, psb.stream_offset[0] + 0x1c, 0); + dsp_read_hist_le(vgmstream,sf, psb.stream_offset[0] + 0x1c + 0x20, 0); + } + + vgmstream->num_samples = read_u32le(psb.stream_offset[0] + 0x00, sf); + break; + default: - vgm_logi("PSB: not implemented (ignore)\n"); goto fail; } strncpy(vgmstream->stream_name, psb.readable_name, STREAM_NAME_SIZE); - if (!vgmstream_open_stream(vgmstream, sf, psb.stream_offset)) + if (!vgmstream_open_stream(vgmstream, sf, psb.stream_offset[0])) goto fail; return vgmstream; @@ -193,6 +223,103 @@ fail: return NULL; } +static segmented_layout_data* build_segmented_psb_opus(STREAMFILE* sf, psb_header_t* psb) { + segmented_layout_data* data = NULL; + int i, pos = 0, segment_count = 0, max_count = 2; + + //TODO improve + //TODO these use standard switch opus (VBR), could sub-file? but skip_samples becomes more complex + + uint32_t offsets[] = {psb->intro_offset, psb->body_offset}; + uint32_t sizes[] = {psb->intro_size, psb->body_size}; + uint32_t samples[] = {psb->intro_samples, psb->body_samples}; + uint32_t skips[] = {0, psb->skip_samples}; + + /* intro + body (looped songs) or just body (standard songs) + in full loops intro is 0 samples with a micro 1-frame opus [Nekopara (Switch)] */ + if (offsets[0] && samples[0]) + segment_count++; + if (offsets[1] && samples[1]) + segment_count++; + + /* init layout */ + data = init_layout_segmented(segment_count); + if (!data) goto fail; + + for (i = 0; i < max_count; i++) { + if (!offsets[i] || !samples[i]) + continue; +#ifdef VGM_USE_FFMPEG + { + int start = read_u32le(offsets[i] + 0x10, sf) + 0x08; + int skip = read_s16le(offsets[i] + 0x1c, sf); + + VGMSTREAM* v = allocate_vgmstream(psb->channels, 0); + if (!v) goto fail; + + data->segments[pos++] = v; + v->sample_rate = psb->sample_rate; + v->num_samples = samples[i]; + v->codec_data = init_ffmpeg_switch_opus(sf, offsets[i] + start, sizes[i] - start, psb->channels, skips[i] + skip, psb->sample_rate); + if (!v->codec_data) goto fail; + v->coding_type = coding_FFmpeg; + v->layout_type = layout_none; + } +#else + goto fail; +#endif + } + + if (!setup_layout_segmented(data)) + goto fail; + + return data; +fail: + free_layout_segmented(data); + return NULL; +} + +static layered_layout_data* build_layered_psb(STREAMFILE* sf, psb_header_t* psb) { + layered_layout_data* data = NULL; + int i; + + + /* init layout */ + data = init_layout_layered(psb->layers); + if (!data) goto fail; + + for (i = 0; i < psb->layers; i++) { + STREAMFILE* temp_sf = NULL; + VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf) = NULL; + const char* extension = NULL; + + switch (psb->codec) { + case DSP: + extension = "adpcm"; + init_vgmstream = init_vgmstream_ngc_dsp_std_le; + break; + default: + goto fail; + } + + temp_sf = setup_subfile_streamfile(sf, psb->stream_offset[i], psb->stream_size[i], extension); + if (!temp_sf) goto fail; + + data->layers[i] = init_vgmstream(temp_sf); + close_streamfile(temp_sf); + if (!data->layers[i]) goto fail; + } + + /* setup layered VGMSTREAMs */ + if (!setup_layout_layered(data)) + goto fail; + return data; +fail: + free_layout_layered(data); + return NULL; +} + + /*****************************************************************************/ static int prepare_fmt(STREAMFILE* sf, psb_header_t* psb) { @@ -272,11 +399,20 @@ static int prepare_codec(STREAMFILE* sf, psb_header_t* psb) { if (strcmp(ext, ".opus") == 0) { psb->codec = OPUSNX; + + psb->body_samples -= psb->skip_samples; + if (!psb->loop_flag) + psb->loop_flag = psb->intro_samples > 0; + psb->loop_start = psb->intro_samples; + psb->loop_end = psb->body_samples + psb->intro_samples; + psb->num_samples = psb->intro_samples + psb->body_samples; return 1; } if (strcmp(ext, ".adpcm") == 0) { psb->codec = DSP; + + psb->channels = psb->layers; return 1; } } @@ -286,8 +422,8 @@ static int prepare_codec(STREAMFILE* sf, psb_header_t* psb) { return 1; } - if (strcmp(spec, "vita") == 0) { - if (is_id32be(psb->stream_offset, sf, "RIFF")) + if (strcmp(spec, "vita") == 0 || strcmp(spec, "ps4") == 0) { + if (is_id32be(psb->stream_offset[0], sf, "RIFF")) psb->codec = RIFF_AT9; else psb->codec = VAG; @@ -374,8 +510,8 @@ static int parse_psb_channels(psb_header_t* psb, psb_node_t* nchans) { switch (type) { case PSB_TYPE_DATA: /* Sega Vintage Collection (PS3) */ data = psb_node_get_result(&narch).data; - psb->stream_offset = data.offset; - psb->stream_size = data.size; + psb->stream_offset[i] = data.offset; + psb->stream_size[i] = data.size; break; case PSB_TYPE_OBJECT: /* rest */ @@ -386,8 +522,8 @@ static int parse_psb_channels(psb_header_t* psb, psb_node_t* nchans) { data = psb_node_get_data(&narch, "data"); if (data.offset) { - psb->stream_offset = data.offset; - psb->stream_size = data.size; + psb->stream_offset[i] = data.offset; + psb->stream_size[i] = data.size; } data = psb_node_get_data(&narch, "fmt"); @@ -408,23 +544,20 @@ static int parse_psb_channels(psb_header_t* psb, psb_node_t* nchans) { } } -#if 0 if (psb_node_by_key(&narch, "body", &node)) { data = psb_node_get_data(&node, "data"); - psb->stream_offset = data.offset; - psb->stream_size = data.size; - psb->num_samples = psb_node_get_integer(&node, "sampleCount"); + psb->body_offset = data.offset; + psb->body_size = data.size; + psb->body_samples = psb_node_get_integer(&node, "sampleCount"); psb->skip_samples = psb_node_get_integer(&node, "skipSampleCount"); } if (psb_node_by_key(&narch, "intro", &node)) { data = psb_node_get_data(&node, "data"); - psb->stream_offset = data.offset; - psb->stream_size = data.size; - psb->num_samples = psb_node_get_integer(&node, "sampleCount"); - psb->skip_samples = psb_node_get_integer(&node, "skipSampleCount"); + psb->intro_offset = data.offset; + psb->intro_size = data.size; + psb->intro_samples = psb_node_get_integer(&node, "sampleCount"); } -#endif data = psb_node_get_data(&narch, "dpds"); if (data.offset) { @@ -432,10 +565,25 @@ static int parse_psb_channels(psb_header_t* psb, psb_node_t* nchans) { psb->dpds_size = data.size; } - psb->sample_rate = (int)psb_node_get_float(&narch, "samprate"); + psb->channels = psb_node_get_integer(&narch, "channelCount"); + + psb->sample_rate = (int)psb_node_get_float(&narch, "samprate"); /* seen in DSP */ + if (!psb->sample_rate) + psb->sample_rate = psb_node_get_integer(&narch, "samprate"); /* seen in OpusNX */ + + psb->tmp->ext = psb_node_get_string(&narch, "ext"); /* appears for all channels, assumed to be the same */ psb->tmp->wav = psb_node_get_string(&narch, "wav"); + /* DSP has a "pan" array like: [1.0, 0.0]=L, [0.0, 1.0 ]=R */ + if (psb_node_by_key(&narch, "pan", &node)) { + + psb_node_by_index(&node, i, &nsub); + if (psb_node_get_result(&nsub).flt != 1.0f) { + vgm_logi("PSB: unexpected pan (report)\n"); + }; + } + /* background: false? */ break; diff --git a/src/streamfile.c b/src/streamfile.c index c3e4f90f..71b30bc5 100644 --- a/src/streamfile.c +++ b/src/streamfile.c @@ -16,22 +16,13 @@ * - Clang: seems only defined on Linux/GNU environments, somehow emscripten is out * (unsure about Clang Win since apparently they define _MSC_VER) * - Android: API +24 if not using __USE_FILE_OFFSET64 - * Not sure if fopen64 is needed in some cases. May be work adding some compiler flag to control this manually. + * Not sure if fopen64 is needed in some cases. May be worth adding some compiler flag to enable 64 versions manually. */ /* MSVC fixes (though mingw uses MSVCRT but not MSC_VER, maybe use AND?) */ #if defined(__MSVCRT__) || defined(_MSC_VER) #include -/* - #ifndef fseeko - #define fseeko fseek - #endif - #ifndef ftello - #define ftello ftell - #endif -*/ - #define fopen_v fopen #if (_MSC_VER >= 1400) #define fseek_v _fseeki64 @@ -48,9 +39,9 @@ #define fdopen _fdopen #define dup _dup - #ifndef off64_t - #define off_t __int64 - #endif + //#ifndef off64_t + // #define off_t/off64_t __int64 + //#endif #elif defined(XBMC) || defined(__EMSCRIPTEN__) || defined (__ANDROID__) #define fopen_v fopen @@ -70,6 +61,7 @@ typedef struct { FILE* infile; /* actual FILE */ char name[PATH_LIMIT]; /* FILE filename */ + int name_len; /* cache */ offv_t offset; /* last read offset (info) */ offv_t buf_offset; /* current buffer data start */ uint8_t* buf; /* data buffer */ @@ -87,7 +79,7 @@ static size_t stdio_read(STDIO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size if (!sf->infile || !dst || length <= 0 || offset < 0) return 0; - //;VGM_LOG("STDIO: read %lx + %x (buf %lx + %x)\n", offset, length, sf->buf_offset, sf->valid_size); + //;VGM_LOG("stdio: read %lx + %x (buf %lx + %x)\n", offset, length, sf->buf_offset, sf->valid_size); /* is the part of the requested length in the buffer? */ if (offset >= sf->buf_offset && offset < sf->buf_offset + sf->valid_size) { @@ -98,7 +90,7 @@ static size_t stdio_read(STDIO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size if (buf_limit > length) buf_limit = length; - //;VGM_LOG("STDIO: copy buf %lx + %x (+ %x) (buf %lx + %x)\n", offset, length_to_read, (length - length_to_read), sf->buf_offset, sf->valid_size); + //;VGM_LOG("stdio: copy buf %lx + %x (+ %x) (buf %lx + %x)\n", offset, length_to_read, (length - length_to_read), sf->buf_offset, sf->valid_size); memcpy(dst, sf->buf + buf_into, buf_limit); read_total += buf_limit; @@ -109,7 +101,7 @@ static size_t stdio_read(STDIO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size #ifdef VGM_DEBUG_OUTPUT if (offset < sf->buf_offset && length > 0) { - VGM_LOG("STDIO: rebuffer, requested %lx vs %lx (sf %x)\n", offset, sf->buf_offset, (uint32_t)sf); + VGM_LOG("stdio: rebuffer, requested %x vs %x (sf %x)\n", (uint32_t)offset, (uint32_t)sf->buf_offset, (uint32_t)sf); //sf->rebuffer++; //if (rebuffer > N) ... } @@ -143,7 +135,7 @@ static size_t stdio_read(STDIO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size /* fill the buffer (offset now is beyond buf_offset) */ sf->buf_offset = offset; sf->valid_size = fread(sf->buf, sizeof(uint8_t), sf->buf_size, sf->infile); - //;VGM_LOG("STDIO: read buf %lx + %x\n", sf->buf_offset, sf->valid_size); + //;VGM_LOG("stdio: read buf %lx + %x\n", sf->buf_offset, sf->valid_size); /* decide how much must be read this time */ if (length > sf->buf_size) @@ -177,8 +169,12 @@ static offv_t stdio_get_offset(STDIO_STREAMFILE* sf) { return sf->offset; } static void stdio_get_name(STDIO_STREAMFILE* sf, char* name, size_t name_size) { - strncpy(name, sf->name, name_size); - name[name_size - 1] = '\0'; + int copy_size = sf->name_len + 1; + if (copy_size > name_size) + copy_size = name_size; + + memcpy(name, sf->name, copy_size); + name[copy_size - 1] = '\0'; } static STREAMFILE* stdio_open(STDIO_STREAMFILE* sf, const char* const filename, size_t buf_size) { @@ -241,8 +237,11 @@ static STREAMFILE* open_stdio_streamfile_buffer_by_file(FILE* infile, const char this_sf->buf_size = buf_size; this_sf->buf = buf; - strncpy(this_sf->name, filename, sizeof(this_sf->name)); - this_sf->name[sizeof(this_sf->name)-1] = '\0'; + this_sf->name_len = strlen(filename); + if (this_sf->name_len >= sizeof(this_sf->name)) + goto fail; + memcpy(this_sf->name, filename, this_sf->name_len); + this_sf->name[this_sf->name_len] = '\0'; /* cache file_size */ if (infile) { @@ -335,7 +334,7 @@ static size_t buffer_read(BUFFER_STREAMFILE* sf, uint8_t* dst, offv_t offset, si #ifdef VGM_DEBUG_OUTPUT if (offset < sf->buf_offset) { - VGM_LOG("BUFFER: rebuffer, requested %lx vs %lx (sf %x)\n", offset, sf->buf_offset, (uint32_t)sf); + VGM_LOG("buffer: rebuffer, requested %x vs %x (sf %x)\n", (uint32_t)offset, (uint32_t)sf->buf_offset, (uint32_t)sf); } #endif @@ -346,7 +345,7 @@ static size_t buffer_read(BUFFER_STREAMFILE* sf, uint8_t* dst, offv_t offset, si /* ignore requests at EOF */ if (offset >= sf->file_size) { //offset = sf->file_size; /* seems fseek doesn't clamp offset */ - VGM_ASSERT_ONCE(offset > sf->file_size, "BUFFER: reading over file_size 0x%x @ 0x%x + 0x%x\n", sf->file_size, (uint32_t)offset, length); + VGM_ASSERT_ONCE(offset > sf->file_size, "buffer: reading over file_size 0x%x @ 0x%x + 0x%x\n", sf->file_size, (uint32_t)offset, length); break; } @@ -598,14 +597,15 @@ typedef struct { STREAMFILE* inner_sf; void* data; /* state for custom reads, malloc'ed + copied on open (to re-open streamfiles cleanly) */ size_t data_size; - size_t (*read_callback)(STREAMFILE*, uint8_t*, offv_t, size_t, void*); /* custom read to modify data before copying into buffer */ + size_t (*read_callback)(STREAMFILE*, uint8_t*, off_t, size_t, void*); /* custom read to modify data before copying into buffer */ size_t (*size_callback)(STREAMFILE*, void*); /* size when custom reads make data smaller/bigger than underlying streamfile */ int (*init_callback)(STREAMFILE*, void*); /* init the data struct members somehow, return >= 0 if ok */ void (*close_callback)(STREAMFILE*, void*); /* close the data struct members somehow */ + /* read doesn't use offv_t since callbacks would need to be modified */ } IO_STREAMFILE; static size_t io_read(IO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) { - return sf->read_callback(sf->inner_sf, dst, offset, length, sf->data); + return sf->read_callback(sf->inner_sf, dst, (off_t)offset, length, sf->data); } static size_t io_get_size(IO_STREAMFILE* sf) { if (sf->size_callback) @@ -697,6 +697,7 @@ typedef struct { STREAMFILE* inner_sf; char fakename[PATH_LIMIT]; + int fakename_len; } FAKENAME_STREAMFILE; static size_t fakename_read(FAKENAME_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) { @@ -709,8 +710,11 @@ static offv_t fakename_get_offset(FAKENAME_STREAMFILE* sf) { return sf->inner_sf->get_offset(sf->inner_sf); /* default */ } static void fakename_get_name(FAKENAME_STREAMFILE* sf, char* name, size_t name_size) { - strncpy(name,sf->fakename, name_size); - name[name_size - 1] = '\0'; + int copy_size = sf->fakename_len + 1; + if (copy_size > name_size) + copy_size = name_size; + memcpy(name, sf->fakename, copy_size); + name[copy_size - 1] = '\0'; } static STREAMFILE* fakename_open(FAKENAME_STREAMFILE* sf, const char* const filename, size_t buf_size) { @@ -753,7 +757,7 @@ STREAMFILE* open_fakename_streamfile(STREAMFILE* sf, const char* fakename, const /* copy passed name or retain current, and swap extension if expected */ if (fakename) { - strcpy(this_sf->fakename,fakename); + strcpy(this_sf->fakename, fakename); } else { sf->get_name(sf, this_sf->fakename, PATH_LIMIT); } @@ -768,6 +772,8 @@ STREAMFILE* open_fakename_streamfile(STREAMFILE* sf, const char* fakename, const strcat(this_sf->fakename, fakeext); } + this_sf->fakename_len = strlen(this_sf->fakename); + return &this_sf->vt; } STREAMFILE* open_fakename_streamfile_f(STREAMFILE* sf, const char* fakename, const char* fakeext) { @@ -1365,7 +1371,6 @@ static int find_chunk_internal(STREAMFILE* sf, uint32_t chunk_id, off_t start_of while (offset < max_offset) { uint32_t chunk_type = read_32bit_type(offset + 0x00,sf); uint32_t chunk_size = read_32bit_size(offset + 0x04,sf); - //;VGM_LOG("CHUNK: type=%x, size=%x at %lx\n", chunk_type, chunk_size, offset); if (chunk_type == 0xFFFFFFFF || chunk_size == 0xFFFFFFFF) return 0; @@ -1503,7 +1508,7 @@ void dump_streamfile(STREAMFILE* sf, int num) { bytes = read_streamfile(buf, offset, sizeof(buf), sf); if(!bytes) { - VGM_LOG("dump streamfile: can't read at %lx\n", offset); + VGM_LOG("dump streamfile: can't read at %x\n", (uint32_t)offset); break; } diff --git a/src/streamfile.h b/src/streamfile.h index 1aa46d92..f29be96a 100644 --- a/src/streamfile.h +++ b/src/streamfile.h @@ -20,10 +20,6 @@ /* MSVC fixes (though mingw uses MSVCRT but not MSC_VER, maybe use AND?) */ #if defined(__MSVCRT__) || defined(_MSC_VER) #include - - #ifndef off64_t - #define off_t __int64 - #endif #endif #ifndef DIR_SEPARATOR diff --git a/src/util.h b/src/util.h index e19a0f82..0e4aae73 100644 --- a/src/util.h +++ b/src/util.h @@ -70,7 +70,15 @@ static inline float get_f32le(const uint8_t* p) { temp.u32 = get_u32le(p); return temp.f32; } -static inline float get_d64le(const uint8_t* p) { +static inline double get_d64be(const uint8_t* p) { + union { + uint64_t u64; + double d64; + } temp; + temp.u64 = get_u64be(p); + return temp.d64; +} +static inline double get_d64le(const uint8_t* p) { union { uint64_t u64; double d64; diff --git a/src/util/log.h b/src/util/log.h index 6bcbfa97..b94641c3 100644 --- a/src/util/log.h +++ b/src/util/log.h @@ -15,12 +15,22 @@ * - still WIP, some stuff not working ATM or may change */ +/* compiler hints to force printf-style checks, butt-ugly but so useful... */ +/* supposedly MSCV has _Printf_format_string_ with /analyze but I can't get it to work */ +#if defined(__GNUC__) /* clang too */ + #define GNUC_LOG_ATRIB __attribute__ ((format(printf, 1, 2))) /* only with -Wformat (1=format param, 2=other params) */ + #define GNUC_ASR_ATRIB __attribute__ ((format(printf, 2, 3))) +#else + #define GNUC_LOG_ATRIB /* none */ + #define GNUC_ASR_ATRIB /* none */ +#endif + // void (*callback)(int level, const char* str); void vgm_log_set_callback(void* ctx_p, int level, int type, void* callback); #if defined(VGM_LOG_OUTPUT) || defined(VGM_DEBUG_OUTPUT) - void vgm_logi(/*void* ctx,*/ const char* fmt, ...); - void vgm_asserti(/*void* ctx,*/ int condition, const char* fmt, ...); + void vgm_logi(/*void* ctx,*/ const char* fmt, ...) GNUC_LOG_ATRIB; + void vgm_asserti(/*void* ctx,*/ int condition, const char* fmt, ...) GNUC_ASR_ATRIB; //void vgm_logi_once(/*void* ctx, int* once_flag, */ const char* fmt, ...); #else #define vgm_logi(...) /* nothing */ @@ -28,7 +38,7 @@ void vgm_log_set_callback(void* ctx_p, int level, int type, void* callback); #endif #ifdef VGM_DEBUG_OUTPUT - void vgm_logd(/*void* ctx,*/ const char* fmt, ...); + void vgm_logd(/*void* ctx,*/ const char* fmt, ...) GNUC_LOG_ATRIB; #define VGM_LOG(...) do { vgm_logd(__VA_ARGS__); } while (0) #define VGM_ASSERT(condition, ...) do { if (condition) {vgm_logd(__VA_ARGS__);} } while (0) #else