diff --git a/doc/TXTH.md b/doc/TXTH.md index c6a19a18..c737873a 100644 --- a/doc/TXTH.md +++ b/doc/TXTH.md @@ -410,28 +410,42 @@ subfile_extension = (string) ``` #### CHUNK DEINTERLEAVING -Some files interleave data chunks, for example 3 stereo songs pasted together, alternating 0x10000 bytes of data each. These settings allow vgmstream to play one of the chunks while ignoring the rest (read 0x10000 data, skip 0x10000*2). -File is first "dechunked" then played with using other settings (`start_offset` would point within the internal dechunked" file). It can be used to remove garbage data that affects decoding, too. +Some files interleave data chunks, for example 3 stereo songs pasted together, alternating 0x10000 bytes of data each. Or maybe 0x100 of useless header + 0x10000 of valid data. Chunk settings allow vgmstream to play valid chunks while ignoring the rest (read 0x10000 data, skip rest). +File is first "dechunked" before being played, so other settings work over this final file (`start_offset` would be a point within the internal dechunked" file). Use combinations of chunk settings to make vgmstream "see" only actual codec data. -You need to set: +Main settings: - `chunk_count`: total number of interleaved chunks (ex. 3=3 interleaved songs) -- `chunk_number`: first chunk to start (ex. 1=0x00000, 2=0x10000, 3=0x20000...) - * If you set `subsong_count` and `chunk_count` first, `chunk_number` will be auto-set per subsong (subsong 1 starts from chunk number 1, subsong 2 from chunk 2, etc) - `chunk_start`: absolute offset where chunks start (normally 0x00) - `chunk_size`: amount of data in a single chunk (ex. 0x10000) -For fine-tuning you can optionally set (before `chunk_size`, for reasons): -- `chunk_header_size`: header to skip before chunk data (part of chunk_size) -- `chunk_data_size`: actual data size (part of chunk_size, rest is header/padding) -So, if you set size to 0x1000, header_size 0x100, data_size is implicitly 0xF00, or if size is 0x1000 and data_size 0x800 last 0x200 is ignored padding. Use combinations of the above to make vgmstream "see" only actual codec data. +Optional settings (set before main): +- `chunk_number`: first chunk to start (ex. 1=0x00000, 2=0x10000, 3=0x20000...) + - If you set `subsong_count` and `chunk_count` first, `chunk_number` will be auto-set per subsong (subsong 1 starts from chunk number 1, subsong 2 from chunk 2, etc) +- `chunk_header_size`: header to skip before chunk data (part of chunk_size) + - If size is 0x1000 and header_size 0x100, data_size is implicitly set to 0xF00 +- `chunk_data_size`: actual data size (part of chunk_size, rest is header/padding) + - If size is 0x1000 and data_size 0x800 last 0x200 is ignored padding. + +Dynamic settings (set before main, requires `chunk_header_size`): +- `chunk_value`: ignores chunks that don't match this value at chunk offset 0x00 (32-bit, in `chunk_endianness`) +- `chunk_size_offset`: reads chunk size at this offset, in header (32-bit in `chunk_endianness`). +- `chunk_endianness`: sets endianness of the above values + +For technical reasons, "dechunking" activates when setting all main settings, so set optional config first. Note that config is static (not per-chunk), so `chunk_size = @0x10` is read from the beginning of the file once, not every time a new chunk is found. + ``` chunk_count = (value) -chunk_number = (value) chunk_start = (value) +chunk_size = (value) + +chunk_number = (value) chunk_header_size = (value) chunk_data_size = (value) -chunk_size = (value) + +chunk_value = (value) +chunk_size_offset = (value) +chunk_endian = LE|BE ``` #### NAME TABLE diff --git a/src/meta/txth.c b/src/meta/txth.c index 05e3eda3..fc86d5b1 100644 --- a/src/meta/txth.c +++ b/src/meta/txth.c @@ -118,6 +118,9 @@ typedef struct { uint32_t chunk_count; uint32_t chunk_header_size; uint32_t chunk_data_size; + uint32_t chunk_value; + uint32_t chunk_size_offset; + uint32_t chunk_be; int chunk_start_set; int chunk_size_set; int chunk_count_set; @@ -791,7 +794,9 @@ static void set_body_chunk(txth_header* txth) { //todo maybe should only be done once, or have some count to retrigger to simplify? if (!txth->chunk_start_set || !txth->chunk_size_set || !txth->chunk_count_set) return; - if (txth->chunk_size == 0 || txth->chunk_start > txth->data_size || txth->chunk_count == 0) + if ((txth->chunk_size == 0 && ! txth->chunk_size_offset) || + txth->chunk_start > txth->data_size || + txth->chunk_count == 0) return; if (!txth->sf_body) return; @@ -807,18 +812,22 @@ static void set_body_chunk(txth_header* txth) { { txth_io_config_data cfg = {0}; - cfg.chunk_start = txth->chunk_start; + cfg.chunk_number = txth->chunk_number - 1; /* 1-index to 0-index */ cfg.chunk_header_size = txth->chunk_header_size; cfg.chunk_data_size = txth->chunk_data_size; + + cfg.chunk_value = txth->chunk_value; + cfg.chunk_size_offset = txth->chunk_size_offset; + cfg.chunk_be = txth->chunk_be; + + cfg.chunk_start = txth->chunk_start; cfg.chunk_size = txth->chunk_size; cfg.chunk_count = txth->chunk_count; - cfg.chunk_number = txth->chunk_number - 1; /* 1-index to 0-index */ temp_sf = setup_txth_streamfile(txth->sf_body, cfg, txth->sf_body_opened); if (!temp_sf) return; } - /* closing is handled by temp_sf */ //if (txth->sf_body_opened) { // close_streamfile(txth->sf_body); @@ -953,6 +962,19 @@ static txth_codec_t parse_codec(txth_header* txth, const char* val) { return UNKNOWN; } +static int parse_be(txth_header* txth, const char* val, uint32_t* p_value) { + if (is_string(val, "BE")) + *p_value = 1; + else if (is_string(val, "LE")) + *p_value = 0; + else + if (!parse_num(txth->sf_head,txth,val, p_value)) + goto fail; + return 1; +fail: + return 0; +} + static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, char* val) { //;VGM_LOG("TXTH: key=%s, val=%s\n", key, val); @@ -1185,11 +1207,7 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, cha if (!parse_num(txth->sf_head,txth,val, &txth->coef_spacing)) goto fail; } else if (is_string(key,"coef_endianness")) { - if (is_string(val, "BE")) - txth->coef_big_endian = 1; - else if (is_string(val, "LE")) - txth->coef_big_endian = 0; - else if (!parse_num(txth->sf_head,txth,val, &txth->coef_big_endian)) goto fail; + if (!parse_be(txth, val, &txth->coef_big_endian)) goto fail; } else if (is_string(key,"coef_mode")) { if (!parse_num(txth->sf_head,txth,val, &txth->coef_mode)) goto fail; @@ -1212,11 +1230,7 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, cha if (!parse_num(txth->sf_head,txth,val, &txth->hist_spacing)) goto fail; } else if (is_string(key,"hist_endianness")) { - if (is_string(val, "BE")) - txth->hist_big_endian = 1; - else if (is_string(val, "LE")) - txth->hist_big_endian = 0; - else if (!parse_num(txth->sf_head,txth,val, &txth->hist_big_endian)) goto fail; + if (!parse_be(txth, val, &txth->hist_big_endian)) goto fail; } /* SUBSONGS */ @@ -1340,34 +1354,41 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, cha } /* CHUNKS */ - else if (is_string(key,"chunk_number")) { - if (!parse_num(txth->sf_head,txth,val, &txth->chunk_number)) goto fail; + else if (is_string(key,"chunk_count")) { + if (!parse_num(txth->sf_head,txth,val, &txth->chunk_count)) goto fail; + txth->chunk_count_set = 1; + set_body_chunk(txth); } else if (is_string(key,"chunk_start")) { if (!parse_num(txth->sf_head,txth,val, &txth->chunk_start)) goto fail; txth->chunk_start_set = 1; set_body_chunk(txth); } - else if (is_string(key,"chunk_header_size")) { - if (!parse_num(txth->sf_head,txth,val, &txth->chunk_header_size)) goto fail; - //txth->chunk_header_size_set = 1; - //set_body_chunk(txth); /* optional and should go before chunk_size */ - } - else if (is_string(key,"chunk_data_size")) { - if (!parse_num(txth->sf_head,txth,val, &txth->chunk_data_size)) goto fail; - //txth->chunk_data_size_set = 1; - //set_body_chunk(txth); /* optional and should go before chunk_size */ - } else if (is_string(key,"chunk_size")) { if (!parse_num(txth->sf_head,txth,val, &txth->chunk_size)) goto fail; txth->chunk_size_set = 1; set_body_chunk(txth); } - else if (is_string(key,"chunk_count")) { - if (!parse_num(txth->sf_head,txth,val, &txth->chunk_count)) goto fail; - txth->chunk_count_set = 1; - set_body_chunk(txth); + /* optional and should go before the above */ + else if (is_string(key,"chunk_number")) { + if (!parse_num(txth->sf_head,txth,val, &txth->chunk_number)) goto fail; } + else if (is_string(key,"chunk_header_size")) { + if (!parse_num(txth->sf_head,txth,val, &txth->chunk_header_size)) goto fail; + } + else if (is_string(key,"chunk_data_size")) { + if (!parse_num(txth->sf_head,txth,val, &txth->chunk_data_size)) goto fail; + } + else if (is_string(key,"chunk_value")) { + if (!parse_num(txth->sf_head,txth,val, &txth->chunk_value)) goto fail; + } + else if (is_string(key,"chunk_size_offset")) { + if (!parse_num(txth->sf_head,txth,val, &txth->chunk_size_offset)) goto fail; + } + else if (is_string(key,"chunk_endianness")) { + if (!parse_be(txth, val, &txth->chunk_be)) goto fail; + } + /* BASE OFFSET */ else if (is_string(key,"base_offset")) { diff --git a/src/meta/txth_streamfile.h b/src/meta/txth_streamfile.h index 5e4b0f2a..aeae4444 100644 --- a/src/meta/txth_streamfile.h +++ b/src/meta/txth_streamfile.h @@ -1,30 +1,36 @@ #ifndef _TXTH_STREAMFILE_H_ #define _TXTH_STREAMFILE_H_ #include "../streamfile.h" +#include "../util/endianness.h" typedef struct { - off_t chunk_start; - size_t chunk_size; - size_t chunk_header_size; - size_t chunk_data_size; + uint32_t chunk_start; + uint32_t chunk_size; + uint32_t chunk_header_size; + uint32_t chunk_data_size; + int chunk_count; int chunk_number; + + uint32_t chunk_value; + uint32_t chunk_size_offset; + int chunk_be; } txth_io_config_data; typedef struct { /* config */ txth_io_config_data cfg; - size_t stream_size; + uint32_t stream_size; /* state */ - off_t logical_offset; /* fake offset */ - off_t physical_offset; /* actual offset */ - size_t block_size; /* current size */ - size_t skip_size; /* size from block start to reach data */ - size_t data_size; /* usable size in a block */ + uint32_t logical_offset; /* fake offset */ + uint32_t physical_offset; /* actual offset */ + uint32_t block_size; /* current size */ + uint32_t skip_size; /* size from block start to reach data */ + uint32_t data_size; /* usable size in a block */ - size_t logical_size; + uint32_t logical_size; } txth_io_data; @@ -64,6 +70,27 @@ static size_t txth_io_read(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t l data->data_size = data->cfg.chunk_data_size; } + /* chunk size reader (overwrites the above) */ + if (data->cfg.chunk_header_size && data->cfg.chunk_size_offset) { + read_u32_t read_u32 = data->cfg.chunk_be ? read_u32be : read_u32le; + + data->block_size = read_u32(data->physical_offset + data->cfg.chunk_size_offset, sf); + data->data_size = data->block_size - data->cfg.chunk_header_size; + VGM_LOG("bs %x = %x\n", data->physical_offset, data->block_size); + + /* skip chunk if doesn't match expected header value */ + if (data->cfg.chunk_value) { + uint32_t value = read_u32(data->physical_offset + 0x00, sf); + if (value != data->cfg.chunk_value) { + VGM_LOG("skip %x vs %x at %x\n", value, data->cfg.chunk_value, data->physical_offset); + data->data_size = 0; + } + } + else { + VGM_LOG("not skip at %x\n", data->physical_offset) ; + } + } + /* clamp for games where last block is smaller */ //todo not correct for all cases if (data->physical_offset + data->block_size > data->cfg.chunk_start + data->stream_size) { data->block_size = (data->cfg.chunk_start + data->stream_size) - data->physical_offset;