Add TXTH dual file/bigfile/subsong/multiplier handling

- Can loading a separate header_file or body_file for dual files
- Can load from .txth directly while setting header_file/body_file for
bigfiles
- Can set value_multiplier for use with sector and similar values that
must be multiplied
- Can set subsong_count and subsong_offset for subsong headers suboffset
This commit is contained in:
bnnm 2018-11-24 01:45:04 +01:00
parent e8ca19ba2c
commit 976e1f3efe
3 changed files with 374 additions and 137 deletions

View File

@ -2,7 +2,7 @@
TXTH is a simple text file that uses text commands to simulate a header for files unsupported by vgmstream, mainly headerless audio.
When an unsupported file is loaded, vgmstream tries to find a TXTH header in the same dir, in this order:
When an unsupported file is loaded (for instance "bgm01.snd"), vgmstream tries to find a TXTH header in the same dir, in this order:
- (filename.ext).txth
- .(ext).txth
- .txth
@ -31,16 +31,16 @@ A text file with the above commands must be saved as ".vag.txth" or ".txth", not
```
######################################################
# comments start with #, can be inline
# The file is made lines of "key = value" describing a header.
# Spaces are optional: key=value, key= value, and so on are all ok.
# Comments start with #, can be inlined.
# Spaces are optional: key=value, key = value, and so on are all ok.
# The parser is fairly simple and may be buggy or unexpected in some cases.
# The order of keys is variable but some things won't work if others aren't defined
# (ex. bytes-to-samples may not work without channels or interleave).
# Common values:
# - (number): constant number in dec/hex.
# Examples: 44100, 40, 0x40 (dec=64)
# Examples: 44100, 40, 0x40 (decimal=64)
# - (offset): format is @(number)[:LE|BE][$1|2|3|4]
# * @(number): value at offset (required)
# * :LE|BE: value is little/big endian (optional, defaults to LE)
@ -52,73 +52,76 @@ A text file with the above commands must be saved as ".vag.txth" or ".txth", not
# Accepted codec strings:
# - PSX PlayStation ADPCM
# - XBOX Xbox IMA ADPCM
# - NGC_DTK Nintendo ADP/DTK ADPCM
# - PCM16BE PCM RAW 16bit big endian
# - PCM16LE PCM RAW 16bit little endian
# - PCM8 PCM RAW 8bit
# - NGC_DTK|DTK Nintendo ADP/DTK ADPCM
# - PCM16BE PCM 16-bit big endian
# - PCM16LE PCM 16-bit little endian
# - PCM8 PCM 8-bit
# - SDX2 Squareroot-delta-exact 8-bit DPCM (3DO games)
# - DVI_IMA DVI IMA ADPCM
# - MPEG MPEG Audio Layer File (MP1/2/3)
# - MPEG MPEG Audio Layer file (MP1/2/3)
# - IMA IMA ADPCM
# - AICA Yamaha AICA ADPCM (Dreamcast)
# - MSADPCM Microsoft ADPCM (Windows)
# - NGC_DSP Nintendo GameCube ADPCM
# - NGC_DSP|DSP Nintendo GameCube ADPCM
# - PCM8_U_int PCM RAW 8bit unsigned (interleaved)
# - PSX_bf PlayStation ADPCM with bad flags
# - MS_IMA Microsoft IMA ADPCM
# - PCM8_U PCM RAW 8bit unsigned
# - PCM8_U PCM 8-bit unsigned
# - APPLE_IMA4 Apple Quicktime IMA ADPCM
# - ATRAC3 raw ATRAC3
# - ATRAC3PLUS raw ATRAC3PLUS
# - XMA1 raw XMA1
# - XMA2 raw XMA2
# - FFMPEG any headered FFmpeg format
# - ATRAC3 Sony ATRAC3
# - ATRAC3PLUS Sony ATRAC3plus
# - XMA1 Microsoft XMA1
# - XMA2 Microsoft XMA2
# - FFMPEG Any headered FFmpeg format
# - AC3 AC3/SPDIF
codec = (codec string)
# Varies with codec:
# Codec variations [OPTIONAL, depends on codec]
# - NGC_DSP: 0=normal interleave, 1=byte interleave, 2=no interleave
# - ATRAC3: 0=autodetect joint stereo, 1=force joint stereo, 2=force normal stereo
# - XMA1|XMA2: 0=dual multichannel (2ch xN), 1=single multichannel (1ch xN)
# - XBOX: 0=standard (mono or stereo interleave), 1=force mono interleave mode
# - others: ignored
codec_mode = (number)
# Multiplies any next read values [OPTIONAL]
# Will change to "(key) = (number)|(offset) * value_multiplier",
# useful if a size is given in 0x800 sectors or such.
# Should be set to 0 when done using, as it affects ANY value.
value_multiplier = (number)|(offset)
# Interleave or block size [REQUIRED/OPTIONAL, depends on codec]
# For mono/interleaved codecs it's the amount of data between channels.
# For codecs with variable-sized frames (MSADPCM, MS-IMA, ATRAC3/plus)
# it's the block size (size of a single frame).
# - half_size: sets interleave as data_size / channels
# For mono/interleaved codecs it's the amount of data between channels,
# and for codecs with variable-sized frames (MSADPCM, MS-IMA, ATRAC3/plus)
# means block size (size of a single frame).
# Interleave 0 means "stereo mode" for some codecs (IMA, AICA, etc).
interleave = (number)|(offset)
interleave = (number)|(offset)|half_size
# Validate that id_value matches value at id_offset [OPTIONAL]
# Can be redefined several times, it's checked whenever a new id_offset is found.
id_value = (number)|(offset)
id_offset = (number)|(offset)
# Number of channels [REQUIRED]
channels = (number)|(offset)
# Music frequency in hz [REQUIRED]
sample_rate = (number)|(offset)
# Data start [OPTIONAL, default to 0]
start_offset = (number)|(offset)
# Variable that can be used in sample values [OPTIONAL]
# Defaults to (file_size - start_offset), re-calculated when start_offset is set.
# Defaults to (file_size - start_offset), re-calculated when start_offset
# is set (won't recalculate if data_size is set then start_offset changes).
data_size = (number)|(offset)
# Modifies the meaning of sample fields when set *before* them [OPTIONAL, defaults to samples]
# - samples: exact sample
# - bytes: automatically converts bytes/offset to samples
# - blocks: same as bytes, but value is given in blocks/frames
# Value is internally converted from blocks to bytes first: bytes = (value * interleave*channels)
# It's possible to re-define values multiple times:
# * samples_type=bytes ... num_samples=@0x10
# * samples_type=sample ... loop_end_sample=@0x14
# Sometimes "bytes" values are given for a single channel only. In that case you can temporally set 1 channel
# * channels=1 ... sample_type=bytes ... num_samples=@0x10 ... channels=2
# Some codecs can't convert bytes-to-samples at the moment: MPEG/FFMPEG
# For XMA1/2 bytes does special parsing, with loop values being bit offsets within data.
sample_type = samples|bytes|blocks
@ -128,29 +131,124 @@ num_samples = (number)|(offset)|data_size
loop_start_sample = (number)|(offset)
loop_end_sample = (number)|(offset)|data_size
# For XMA1/2 + sample_type=bytes it means loop subregion, if read after loop values.
# For other codecs its added to loop start/end, if read before loop values
# (rarely a format may have rough loop offset/bytes, then a loop adjust in samples).
loop_adjust = (number)|(offset)
# Force loop, on (>0) or off (0), as loop start/end may be defined but not used [OPTIONAL]
# - auto: tries to autodetect loop points for PS-ADPCM data, which may include loop flags.
# By default it loops when loop_end_sample is defined
# auto tries to autodetect loop points for PS-ADPCM data, which may include loop flags.
loop_flag = (number)|(offset)|auto
# beginning samples to skip (encoder delay), for codecs that need them (ATRAC3/XMA/etc)
# Loop start/end modifier [OPTIONAL]
# For XMA1/2 + sample_type=bytes it means loop subregion, if read after loop values.
# For other codecs its added to loop start/end, if read before loop values
# (a format may rarely have rough loop offset/bytes, then a loop adjust in samples).
loop_adjust = (number)|(offset)
# Beginning samples to skip (encoder delay) [OPTIONAL]
# Only some codecs use them (ATRAC3/ATRAC3PLUS/XMA/FFMPEG/AC3)
skip_samples = (number)|(offset)
# DSP coefficients [REQUIRED for NGC_DSP]
# Coefs start
# DSP decoding coefficients [REQUIRED for NGC_DSP]
# These coefs are a list of 8*2 16-bit values per channel, starting from offset.
coef_offset = (number)|(offset)
# offset separation per channel, usually 0x20
# - Example: channel N coefs are read at coef_offset + coef_spacing * N
# Offset separation per channel, usually 0x20 (16 values * 2 bytes)
# Channel N coefs are read at coef_offset + coef_spacing * N
coef_spacing = (number)|(offset)
# Format, usually BE; with (offset): 0=LE, >0=BE
coef_endianness = BE|LE|(offset)
# Split/normal coefs [NOT IMPLEMENTED YET]
#coef_mode = (number)|(offset)
# Change header/body to external files [OPTIONAL]
# TXTH commands are done on a "header", and decoding on "body".
# When loading an unsupported file it becomes the "base" file
# that loads the .txth, and is both header and body.
#
# You can alter those, mainly for files that split header and body
# in separate files (load base file and txth sets header on another file).
# It's also possible to load the .txth directly with a set body, as a sort of
# "reverse TXTH" (useful with bigfiles, as you could have one .txth per song).
#
# Allowed values:
# - (filename): open any file, subdirs also work (dir/filename)
# - *.(extension): opens with same name as the "base" file plus another extension
# - null: unloads file and goes back to defaults (body/header = base file).
header_file = (filename)|*.(extension)|null
body_file = (filename)|*.(extension)|null
# Subsongs [OPTIONAL]
# Sets the number of subsongs in the file, adjusting reads per subsong N:
# "value = @(offset) + subsong_offset*N". Mainly for bigfiles with consecutive
# headers per subsong, set subsong_offset to 0 when done as it affects any reads.
# The current subsong number is handled externally by plugins or TXTP.
subsong_count = (number)|(offset)
subsong_offset = (number)|(offset)
```
## 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.
For example, normally you are given a data_size in bytes, that can be used to calculate num_samples for all channels.
```
channels = 2
sample_type = bytes
num_samples = @0x10 #calculated from data_size
```
But sometimes this size is for a single channel only (even though the file may be stereo). You can set temporally change the channel number to force a correct calculation.
```
channels = 1 #not the actual number of channels
sample_type = bytes
num_samples = @0x10 #calculated from channel_size
channels = 2 #change once calculations are done
```
### Redefining values
Some commands alter the function of all next commands and can be redefined as needed:
```
samples_type = bytes
num_samples = @0x10
samples_type = sample
loop_end_sample = @0x14
```
### External files
When setting external files all commands are done on the "header" file, but with some creativity you can read in multiple files.
```
body_file = bgm01.bdy
header_file = bgm01.hdr
channels = @0x10 #base info in bgm01.hdr
header_file = bgm01.bdy
coef_offset = 0x00 #DSP coefs in bgm01.bdy
```
Note that DSP coefs are special in that aren't read immediately, and will use *last* header_file set.
### Resetting values
Values may need to be reset (to 0 or other sensible value) when done. Subsong example:
```
subsong_count = 5
subsong_offset = 0x20 # there are 5 subsong headers, 0x20 each
channel_count = @0x10 # reads channels at 0x10+0x20*subsong
# 1st subsong: 0x10+0x20*0: 0x10
# 2nd subsong: 0x10+0x20*1: 0x30
# 2nd subsong: 0x10+0x20*2: 0x50
# ...
start_offset = @0x14 # reads offset within data at 0x14+0x20*subsong
subsong_offset = 0 # reset value
sample_rate = 0x04 # sample rate is the same for all subsongs
# Nth subsong ch: 0x04+0x00*N: 0x08
```
### Multipliers
Sometimes header values are in "sectors" or similar concepts (typical in DVD games), and need to be adjusted to a real value.
```
value_multiplier = 0x800 # offsets are in DVD sector size
start_offset = @0x10 # 2*0x800, for example
value_multiplier = 0 # next values don't need to be multiplied
start_offset = @0x14
```

