From 2be24a4d6e3345f673e4d87850c7388e5167ff5a Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 15 Jun 2019 13:03:30 +0200 Subject: [PATCH] Add TXTH chunk_header_size/chunk_data_size for padding and fix fields --- doc/TXTH.md | 123 ++++++++++++++++++++++++++++++++++--- src/meta/txth.c | 31 +++++++++- src/meta/txth_streamfile.h | 53 +++++++++++----- 3 files changed, 180 insertions(+), 27 deletions(-) diff --git a/doc/TXTH.md b/doc/TXTH.md index 4bba17c9..11cf84ec 100644 --- a/doc/TXTH.md +++ b/doc/TXTH.md @@ -318,25 +318,36 @@ subfile_size = (number)|(offset)|(field) subfile_extension = (string) # CHUNK DEINTERLEAVING [OPTIONAL] -# Some files interleave raw data chunks, for example 3 stereo songs pasted together, +# 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 points within the internal "dechunked" file). # # You need to set: -# - start: where all chunk data start (normally 0x00) -# - size: amount of data in a single chunk (ex. 0x10000). -# - count: total number of interleaved chunks (ex. 3=3 interleaved songs) -# - number: first chunk to start (ex. 1=0x00000, 2=0x10000, 3=0x20000...) -# If you set subsong_count first chunk_number will be a set per subsong. -chunk_start = (number)|(offset)|(field) -chunk_size = (number)|(offset)|(field) +# - 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 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. +# chunk_count = (number)|(offset)|(field) chunk_number = (number)|(offset)|(field) +chunk_start = (number)|(offset)|(field) +chunk_header_size = (number)|(offset)|(field) +chunk_data_size = (number)|(offset)|(field) +chunk_size = (number)|(offset)|(field) ``` -## Usages +## Complex usages ### Temporary values Most commands are evaluated and calculated immediatedly, every time they are found. This is by design, as it can be used to adjust and trick for certain calculations. @@ -548,3 +559,97 @@ subfile_offset = 0 subfile_size = @0x04 + 0x08 #RIFF size subfile_extension = at3 ``` + +It can be used to make blocks with padding playable: +``` +# Mortal Kombat: Deception (PS2) +codec = PSX +interleave = 0x3F40 +sample_rate = 32000 +channels = 2 + +chunk_number = 1 +chunk_count = 1 +chunk_start = 0x00 +chunk_data_size = interleave * channels +chunk_size = 0x8000 + +num_samples = data_size +``` + +## Examples + +**Colin McRae DiRT (PC) .wip.txth** +``` +id_value = 0x00000000 +id_offset = @0x00 + +codec = PCM16LE +channels = 2 +sample_rate = 32000 +start_offset = 0x04 +num_samples = data_size +loop_start_sample = 0 +loop_end_sample = data_size +``` + +**Kim Possible: What's the Switch (PS2) .str.txth** +``` +codec = PSX +interleave = 0x2000 +channels = 2 +sample_rate = 48000 +num_samples = data_size +interleave_last = auto +``` + +**Manhunt (Xbox) .rib.txth** +``` +codec = XBOX +codec_mode = 1 #interleaved XBOX +interleave = 0xD800 + +channels = 12 +sample_rate = 44100 +start_offset = 0x00 +num_samples = data_size +``` + +**Pitfall The Lost Expedition (PC) .txth** +``` +codec = DVI_IMA +interleave = 0x80 +start_offset = 0x00 +channels = 2 +sample_rate = 44100 +num_samples = data_size +``` + +**Spy Hunter (GC) .pcm.txth** +``` +codec = PCM8 +sample_rate = 32000 +channels = 1 +start_offset = 0 +num_samples = data_size +``` + +**Ultimate Board Game Collection (Wii) .dsp.txth** +``` +codec = NGC_DSP +interleave = 0x10000 + +channels = 2 +start_offset = 0x00 + +num_samples = @0x00:BE +sample_rate = @0x08:BE +loop_flag = @0x0C:BE$2 +sample_type = bytes +loop_start_sample = @0x10:BE +loop_end_sample = @0x14:BE + +coef_offset = 0x1c +coef_spacing = 0x10000 +coef_endianness = BE +``` diff --git a/src/meta/txth.c b/src/meta/txth.c index 4a3656bf..24bb927b 100644 --- a/src/meta/txth.c +++ b/src/meta/txth.c @@ -99,6 +99,8 @@ typedef struct { uint32_t chunk_start; uint32_t chunk_size; uint32_t chunk_count; + uint32_t chunk_header_size; + uint32_t chunk_data_size; int chunk_start_set; int chunk_size_set; int chunk_count_set; @@ -637,6 +639,7 @@ static void set_body_chunk(txth_header * txth) { /* sets body "chunk" if all needed values are set * (done inline for padding/get_samples/etc calculators to work) */ + //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) @@ -652,8 +655,20 @@ static void set_body_chunk(txth_header * txth) { if (txth->chunk_number > txth->chunk_count) return; - temp_streamFile = setup_txth_streamfile(txth->streamBody, txth->chunk_start, txth->chunk_size, txth->chunk_count, txth->chunk_number - 1, txth->streambody_opened); - if (!temp_streamFile) return; + { + txth_io_config_data cfg = {0}; + + cfg.chunk_start = txth->chunk_start; + cfg.chunk_header_size = txth->chunk_header_size; + cfg.chunk_data_size = txth->chunk_data_size; + 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_streamFile = setup_txth_streamfile(txth->streamBody, cfg, txth->streambody_opened); + if (!temp_streamFile) return; + } + /* closing is handled by temp_streamFile */ //if (txth->streambody_opened) { @@ -1103,6 +1118,16 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char txth->chunk_start_set = 1; set_body_chunk(txth); } + else if (is_string(key,"chunk_header_size")) { + if (!parse_num(txth->streamHead,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->streamHead,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->streamHead,txth,val, &txth->chunk_size)) goto fail; txth->chunk_size_set = 1; @@ -1282,7 +1307,7 @@ static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * v } else { /* known field */ if ((n = is_substring(val,"interleave"))) value = txth->interleave; - if ((n = is_substring(val,"interleave_last"))) value = txth->interleave_last; + else if ((n = is_substring(val,"interleave_last"))) value = txth->interleave_last; else if ((n = is_substring(val,"channels"))) value = txth->channels; else if ((n = is_substring(val,"sample_rate"))) value = txth->sample_rate; else if ((n = is_substring(val,"start_offset"))) value = txth->start_offset; diff --git a/src/meta/txth_streamfile.h b/src/meta/txth_streamfile.h index b9bfbd0d..5419c815 100644 --- a/src/meta/txth_streamfile.h +++ b/src/meta/txth_streamfile.h @@ -4,12 +4,18 @@ typedef struct { - /* config */ - off_t stream_offset; - size_t stream_size; + off_t chunk_start; size_t chunk_size; + size_t chunk_header_size; + size_t chunk_data_size; int chunk_count; int chunk_number; +} txth_io_config_data; + +typedef struct { + /* config */ + txth_io_config_data cfg; + size_t stream_size; /* state */ off_t logical_offset; /* fake offset */ @@ -28,7 +34,7 @@ static size_t txth_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, /* re-start when previous offset (can't map logical<>physical offsets) */ if (data->logical_offset < 0 || offset < data->logical_offset) { - data->physical_offset = data->stream_offset; + data->physical_offset = data->cfg.chunk_start; data->logical_offset = 0x00; data->data_size = 0; data->skip_size = 0; @@ -38,15 +44,35 @@ static size_t txth_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, while (length > 0) { /* ignore EOF */ - if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) { + if (offset < 0 || data->physical_offset >= data->cfg.chunk_start + data->stream_size) { break; } /* process new block */ if (data->data_size == 0) { - data->block_size = data->chunk_size * data->chunk_count; - data->skip_size = data->chunk_size * data->chunk_number; - data->data_size = data->chunk_size; + /* base sizes */ + data->block_size = data->cfg.chunk_size * data->cfg.chunk_count; + data->skip_size = data->cfg.chunk_size * data->cfg.chunk_number; + data->data_size = data->cfg.chunk_size; + + /* chunk size modifiers */ + if (data->cfg.chunk_header_size) { + data->skip_size += data->cfg.chunk_header_size; + data->data_size -= data->cfg.chunk_header_size; + } + if (data->cfg.chunk_data_size) { + data->data_size = data->cfg.chunk_data_size; + } + + /* 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; + data->skip_size = (data->block_size / data->cfg.chunk_count) * data->cfg.chunk_number; + } + if (data->physical_offset + data->data_size > data->cfg.chunk_start + data->stream_size) { + data->data_size = (data->cfg.chunk_start + data->stream_size) - data->physical_offset; + } + } /* move to next block */ @@ -95,18 +121,15 @@ static size_t txth_io_size(STREAMFILE *streamfile, txth_io_data* data) { } /* Handles deinterleaving of generic chunked streams */ -static STREAMFILE* setup_txth_streamfile(STREAMFILE *streamFile, off_t chunk_start, size_t chunk_size, int chunk_count, int chunk_number, int is_opened_streamfile) { +static STREAMFILE* setup_txth_streamfile(STREAMFILE *streamFile, txth_io_config_data cfg, int is_opened_streamfile) { STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; txth_io_data io_data = {0}; size_t io_data_size = sizeof(txth_io_data); - io_data.stream_offset = chunk_start; - io_data.stream_size = (get_streamfile_size(streamFile) - chunk_start); - io_data.chunk_size = chunk_size; - io_data.chunk_count = chunk_count; - io_data.chunk_number = chunk_number; - io_data.logical_size = io_data.stream_size / chunk_count; + io_data.cfg = cfg; /* memcpy */ + io_data.stream_size = (get_streamfile_size(streamFile) - cfg.chunk_start); io_data.logical_offset = -1; /* force phys offset reset */ + //io_data.logical_size = io_data.stream_size / cfg.chunk_count; //todo would help with performance but not ok if data_size is set new_streamFile = streamFile;