mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-24 06:50:20 +01:00
Add more TXTH chunk options
This commit is contained in:
parent
d864d0ce45
commit
c1e0143881
36
doc/TXTH.md
36
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
|
||||
|
@ -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")) {
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user