View File

@ -392,6 +392,7 @@ static const char* extension_list[] = {
"trj",
"trm",
"tun",
"txth",
"txtp",
"tydsp",

View File

@ -35,6 +35,7 @@ typedef enum {
typedef struct {
txth_type codec;
uint32_t codec_mode;
uint32_t value_multiplier;
uint32_t interleave;
uint32_t id_value;
@ -66,12 +67,29 @@ typedef struct {
int num_samples_data_size;
int target_subsong;
uint32_t subsong_count;
uint32_t subsong_offset;
/* original STREAMFILE and its type (may be an unsupported "base" file or a .txth) */
STREAMFILE *streamFile;
int streamfile_is_txth;
/* configurable STREAMFILEs and if we opened it (thus must close it later) */
STREAMFILE *streamText;
STREAMFILE *streamHead;
STREAMFILE *streamBody;
int streamtext_opened;
int streamhead_opened;
int streambody_opened;
} txth_header;
static STREAMFILE * open_txth(STREAMFILE * streamFile);
static int parse_txth(STREAMFILE * streamFile, STREAMFILE * streamText, txth_header * txth);
static int parse_keyval(STREAMFILE * streamFile, STREAMFILE * streamText, txth_header * txth, const char * key, const char * val);
static int parse_num(STREAMFILE * streamFile, const char * val, uint32_t * out_value);
static int parse_txth(txth_header * txth);
static int parse_keyval(STREAMFILE * streamFile, txth_header * txth, const char * key, char * val);
static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * val, uint32_t * out_value);
static int get_bytes_to_samples(txth_header * txth, uint32_t bytes);
@ -79,23 +97,42 @@ static int get_bytes_to_samples(txth_header * txth, uint32_t bytes);
* Similar to GENH, but with a single separate .txth file in the dir and text-based. */
VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
STREAMFILE * streamText = NULL;
txth_header txth = {0};
coding_t coding;
int i, j;
/* reject .txth as the CLI can open and decode with itself */
if (check_extensions(streamFile, "txth"))
goto fail;
/* accept .txth (should set body_file or will fail later) */
if (check_extensions(streamFile, "txth")) {
txth.streamFile = streamFile;
txth.streamfile_is_txth = 1;
/* no need for ID or ext checks -- if a .TXTH exists all is good
* (player still needs to accept the streamfile's ext, so at worst rename to .vgmstream) */
streamText = open_txth(streamFile);
if (!streamText) goto fail;
txth.streamText = streamFile;
txth.streamHead = NULL;
txth.streamBody = NULL;
txth.streamtext_opened = 0;
txth.streamhead_opened = 0;
txth.streambody_opened = 0;
}
else {
/* accept base file (no need for ID or ext checks --if a companion .TXTH exists all is good)
* (player still needs to accept the streamfile's ext, so at worst rename to .vgmstream) */
STREAMFILE * streamText = open_txth(streamFile);
if (!streamText) goto fail;
txth.streamFile = streamFile;
txth.streamfile_is_txth = 0;
txth.streamText = streamText;
txth.streamHead = streamFile;
txth.streamBody = streamFile;
txth.streamtext_opened = 1;
txth.streamhead_opened = 0;
txth.streambody_opened = 0;
}
/* process the text file */
if (!parse_txth(streamFile, streamText, &txth))
if (!parse_txth(&txth))
goto fail;
@ -135,8 +172,8 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
/* try to autodetect PS-ADPCM loop data */
if (txth.loop_flag_auto && coding == coding_PSX) {
size_t data_size = get_streamfile_size(streamFile) - txth.start_offset;
txth.loop_flag = ps_find_loop_offsets(streamFile, txth.start_offset, data_size, txth.channels, txth.interleave,
size_t data_size = get_streamfile_size(txth.streamBody) - txth.start_offset;
txth.loop_flag = ps_find_loop_offsets(txth.streamBody, txth.start_offset, data_size, txth.channels, txth.interleave,
(int32_t*)&txth.loop_start_sample, (int32_t*)&txth.loop_end_sample);
}
@ -149,6 +186,10 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
vgmstream->num_samples = txth.num_samples;
vgmstream->loop_start_sample = txth.loop_start_sample;
vgmstream->loop_end_sample = txth.loop_end_sample;
if (txth.subsong_count) {
vgmstream->num_streams = txth.subsong_count;
vgmstream->stream_size = txth.data_size;
}
/* codec specific (taken from GENH with minimal changes) */
switch (coding) {
@ -262,15 +303,15 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
/* normal/split coefs */
if (txth.coef_mode == 0) {
for (j=0;j<16;j++) {
vgmstream->ch[i].adpcm_coef[j] = read_16bit(txth.coef_offset + i*txth.coef_spacing + j*2,streamFile);
vgmstream->ch[i].adpcm_coef[j] = read_16bit(txth.coef_offset + i*txth.coef_spacing + j*2,txth.streamHead);
}
}
else {
goto fail; //IDK what is this
/*
for (j=0;j<8;j++) {
vgmstream->ch[i].adpcm_coef[j*2]=read_16bit(coef[i]+j*2,streamFile);
vgmstream->ch[i].adpcm_coef[j*2+1]=read_16bit(coef_splitted[i]+j*2,streamFile);
vgmstream->ch[i].adpcm_coef[j*2]=read_16bit(coef[i]+j*2,txth.streamHead);
vgmstream->ch[i].adpcm_coef[j*2+1]=read_16bit(coef_splitted[i]+j*2,txth.streamHead);
}
*/
}
@ -280,7 +321,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
#ifdef VGM_USE_MPEG
case coding_MPEG_layer3:
vgmstream->layout_type = layout_none;
vgmstream->codec_data = init_mpeg(streamFile, txth.start_offset, &coding, vgmstream->channels);
vgmstream->codec_data = init_mpeg(txth.streamBody, txth.start_offset, &coding, vgmstream->channels);
if (!vgmstream->codec_data) goto fail;
break;
@ -291,7 +332,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
if (txth.codec == FFMPEG || txth.codec == AC3) {
/* default FFmpeg */
ffmpeg_data = init_ffmpeg_offset(streamFile, txth.start_offset,txth.data_size);
ffmpeg_data = init_ffmpeg_offset(txth.streamBody, txth.start_offset,txth.data_size);
if ( !ffmpeg_data ) goto fail;
if (vgmstream->num_samples == 0)
@ -336,7 +377,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
goto fail;
}
ffmpeg_data = init_ffmpeg_header_offset(streamFile, buf,bytes, txth.start_offset,txth.data_size);
ffmpeg_data = init_ffmpeg_header_offset(txth.streamBody, buf,bytes, txth.start_offset,txth.data_size);
if ( !ffmpeg_data ) goto fail;
}
@ -344,7 +385,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
vgmstream->layout_type = layout_none;
if (txth.codec == XMA1 || txth.codec == XMA2) {
xma_fix_raw_samples(vgmstream, streamFile, txth.start_offset,txth.data_size, 0, 0,0);
xma_fix_raw_samples(vgmstream, txth.streamBody, txth.start_offset,txth.data_size, 0, 0,0);
} else if (txth.skip_samples_set) { /* force encoder delay */
ffmpeg_set_skip_samples(ffmpeg_data, txth.skip_samples);
}
@ -373,14 +414,13 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
msd.loop_end_subframe = txth.loop_adjust >> 4; /* upper 4b: subframe where the loop ends, 0..3 */
}
xma_get_samples(&msd, streamFile);
xma_get_samples(&msd, txth.streamBody);
vgmstream->num_samples = msd.num_samples;
if (txth.sample_type==1) {
vgmstream->loop_start_sample = msd.loop_start_sample;
vgmstream->loop_end_sample = msd.loop_end_sample;
}
//skip_samples = msd.skip_samples; //todo add skip samples
}
#endif
@ -389,14 +429,18 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
vgmstream->allow_dual_stereo = 1;
if ( !vgmstream_open_stream(vgmstream,streamFile,txth.start_offset) )
if ( !vgmstream_open_stream(vgmstream,txth.streamBody,txth.start_offset) )
goto fail;
if (streamText) close_streamfile(streamText);
if (txth.streamtext_opened) close_streamfile(txth.streamText);
if (txth.streamhead_opened) close_streamfile(txth.streamHead);
if (txth.streambody_opened) close_streamfile(txth.streamBody);
return vgmstream;
fail:
if (streamText) close_streamfile(streamText);
if (txth.streamtext_opened) close_streamfile(txth.streamText);
if (txth.streamhead_opened) close_streamfile(txth.streamHead);
if (txth.streambody_opened) close_streamfile(txth.streamBody);
close_vgmstream(vgmstream);
return NULL;
}
@ -434,15 +478,25 @@ static STREAMFILE * open_txth(STREAMFILE * streamFile) {
/* Simple text parser of "key = value" lines.
* The code is meh and error handling not exactly the best. */
static int parse_txth(STREAMFILE * streamFile, STREAMFILE * streamText, txth_header * txth) {
static int parse_txth(txth_header * txth) {
off_t txt_offset = 0x00;
off_t file_size = get_streamfile_size(streamText);
off_t file_size = get_streamfile_size(txth->streamText);
/* setup txth defaults */
if (txth->streamBody)
txth->data_size = get_streamfile_size(txth->streamBody);
txth->target_subsong = txth->streamFile->stream_index;
if (txth->target_subsong == 0) txth->target_subsong = 1;
txth->data_size = get_streamfile_size(streamFile); /* for later use */
/* skip BOM if needed */
if ((uint16_t)read_16bitLE(0x00, streamText) == 0xFFFE || (uint16_t)read_16bitLE(0x00, streamText) == 0xFEFF)
if ((uint16_t)read_16bitLE(0x00, txth->streamText) == 0xFFFE ||
(uint16_t)read_16bitLE(0x00, txth->streamText) == 0xFEFF) {
txt_offset = 0x02;
}
else if (((uint32_t)read_32bitBE(0x00, txth->streamText) & 0xFFFFFF00) == 0xEFBBBF00) {
txt_offset = 0x03;
}
/* read lines */
while (txt_offset < file_size) {
@ -450,7 +504,7 @@ static int parse_txth(STREAMFILE * streamFile, STREAMFILE * streamText, txth_hea
char key[TXT_LINE_MAX] = {0}, val[TXT_LINE_MAX] = {0}; /* at least as big as a line to avoid overflows (I hope) */
int ok, bytes_read, line_done;
bytes_read = get_streamfile_text_line(TXT_LINE_MAX,line, txt_offset,streamText, &line_done);
bytes_read = get_streamfile_text_line(TXT_LINE_MAX,line, txt_offset,txth->streamText, &line_done);
if (!line_done) goto fail;
txt_offset += bytes_read;
@ -460,49 +514,58 @@ static int parse_txth(STREAMFILE * streamFile, STREAMFILE * streamText, txth_hea
if (ok != 2) /* ignore line if no key=val (comment or garbage) */
continue;
if (!parse_keyval(streamFile, streamText, txth, key, val)) /* read key/val */
if (!parse_keyval(txth->streamFile, txth, key, val)) /* read key/val */
goto fail;
}
if (!txth->loop_flag_set)
txth->loop_flag = txth->loop_end_sample && txth->loop_end_sample != 0xFFFFFFFF;
if (!txth->streamBody)
goto fail;
return 1;
fail:
return 0;
}
static int parse_keyval(STREAMFILE * streamFile, STREAMFILE * streamText, txth_header * txth, const char * key, const char * val) {
static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char * key, char * val) {
//;VGM_LOG("TXTH: key=%s, val=%s\n", key, val);
if (0==strcmp(key,"codec")) {
if (0==strcmp(val,"PSX")) txth->codec = PSX;
else if (0==strcmp(val,"XBOX")) txth->codec = XBOX;
else if (0==strcmp(val,"NGC_DTK")) txth->codec = NGC_DTK;
else if (0==strcmp(val,"PCM16BE")) txth->codec = PCM16BE;
else if (0==strcmp(val,"PCM16LE")) txth->codec = PCM16LE;
else if (0==strcmp(val,"PCM8")) txth->codec = PCM8;
else if (0==strcmp(val,"SDX2")) txth->codec = SDX2;
else if (0==strcmp(val,"DVI_IMA")) txth->codec = DVI_IMA;
else if (0==strcmp(val,"MPEG")) txth->codec = MPEG;
else if (0==strcmp(val,"IMA")) txth->codec = IMA;
else if (0==strcmp(val,"AICA")) txth->codec = AICA;
else if (0==strcmp(val,"MSADPCM")) txth->codec = MSADPCM;
else if (0==strcmp(val,"NGC_DSP")) txth->codec = NGC_DSP;
else if (0==strcmp(val,"PCM8_U_int")) txth->codec = PCM8_U_int;
else if (0==strcmp(val,"PSX_bf")) txth->codec = PSX_bf;
else if (0==strcmp(val,"MS_IMA")) txth->codec = MS_IMA;
else if (0==strcmp(val,"PCM8_U")) txth->codec = PCM8_U;
else if (0==strcmp(val,"APPLE_IMA4")) txth->codec = APPLE_IMA4;
else if (0==strcmp(val,"ATRAC3")) txth->codec = ATRAC3;
else if (0==strcmp(val,"ATRAC3PLUS")) txth->codec = ATRAC3PLUS;
else if (0==strcmp(val,"XMA1")) txth->codec = XMA1;
else if (0==strcmp(val,"XMA2")) txth->codec = XMA2;
else if (0==strcmp(val,"FFMPEG")) txth->codec = FFMPEG;
else if (0==strcmp(val,"AC3")) txth->codec = AC3;
if (0==strcmp(val,"PSX")) txth->codec = PSX;
else if (0==strcmp(val,"XBOX")) txth->codec = XBOX;
else if (0==strcmp(val,"NGC_DTK")) txth->codec = NGC_DTK;
else if (0==strcmp(val,"DTK")) txth->codec = NGC_DTK;
else if (0==strcmp(val,"PCM16BE")) txth->codec = PCM16BE;
else if (0==strcmp(val,"PCM16LE")) txth->codec = PCM16LE;
else if (0==strcmp(val,"PCM8")) txth->codec = PCM8;
else if (0==strcmp(val,"SDX2")) txth->codec = SDX2;
else if (0==strcmp(val,"DVI_IMA")) txth->codec = DVI_IMA;
else if (0==strcmp(val,"MPEG")) txth->codec = MPEG;
else if (0==strcmp(val,"IMA")) txth->codec = IMA;
else if (0==strcmp(val,"AICA")) txth->codec = AICA;
else if (0==strcmp(val,"MSADPCM")) txth->codec = MSADPCM;
else if (0==strcmp(val,"NGC_DSP")) txth->codec = NGC_DSP;
else if (0==strcmp(val,"DSP")) txth->codec = NGC_DSP;
else if (0==strcmp(val,"PCM8_U_int")) txth->codec = PCM8_U_int;
else if (0==strcmp(val,"PSX_bf")) txth->codec = PSX_bf;
else if (0==strcmp(val,"MS_IMA")) txth->codec = MS_IMA;
else if (0==strcmp(val,"PCM8_U")) txth->codec = PCM8_U;
else if (0==strcmp(val,"APPLE_IMA4")) txth->codec = APPLE_IMA4;
else if (0==strcmp(val,"ATRAC3")) txth->codec = ATRAC3;
else if (0==strcmp(val,"ATRAC3PLUS")) txth->codec = ATRAC3PLUS;
else if (0==strcmp(val,"XMA1")) txth->codec = XMA1;
else if (0==strcmp(val,"XMA2")) txth->codec = XMA2;
else if (0==strcmp(val,"FFMPEG")) txth->codec = FFMPEG;
else if (0==strcmp(val,"AC3")) txth->codec = AC3;
else goto fail;
}
else if (0==strcmp(key,"codec_mode")) {
if (!parse_num(streamFile,val, &txth->codec_mode)) goto fail;
if (!parse_num(txth->streamHead,txth,val, &txth->codec_mode)) goto fail;
}
else if (0==strcmp(key,"value_multiplier")) {
if (!parse_num(txth->streamHead,txth,val, &txth->value_multiplier)) goto fail;
}
else if (0==strcmp(key,"interleave")) {
if (0==strcmp(val,"half_size")) {
@ -510,30 +573,32 @@ static int parse_keyval(STREAMFILE * streamFile, STREAMFILE * streamText, txth_h
txth->interleave = txth->data_size / txth->channels;
}
else {
if (!parse_num(streamFile,val, &txth->interleave)) goto fail;
if (!parse_num(txth->streamHead,txth,val, &txth->interleave)) goto fail;
}
}
else if (0==strcmp(key,"id_value")) {
if (!parse_num(streamFile,val, &txth->id_value)) goto fail;
if (!parse_num(txth->streamHead,txth,val, &txth->id_value)) goto fail;
}
else if (0==strcmp(key,"id_offset")) {
if (!parse_num(streamFile,val, &txth->id_offset)) goto fail;
if (!parse_num(txth->streamHead,txth,val, &txth->id_offset)) goto fail;
if (txth->id_value != txth->id_offset) /* evaluate current ID */
goto fail;
}
else if (0==strcmp(key,"channels")) {
if (!parse_num(streamFile,val, &txth->channels)) goto fail;
if (!parse_num(txth->streamHead,txth,val, &txth->channels)) goto fail;
}
else if (0==strcmp(key,"sample_rate")) {
if (!parse_num(streamFile,val, &txth->sample_rate)) goto fail;
if (!parse_num(txth->streamHead,txth,val, &txth->sample_rate)) goto fail;
}
else if (0==strcmp(key,"start_offset")) {
if (!parse_num(streamFile,val, &txth->start_offset)) goto fail;
if (!txth->data_size_set)
txth->data_size = get_streamfile_size(streamFile) - txth->start_offset; /* re-evaluate */
if (!parse_num(txth->streamHead,txth,val, &txth->start_offset)) goto fail;
if (!txth->data_size_set) {
txth->data_size = !txth->streamBody ? 0 :
get_streamfile_size(txth->streamBody) - txth->start_offset; /* re-evaluate */
}
}
else if (0==strcmp(key,"data_size")) {
if (!parse_num(streamFile,val, &txth->data_size)) goto fail;
if (!parse_num(txth->streamHead,txth,val, &txth->data_size)) goto fail;
txth->data_size_set = 1;
}
else if (0==strcmp(key,"sample_type")) {
@ -548,7 +613,7 @@ static int parse_keyval(STREAMFILE * streamFile, STREAMFILE * streamText, txth_h
txth->num_samples_data_size = 1;
}
else {
if (!parse_num(streamFile,val, &txth->num_samples)) goto fail;
if (!parse_num(txth->streamHead,txth,val, &txth->num_samples)) goto fail;
if (txth->sample_type==1)
txth->num_samples = get_bytes_to_samples(txth, txth->num_samples);
if (txth->sample_type==2)
@ -556,7 +621,7 @@ static int parse_keyval(STREAMFILE * streamFile, STREAMFILE * streamText, txth_h
}
}
else if (0==strcmp(key,"loop_start_sample")) {
if (!parse_num(streamFile,val, &txth->loop_start_sample)) goto fail;
if (!parse_num(txth->streamHead,txth,val, &txth->loop_start_sample)) goto fail;
if (txth->sample_type==1)
txth->loop_start_sample = get_bytes_to_samples(txth, txth->loop_start_sample);
if (txth->sample_type==2)
@ -569,7 +634,7 @@ static int parse_keyval(STREAMFILE * streamFile, STREAMFILE * streamText, txth_h
txth->loop_end_sample = get_bytes_to_samples(txth, txth->data_size);
}
else {
if (!parse_num(streamFile,val, &txth->loop_end_sample)) goto fail;
if (!parse_num(txth->streamHead,txth,val, &txth->loop_end_sample)) goto fail;
if (txth->sample_type==1)
txth->loop_end_sample = get_bytes_to_samples(txth, txth->loop_end_sample);
if (txth->sample_type==2)
@ -579,7 +644,7 @@ static int parse_keyval(STREAMFILE * streamFile, STREAMFILE * streamText, txth_h
txth->loop_end_sample += txth->loop_adjust;
}
else if (0==strcmp(key,"skip_samples")) {
if (!parse_num(streamFile,val, &txth->skip_samples)) goto fail;
if (!parse_num(txth->streamHead,txth,val, &txth->skip_samples)) goto fail;
txth->skip_samples_set = 1;
if (txth->sample_type==1)
txth->skip_samples = get_bytes_to_samples(txth, txth->skip_samples);
@ -587,7 +652,7 @@ static int parse_keyval(STREAMFILE * streamFile, STREAMFILE * streamText, txth_h
txth->skip_samples = get_bytes_to_samples(txth, txth->skip_samples * (txth->interleave*txth->channels));
}
else if (0==strcmp(key,"loop_adjust")) {
if (!parse_num(streamFile,val, &txth->loop_adjust)) goto fail;
if (!parse_num(txth->streamHead,txth,val, &txth->loop_adjust)) goto fail;
if (txth->sample_type==1)
txth->loop_adjust = get_bytes_to_samples(txth, txth->loop_adjust);
if (txth->sample_type==2)
@ -598,28 +663,87 @@ static int parse_keyval(STREAMFILE * streamFile, STREAMFILE * streamText, txth_h
txth->loop_flag_auto = 1;
}
else {
if (!parse_num(streamFile,val, &txth->loop_flag)) goto fail;
if (!parse_num(txth->streamHead,txth,val, &txth->loop_flag)) goto fail;
txth->loop_flag_set = 1;
}
}
else if (0==strcmp(key,"coef_offset")) {
if (!parse_num(streamFile,val, &txth->coef_offset)) goto fail;
if (!parse_num(txth->streamHead,txth,val, &txth->coef_offset)) goto fail;
}
else if (0==strcmp(key,"coef_spacing")) {
if (!parse_num(streamFile,val, &txth->coef_spacing)) goto fail;
if (!parse_num(txth->streamHead,txth,val, &txth->coef_spacing)) goto fail;
}
else if (0==strcmp(key,"coef_endianness")) {
if (val[0]=='B' && val[1]=='E')
txth->coef_big_endian = 1;
else if (val[0]=='L' && val[1]=='E')
txth->coef_big_endian = 0;
else if (!parse_num(streamFile,val, &txth->coef_big_endian)) goto fail;
else if (!parse_num(txth->streamHead,txth,val, &txth->coef_big_endian)) goto fail;
}
else if (0==strcmp(key,"coef_mode")) {
if (!parse_num(streamFile,val, &txth->coef_mode)) goto fail;
if (!parse_num(txth->streamHead,txth,val, &txth->coef_mode)) goto fail;
}
else if (0==strcmp(key,"psx_loops")) {
if (!parse_num(streamFile,val, &txth->coef_mode)) goto fail;
if (!parse_num(txth->streamHead,txth,val, &txth->coef_mode)) goto fail;
}
else if (0==strcmp(key,"subsong_count")) {
if (!parse_num(txth->streamHead,txth,val, &txth->subsong_count)) goto fail;
}
else if (0==strcmp(key,"subsong_offset")) {
if (!parse_num(txth->streamHead,txth,val, &txth->subsong_offset)) goto fail;
}
else if (0==strcmp(key,"header_file")) {
if (txth->streamhead_opened) {
close_streamfile(txth->streamHead);
txth->streamHead = NULL;
txth->streamhead_opened = 0;
}
if (0==strcmp(val,"null")) { /* reset */
if (!txth->streamfile_is_txth) {
txth->streamHead = txth->streamFile;
}
}
else if (val[0]=='*' && val[1]=='.') { /* basename + extension */
txth->streamHead = open_streamfile_by_ext(txth->streamFile, (val+2));
if (!txth->streamHead) goto fail;
txth->streamhead_opened = 1;
}
else { /* open file */
fix_dir_separators(val); /* clean paths */
txth->streamHead = open_streamfile_by_filename(txth->streamFile, val);
if (!txth->streamHead) goto fail;
txth->streamhead_opened = 1;
}
}
else if (0==strcmp(key,"body_file")) {
if (txth->streambody_opened) {
close_streamfile(txth->streamBody);
txth->streamBody = NULL;
txth->streambody_opened = 0;
}
if (0==strcmp(val,"null")) { /* reset */
if (!txth->streamfile_is_txth) {
txth->streamBody = txth->streamFile;
}
}
else if (val[0]=='*' && val[1]=='.') { /* basename + extension */
txth->streamBody = open_streamfile_by_ext(txth->streamFile, (val+2));
if (!txth->streamBody) goto fail;
txth->streambody_opened = 1;
}
else { /* open file */
fix_dir_separators(val); /* clean paths */
txth->streamBody = open_streamfile_by_filename(txth->streamFile, val);
if (!txth->streamBody) goto fail;
txth->streambody_opened = 1;
}
txth->data_size = !txth->streamBody ? 0 :
get_streamfile_size(txth->streamBody) - txth->start_offset; /* re-evaluate */
}
else {
VGM_LOG("TXTH: unknown key=%s, val=%s\n", key,val);
@ -631,27 +755,34 @@ fail:
return 0;
}
static int parse_num(STREAMFILE * streamFile, const char * val, uint32_t * out_value) {
static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * val, uint32_t * out_value) {
/* out_value can be these values, save before modifying */
uint32_t value_multiplier = txth->value_multiplier;
uint32_t subsong_offset = txth->subsong_offset;
if (val[0] == '@') { /* offset */
uint32_t off = 0;
uint32_t offset = 0;
char ed1 = 'L', ed2 = 'E';
int size = 4;
int big_endian = 0;
int hex = (val[1]=='0' && val[2]=='x');
/* can happen when loading .txth and not setting body/head */
if (!streamFile)
goto fail;
/* read exactly N fields in the expected format */
if (strchr(val,':') && strchr(val,'$')) {
if (sscanf(val, hex ? "@%x:%c%c$%i" : "@%u:%c%c$%i", &off, &ed1,&ed2, &size) != 4) goto fail;
if (sscanf(val, hex ? "@%x:%c%c$%i" : "@%u:%c%c$%i", &offset, &ed1,&ed2, &size) != 4) goto fail;
} else if (strchr(val,':')) {
if (sscanf(val, hex ? "@%x:%c%c" : "@%u:%c%c", &off, &ed1,&ed2) != 3) goto fail;
if (sscanf(val, hex ? "@%x:%c%c" : "@%u:%c%c", &offset, &ed1,&ed2) != 3) goto fail;
} else if (strchr(val,'$')) {
if (sscanf(val, hex ? "@%x$%i" : "@%u$%i", &off, &size) != 2) goto fail;
if (sscanf(val, hex ? "@%x$%i" : "@%u$%i", &offset, &size) != 2) goto fail;
} else {
if (sscanf(val, hex ? "@%x" : "@%u", &off) != 1) goto fail;
if (sscanf(val, hex ? "@%x" : "@%u", &offset) != 1) goto fail;
}
if (off < 0 || off > get_streamfile_size(streamFile))
if (offset < 0 || offset > get_streamfile_size(streamFile))
goto fail;
if (ed1 == 'B' && ed2 == 'E')
@ -659,21 +790,28 @@ static int parse_num(STREAMFILE * streamFile, const char * val, uint32_t * out_v
else if (!(ed1 == 'L' && ed2 == 'E'))
goto fail;
if (subsong_offset)
offset = offset + subsong_offset * (txth->target_subsong - 1);
switch(size) {
case 1: *out_value = read_8bit(off,streamFile); break;
case 2: *out_value = big_endian ? (uint16_t)read_16bitBE(off,streamFile) : (uint16_t)read_16bitLE(off,streamFile); break;
case 3: *out_value = (big_endian ? (uint32_t)read_32bitBE(off,streamFile) : (uint32_t)read_32bitLE(off,streamFile)) & 0x00FFFFFF; break;
case 4: *out_value = big_endian ? (uint32_t)read_32bitBE(off,streamFile) : (uint32_t)read_32bitLE(off,streamFile); break;
case 1: *out_value = read_8bit(offset,streamFile); break;
case 2: *out_value = big_endian ? (uint16_t)read_16bitBE(offset,streamFile) : (uint16_t)read_16bitLE(offset,streamFile); break;
case 3: *out_value = (big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile)) & 0x00FFFFFF; break;
case 4: *out_value = big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile); break;
default: goto fail;
}
}
else { /* constant */
int hex = (val[0]=='0' && val[1]=='x');
if (sscanf(val, hex ? "%x" : "%u", out_value)!=1) goto fail;
if (sscanf(val, hex ? "%x" : "%u", out_value)!=1)
goto fail;
}
//VGM_LOG("TXTH: value=%s, read %u\n", val, *out_value);
if (value_multiplier)
*out_value = (*out_value) * value_multiplier;
//;VGM_LOG("TXTH: val=%s, read %u (0x%x)\n", val, *out_value, *out_value);
return 1;
fail:
return 0;