Merge pull request #948 from bnnm/psb-cri

- Improve .acb name reading performance
- Fix some .psb [Legend of Mana (Switch), Judgment (PS4)]
- Minor tweaks
This commit is contained in:
bnnm 2021-09-16 00:32:39 +02:00 committed by GitHub
commit 0dd8bdd763
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1394 additions and 551 deletions

View File

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

View File

@ -23,8 +23,9 @@ typedef struct {
bool m_file_opened; /* if foobar IO service opened the file */
service_ptr_t<file> 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<file> 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)

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 <io.h>
/*
#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;
}

View File

@ -20,10 +20,6 @@
/* MSVC fixes (though mingw uses MSVCRT but not MSC_VER, maybe use AND?) */
#if defined(__MSVCRT__) || defined(_MSC_VER)
#include <io.h>
#ifndef off64_t
#define off_t __int64
#endif
#endif
#ifndef DIR_SEPARATOR

View File

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

View File

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