mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-29 19:37:30 +01:00
commit
86c104c97e
15
README.md
15
README.md
@ -256,7 +256,7 @@ formats.
|
||||
## Tagging
|
||||
Some of vgmstream's plugins support simple read-only tagging via external files.
|
||||
|
||||
Tags are loaded from a text/M3U-like file named _!tags.m3u_ in the song folder.
|
||||
Tags are loaded from a text/M3U-like file named *!tags.m3u* in the song folder.
|
||||
You don't have to load your songs with that M3U though (but you can, for pre-made
|
||||
ordering), the file itself just 'looks' like an M3U.
|
||||
|
||||
@ -279,13 +279,20 @@ _filename_ though, so any @TAG below would be ignored.
|
||||
|
||||
Playlist formatting should follow player's config. ASCII or UTF-8 tags work.
|
||||
|
||||
GLOBAL_COMMANDs currently can be:
|
||||
GLOBAL_COMMANDs currently can be:
|
||||
- AUTOTRACK: sets %TRACK% tag automatically (1..N as files are encountered
|
||||
in the tag file).
|
||||
- AUTOALBUM: sets %ALBUM% tag automatically using the containing dir as album.
|
||||
|
||||
foobar2000 can apply the following replaygain tags (if ReplayGain is enabled
|
||||
in foobar's preferences):
|
||||
Note that since you can use global tags don't need to put all files inside.
|
||||
This would be a perfectly valid *!tags.m3u*:
|
||||
```
|
||||
# @ALBUM Game
|
||||
# @ARTIST Various Artists
|
||||
```
|
||||
|
||||
foobar2000/Winamp can apply the following replaygain tags (if ReplayGain is
|
||||
enabled in preferences):
|
||||
```
|
||||
# %replaygain_track_gain N.NN dB
|
||||
# %replaygain_track_peak N.NNN
|
||||
|
123
doc/TXTH.md
123
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
|
||||
```
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* FEV+FSB5 container [Shantae: Half-Genie Hero (Switch)] */
|
||||
/* FEV+FSB5 container [Just Cause 3 (PC), Shantae: Half-Genie Hero (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_fsb5_fev_bank(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
STREAMFILE *temp_streamFile = NULL;
|
||||
|
@ -35,6 +35,8 @@ static int read_dsp_header_endian(struct dsp_header *header, off_t offset, STREA
|
||||
int i;
|
||||
uint8_t buf[0x4e];
|
||||
|
||||
if (offset > get_streamfile_size(streamFile))
|
||||
return 1;
|
||||
if (read_streamfile(buf, offset, 0x4e, streamFile) != 0x4e)
|
||||
return 1;
|
||||
header->sample_count = get_32bit(buf+0x00);
|
||||
@ -270,14 +272,29 @@ VGMSTREAM * init_vgmstream_ngc_dsp_std(STREAMFILE *streamFile) {
|
||||
* out thoroughly, we're probably not dealing with a genuine mono DSP.
|
||||
* In many cases these will pass all the other checks, including the
|
||||
* predictor/scale check if the first byte is 0 */
|
||||
//todo maybe this meta should be after others, so they have a chance to detect >1ch .dsp
|
||||
{
|
||||
int ko;
|
||||
struct dsp_header header2;
|
||||
read_dsp_header(&header2, header_size, streamFile);
|
||||
|
||||
if (header.sample_count == header2.sample_count &&
|
||||
header.nibble_count == header2.nibble_count &&
|
||||
header.sample_rate == header2.sample_rate &&
|
||||
header.loop_flag == header2.loop_flag) {
|
||||
/* ignore headers one after another */
|
||||
ko = read_dsp_header(&header2, header_size, streamFile);
|
||||
if (!ko &&
|
||||
header.sample_count == header2.sample_count &&
|
||||
header.nibble_count == header2.nibble_count &&
|
||||
header.sample_rate == header2.sample_rate &&
|
||||
header.loop_flag == header2.loop_flag) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
/* ignore headers after interleave [Ultimate Board Collection (Wii)] */
|
||||
ko = read_dsp_header(&header2, 0x10000, streamFile);
|
||||
if (!ko &&
|
||||
header.sample_count == header2.sample_count &&
|
||||
header.nibble_count == header2.nibble_count &&
|
||||
header.sample_rate == header2.sample_rate &&
|
||||
header.loop_flag == header2.loop_flag) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -1875,6 +1875,8 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) {
|
||||
* - base varies per type (0xF0=audio), skip 0x20
|
||||
* - 0x74: type, 0x78: channels, 0x7c: sample rate, 0x80: num_samples
|
||||
* - 0x94: stream id? 0x9C: extra size */
|
||||
case 0x002A0300: /* Watch Dogs (Wii U) */
|
||||
/* similar to SC:B */
|
||||
default: /* others possibly using BAO: Just Dance, Watch_Dogs, Far Cry Primal, Far Cry 4 */
|
||||
VGM_LOG("UBI BAO: unknown BAO version %08x\n", bao->version);
|
||||
return 0;
|
||||
|
@ -5,7 +5,7 @@
|
||||
VGMSTREAM * init_vgmstream_ps2_vpk(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
int loop_flag, channel_count;
|
||||
off_t start_offset;
|
||||
off_t start_offset, loop_channel_offset;
|
||||
size_t channel_size;
|
||||
|
||||
|
||||
@ -16,13 +16,14 @@ VGMSTREAM * init_vgmstream_ps2_vpk(STREAMFILE *streamFile) {
|
||||
if (read_32bitBE(0x00,streamFile) != 0x204B5056) /* " KPV" */
|
||||
goto fail;
|
||||
|
||||
/* seems this consistently has 0x10-0x20 extra bytes, landing in garbage frames at the end */
|
||||
/* seems this consistently has 0x10-0x20 extra bytes, landing in garbage 0xC00000..00 frames at the end */
|
||||
channel_size = read_32bitLE(0x04,streamFile) - 0x20; /* remove for cleaner ends */
|
||||
|
||||
start_offset = read_32bitLE(0x08,streamFile);
|
||||
channel_count = read_32bitLE(0x14,streamFile);
|
||||
/* 0x18+(per channel): channel config? */
|
||||
loop_flag = (read_32bitLE(0x7FC,streamFile) != 0); /* used? */
|
||||
/* 0x18+(per channel): channel config(?) */
|
||||
loop_channel_offset = read_32bitLE(0x7FC,streamFile);
|
||||
loop_flag = (loop_channel_offset != 0); /* found in Sly 2/3 */
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
@ -32,7 +33,7 @@ VGMSTREAM * init_vgmstream_ps2_vpk(STREAMFILE *streamFile) {
|
||||
vgmstream->sample_rate = read_32bitLE(0x10,streamFile);
|
||||
vgmstream->num_samples = ps_bytes_to_samples(channel_size*vgmstream->channels,vgmstream->channels);
|
||||
if (vgmstream->loop_flag) {
|
||||
vgmstream->loop_start_sample = read_32bitLE(0x7FC,streamFile);
|
||||
vgmstream->loop_start_sample = ps_bytes_to_samples(loop_channel_offset*vgmstream->channels,vgmstream->channels);
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user