Merge pull request #886 from bnnm/sspr-cli

- Add CLI option to write multiple subsongs
- Add Capcom .sspr [Sengoku Basara 4 (PS3/PS4)]
- Add HCA key [Dragon Quest Tact (Android)]
- Add .d2 extension [Dodonpachi Dai-Ou-Jou (PS2)]
- Fix some TXTH/TXTP + CLI path issues
- Optimize PSX padding finder
- Fix some auto L+R issues [Gift: Prism (PS2)]
This commit is contained in:
bnnm 2021-07-09 00:14:02 +02:00 committed by GitHub
commit 79f81ac3ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 256 additions and 103 deletions

View File

@ -28,14 +28,14 @@ More technical docs: https://github.com/vgmstream/vgmstream/tree/master/doc
## Usage
There are multiple end-user bits:
- a command line decoder called "test.exe/vgmstream-cli"
- a Winamp plugin called "in_vgmstream"
- a foobar2000 component called "foo_input_vgmstream"
- an XMPlay plugin called "xmp-vgmstream"
- an Audacious plugin called "libvgmstream"
- a command line player called "vgmstream123"
- a command line decoder called *test.exe/vgmstream-cli*
- a Winamp plugin called *in_vgmstream*
- a foobar2000 component called *foo_input_vgmstream*
- an XMPlay plugin called *xmp-vgmstream*
- an Audacious plugin called *libvgmstream*
- a command line player called *vgmstream123*
Main lib (plain vgmstream) is the code that handles internal conversion, while the
Main lib (plain *vgmstream*) is the code that handles internal conversion, while the
above components are what you use to actually get sound. See *components* below for
explanations about each one.
@ -97,28 +97,31 @@ files to the executable to decode them as `(filename.ext).wav`.
There are multiple options that alter how the file is converted, for example:
- `test.exe -m file.adx`: print info but don't decode
- `test.exe -i -o file_noloop.wav file.hca`: convert without looping
- `test.exe -s 2 -F file.fsb`: play 2nd subsong + ending after 2.0 loops
- `test.exe -s 2 -F file.fsb`: write 2nd subsong + ending after 2.0 loops
- `test.exe -l 3.0 -f 5.0 -d 3.0 file.wem`: 3 loops, 3s delay, 5s fade
- `test.exe -o bgm_?f.wav file1.adx file2.adx`: convert multiple files to `bgm_(name).wav`
Available commands are printed when run with no flags. Note that you can also
achieve similar results for other plugins using TXTP, described later.
With files multiple subsongs you need to specify manually subsong (by design, to avoid
massive data dumps since some formats have hundreds of subsongs), but you could do
some command line tricks:
```
: REM extracts from subsong 5 to 10 in file.fsb
for /L %A in (5,1,10) do test.exe -s %A -o file_%A.wav file.fsb
```
Output filename in `-o` may use multiple wildcards:
Output filename in `-o` may use wildcards:
- `?s`: sets current subsong (or 0 if format doesn't have subsongs)
- `?0Ns`: same, but left pads subsong with up to `N` zeroes
- `?n`: internal stream name, or input filename if format doesn't have name
- `?f`: input filename
For example `test.exe -s 2 -o ?04s_?n.wav file.fsb` could generate `0002_song1.wav`
For example `test.exe -s 2 -o ?04s_?n.wav file.fsb` could generate `0002_song1.wav`.
Default output filename is `?f.wav`, or `?f#?s.wav` if you set subsongs (`-s/S`).
For files containing multiple subsongs, you can write them all using some flags.
**WARNING, MAY TAKE A LOT OF SPACE!** Some files have been observed to contain +20000
subsongs, so don't use this lightly. Remember to set an output name (`-o`) with subsong
wilcards (or leave it alone for the defaults).
- `test.exe -s 1 -S 100 file.bank`: writes from subsong 1 to subsong 100
- `test.exe -s 101 -S 0 file.bank`: writes from subsong 101 to max subsong
- `test.exe -S 0 file.bank`: writes from subsong 1 to max subsong (automatically changes 0 to max)
- `test.exe -s 1 -S 5 -o bgm.wav file.bank`: writes 5 subsongs, but all overwrite the same file = wrong.
- `test.exe -s 1 -S 5 -o bgm_?02s.wav file.bank`: writes 5 subsongs, each named differently = correct.
### in_vgmstream (Winamp plugin)
@ -397,6 +400,7 @@ file, or static values. This allows vgmstream to play unsupported formats.
more functions, plus doesn't modify original data.
Usage example (used when opening an unknown file named `bgm_01.pcm`):
**.pcm.txth**
```
codec = PCM16LE
@ -421,6 +425,7 @@ per-file configurations like number of loops, remove unneeded channels,
force looping, and many other features.
Usage examples (open directly, name can be set freely):
**bgm01-full.txtp**
```
# plays 2 files as a single one
@ -599,6 +604,7 @@ enabled in preferences):
### TXTP matching
To ease *TXTP* config, tags with plain files will match `.txtp` with config, and tags
with `.txtp` config also match plain files:
**!tags.m3u**
```
# @TITLE Title1
@ -620,6 +626,7 @@ BGM01.adx #P 10.0.txtp
Since it matches when a tag is found, some cases that depend on order won't work.
You can disable this feature manually then:
**!tags.m3u**
```
# $EXACTMATCH

View File

@ -50,6 +50,7 @@ static void usage(const char* name, int is_full) {
" -e: force end-to-end looping\n"
" -E: force end-to-end looping even if file has real loop points\n"
" -s N: select subsong N, if the format supports multiple subsongs\n"
" -S N: select end subsong (set 0 for 'all')\n"
" -m: print metadata only, don't decode\n"
" -L: append a smpl chunk and create a looping wav\n"
" -2 N: only output the Nth (first is 0) set of stereo channels\n"
@ -103,7 +104,8 @@ typedef struct {
int print_batchvar;
int write_lwav;
int only_stereo;
int stream_index;
int subsong_index;
int subsong_end;
double loop_count;
double fade_time;
@ -145,7 +147,7 @@ static int parse_config(cli_config* cfg, int argc, char** argv) {
opterr = 0;
/* read config */
while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:t:Tk:K:hOvD:"
while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:t:Tk:K:hOvD:S:"
#ifdef HAVE_JSON
"VI"
#endif
@ -207,10 +209,17 @@ static int parse_config(cli_config* cfg, int argc, char** argv) {
cfg->ignore_fade = 1;
break;
case 's':
cfg->stream_index = atoi(optarg);
cfg->subsong_index = atoi(optarg);
break;
case 'S':
cfg->subsong_end = atoi(optarg);
if (!cfg->subsong_end)
cfg->subsong_end = -1; /* signal up to end (otherwise 0 = not set) */
if (!cfg->subsong_index)
cfg->subsong_index = 1;
break;
case 't':
cfg->tag_filename= optarg;
cfg->tag_filename = optarg;
break;
case 'T':
cfg->show_title = 1;
@ -554,8 +563,10 @@ void replace_filename(char* dst, size_t dstsize, const char* outfilename, const
/* ************************************************************ */
static int convert_file(cli_config* cfg);
static int convert_subsongs(cli_config* cfg);
static int write_file(VGMSTREAM* vgmstream, cli_config* cfg);
int main(int argc, char** argv) {
cli_config cfg = {0};
int i, res, ok;
@ -582,11 +593,19 @@ int main(int argc, char** argv) {
if (cfg.outfilename_config)
cfg.outfilename = NULL;
res = convert_file(&cfg);
//if (!res) goto fail; /* keep on truckin' */
if (res) ok = 1; /* return ok if at least one succeeds, for programs that check result code */
if (cfg.subsong_index > 0 && cfg.subsong_end != 0) {
res = convert_subsongs(&cfg);
//if (!res) goto fail;
if (res) ok = 1;
}
else {
res = convert_file(&cfg);
//if (!res) goto fail;
if (res) ok = 1;
}
}
/* ok if at least one succeeds, for programs that check result code */
if (!ok)
goto fail;
@ -595,6 +614,40 @@ fail:
return EXIT_FAILURE;
}
static int convert_subsongs(cli_config* cfg) {
int res;
int subsong;
/* restore original values in case of multiple parsed files */
int start_temp = cfg->subsong_index;
int end_temp = cfg->subsong_end;
/* first call should force load max subsongs */
if (cfg->subsong_end == -1) {
res = convert_file(cfg);
if (!res) goto fail;
}
//;VGM_LOG("CLI: subsongs %i to %i\n", cfg->subsong_index, cfg->subsong_end + 1);
/* convert subsong range */
for (subsong = cfg->subsong_index; subsong < cfg->subsong_end + 1; subsong++) {
cfg->subsong_index = subsong;
res = convert_file(cfg);
if (!res) goto fail;
}
cfg->subsong_index = start_temp;
cfg->subsong_end = end_temp;
return 1;
fail:
cfg->subsong_index = start_temp;
cfg->subsong_end = end_temp;
return 0;
}
static int convert_file(cli_config* cfg) {
VGMSTREAM* vgmstream = NULL;
char outfilename_temp[PATH_LIMIT];
@ -623,7 +676,7 @@ static int convert_file(cli_config* cfg) {
goto fail;
}
sf->stream_index = cfg->stream_index;
sf->stream_index = cfg->subsong_index;
vgmstream = init_vgmstream_from_STREAMFILE(sf);
close_streamfile(sf);
@ -631,6 +684,13 @@ static int convert_file(cli_config* cfg) {
fprintf(stderr, "failed opening %s\n", cfg->infilename);
goto fail;
}
/* force load total subsongs if signalled */
if (cfg->subsong_end == -1) {
cfg->subsong_end = vgmstream->num_streams;
close_vgmstream(vgmstream);
return 1;
}
}
@ -666,19 +726,25 @@ static int convert_file(cli_config* cfg) {
/* prepare output */
{
/* note that outfilename_temp must persist outside this block, hence the external array */
if (!cfg->outfilename_config && !cfg->outfilename) {
/* defaults */
cfg->outfilename_config = (cfg->subsong_index >= 1) ?
"?f#?s.wav" :
"?f.wav";
/* maybe should avoid overwriting with this auto-name, for the unlikely
* case of file header-body pairs (file.ext+file.ext.wav) */
}
if (cfg->outfilename_config) {
/* special substitution */
replace_filename(outfilename_temp, sizeof(outfilename_temp), cfg->outfilename_config, cfg->infilename, vgmstream);
cfg->outfilename = outfilename_temp;
}
else if (!cfg->outfilename) {
/* note that outfilename_temp must persist outside this block, hence the external array */
strcpy(outfilename_temp, cfg->infilename);
strcat(outfilename_temp, ".wav");
cfg->outfilename = outfilename_temp;
/* maybe should avoid overwriting with this auto-name, for the unlikely
* case of file header-body pairs (file.ext+file.ext.wav) */
}
if (!cfg->outfilename)
goto fail;
/* don't overwrite itself! */
if (strcmp(cfg->outfilename, cfg->infilename) == 0) {

View File

@ -486,7 +486,6 @@ id_value = 2
id_check = @0x00 # 2ch only
... #some settings for stereo
```
*.4ch.txth*
```
@ -494,16 +493,20 @@ id_value = 4
id_check = @0x00 # 4ch only
... #different settings for 4ch
```
As an interesting side-effect, you can use this to force load `.txth` in other paths. For example it can be useful if you have files in subdirs and want to point to a base `.txtp` in root.
```
multi_txth = ../.main.txth
```
## Complex usages
### Temporary values
### Order and 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.
It makes TXTHs a bit harder to follow, as they are order dependant, but otherwise it's hard to accomplish some things or others become ambiguous.
It does make TXTHs a bit harder to follow, as they are order dependant, but otherwise it's hard to accomplish some things or others become ambiguous.
For example, normally you are given a data_size in bytes, that can be used to calculate num_samples for all channels.
@ -597,7 +600,7 @@ num_samples = @0x10 * channels # byte-to-samples of channel_size
`data_size` is a special value for `num_samples` and `loop_end_sample` and will always convert as bytes-to-samples, though.
Priority is left-to-right. Do add brackets though, they are accounted for and if they are implemented in the future your .txth *will* break with impunity.
Priority is left-to-right only, due to technical reasons it doesn't handle proper math priority. Do add brackets though, they are accounted for and if they are implemented in the future your .txth *will* break with impunity.
```
# normal priority
data_size = @0x10 * 0x800 + 0x800
@ -731,7 +734,7 @@ num_samples = data_size
### Base offset chaining
Some formats read an offset to another part of the file, then another offset, then other, etc.
You can simulate this chaining multiple `base_offset`
You can simulate this chaining multiple `base_offset`:
```
base_offset = @0x10 #sets current at 0x1000
channels = @0x04 #reads at 0x1004 (base_offset + 0x04)
@ -766,7 +769,7 @@ num_samples = data_size
```
codec = PSX
interleave = 0x10
sample_rate = 24000
sample_rate = 22050
channels = 1
padding_size = auto-empty
num_samples = data_size
@ -1188,6 +1191,7 @@ chunk_start = 0x1f84
chunk_data_size = 0x20000
chunk_size = 0x21000
padding_size = auto
num_samples = data_size
```
@ -1214,6 +1218,7 @@ chunk_header_size = name_value3
chunk_data_size = name_value4
chunk_size = 0x21000
padding_size = auto
num_samples = data_size
# base_offset = 0x1F40

View File

@ -184,7 +184,7 @@ void decode_psx_configurable(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int cha
}
/* PS-ADPCM from Pivotal games, exactly like psx_cfg but with float math (reverse engineered from the exe) */
void decode_psx_pivotal(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int frame_size) {
void decode_psx_pivotal(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int frame_size) {
uint8_t frame[0x50] = {0};
off_t frame_offset;
int i, frames_in, sample_count = 0;
@ -250,7 +250,7 @@ void decode_psx_pivotal(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channe
* - 0x7 (0111): End marker and don't decode
* - 0x8+(1NNN): Not valid
*/
static int ps_find_loop_offsets_internal(STREAMFILE *sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t * p_loop_start, int32_t * p_loop_end, int config) {
static int ps_find_loop_offsets_internal(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t * p_loop_start, int32_t * p_loop_end, int config) {
int num_samples = 0, loop_start = 0, loop_end = 0;
int loop_start_found = 0, loop_end_found = 0;
off_t offset = start_offset;
@ -303,6 +303,7 @@ static int ps_find_loop_offsets_internal(STREAMFILE *sf, off_t start_offset, siz
&& buf[0] != 0x00 /* ignore blank frame */
&& buf[0] != 0x0c /* ignore silent frame */
&& buf[0] != 0x3c /* ignore some L-R tracks with different end flags */
&& buf[0] != 0x1c /* ignore some L-R tracks with different end flags */
) {
/* assume full loop with repeated frame header and null frame */
@ -343,19 +344,22 @@ static int ps_find_loop_offsets_internal(STREAMFILE *sf, off_t start_offset, siz
return 0; /* no loop */
}
int ps_find_loop_offsets(STREAMFILE *sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t *p_loop_start, int32_t *p_loop_end) {
int ps_find_loop_offsets(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t* p_loop_start, int32_t* p_loop_end) {
return ps_find_loop_offsets_internal(sf, start_offset, data_size, channels, interleave, p_loop_start, p_loop_end, 0);
}
int ps_find_loop_offsets_full(STREAMFILE *sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t *p_loop_start, int32_t *p_loop_end) {
int ps_find_loop_offsets_full(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t* p_loop_start, int32_t* p_loop_end) {
return ps_find_loop_offsets_internal(sf, start_offset, data_size, channels, interleave, p_loop_start, p_loop_end, 1);
}
size_t ps_find_padding(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, size_t interleave, int discard_empty) {
off_t min_offset, offset;
size_t ps_find_padding(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, size_t interleave, int discard_empty) {
off_t min_offset, offset, read_offset = 0;
size_t frame_size = 0x10;
size_t padding_size = 0;
size_t interleave_consumed = 0;
uint8_t buf[0x8000];
int buf_pos = 0;
int bytes;
if (data_size == 0 || channels == 0 || (channels > 1 && interleave == 0))
@ -374,12 +378,22 @@ size_t ps_find_padding(STREAMFILE *streamFile, off_t start_offset, size_t data_s
uint8_t flag;
int is_empty = 0;
/* read in chunks to optimize (less SF rebuffering since we go in reverse) */
if (offset < read_offset || buf_pos <= 0) {
read_offset = offset - sizeof(buf);
if (read_offset < 0)
read_offset = 0; //?
bytes = read_streamfile(buf, read_offset, sizeof(buf), sf);
buf_pos = (bytes / frame_size * frame_size);
}
buf_pos -= frame_size;
offset -= frame_size;
f1 = read_32bitBE(offset+0x00,streamFile);
f2 = read_32bitBE(offset+0x04,streamFile);
f3 = read_32bitBE(offset+0x08,streamFile);
f4 = read_32bitBE(offset+0x0c,streamFile);
f1 = get_u32be(buf+buf_pos+0x00);
f2 = get_u32be(buf+buf_pos+0x04);
f3 = get_u32be(buf+buf_pos+0x08);
f4 = get_u32be(buf+buf_pos+0x0c);
flag = (f1 >> 16) & 0xFF;
if (f1 == 0 && f2 == 0 && f3 == 0 && f4 == 0)
@ -406,9 +420,11 @@ size_t ps_find_padding(STREAMFILE *streamFile, off_t start_offset, size_t data_s
if (interleave_consumed == interleave) {
interleave_consumed = 0;
offset -= interleave * (channels - 1);
buf_pos -= interleave * (channels - 1);
}
}
//;VGM_LOG("PSX PAD: total size %x\n", padding_size);
return padding_size;
}
@ -424,14 +440,14 @@ size_t ps_cfg_bytes_to_samples(size_t bytes, size_t frame_size, int channels) {
}
/* test PS-ADPCM frames for correctness */
int ps_check_format(STREAMFILE *streamFile, off_t offset, size_t max) {
int ps_check_format(STREAMFILE* sf, off_t offset, size_t max) {
off_t max_offset = offset + max;
if (max_offset > get_streamfile_size(streamFile))
max_offset = get_streamfile_size(streamFile);
if (max_offset > get_streamfile_size(sf))
max_offset = get_streamfile_size(sf);
while (offset < max_offset) {
uint8_t predictor = (read_8bit(offset+0x00,streamFile) >> 4) & 0x0f;
uint8_t flags = read_8bit(offset+0x01,streamFile);
uint8_t predictor = (read_8bit(offset+0x00,sf) >> 4) & 0x0f;
uint8_t flags = read_8bit(offset+0x01,sf);
if (predictor > 5 || flags > 7) {
return 0;

View File

@ -143,6 +143,7 @@ static const char* extension_list[] = {
"cwav",
"cxs",
"d2", //txth/reserved [Dodonpachi Dai-Ou-Jou (PS2)]
"da",
//"dat", //common
"data",
@ -437,6 +438,7 @@ static const char* extension_list[] = {
"sb6",
"sb7",
"sbk",
"sbin",
"sbr",
"sbv",
"sm0",
@ -447,7 +449,6 @@ static const char* extension_list[] = {
"sm5",
"sm6",
"sm7",
"sbin",
"sc",
"scd",
"sch",
@ -490,6 +491,7 @@ static const char* extension_list[] = {
"ss2",
"ssd", //txth/reserved [Zack & Wiki (Wii)]
"ssm",
"sspr",
"sss",
"ster",
"sth",

View File

@ -1706,6 +1706,10 @@
RelativePath=".\meta\sqex_sead.c"
>
</File>
<File
RelativePath=".\meta\sspr.c"
>
</File>
<File
RelativePath=".\meta\sthd.c"
>

View File

@ -250,6 +250,7 @@
<ClCompile Include="meta\sqex_scd.c" />
<ClCompile Include="meta\sqex_scd_sscf.c" />
<ClCompile Include="meta\sqex_sead.c" />
<ClCompile Include="meta\sspr.c" />
<ClCompile Include="meta\sthd.c" />
<ClCompile Include="meta\tun.c" />
<ClCompile Include="meta\txth.c" />

View File

@ -1636,6 +1636,9 @@
<ClCompile Include="meta\sqex_sead.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\sspr.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\sthd.c">
<Filter>meta\Source Files</Filter>
</ClCompile>

View File

@ -407,6 +407,9 @@ static const hcakey_info hcakey_list[] = {
/* maimai DX Splash (AC) */
{9170825592834449000}, // 7F4551499DF55E68
/* Dragon Quest Tact (Android) */
{3234477171400153310}, // 2CE32BD9B36A98DE
/* D4DJ Groovy Mix (Android) [base files] */
{393410674916959300}, // 0575ACECA945A444
/* D4DJ Groovy Mix (Android) [music_* files, per-song later mixed with subkey] */

View File

@ -949,4 +949,6 @@ VGMSTREAM* init_vgmstream_tac(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_ogv_3rdeye(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_sspr(STREAMFILE* sf);
#endif /*_META_H*/

55
src/meta/sspr.c Normal file
View File

@ -0,0 +1,55 @@
#include "meta.h"
#include "../coding/coding.h"
/* SSPR - Capcom container [Sengoku Basara 4 (PS3/PS4), Mega Man Zero ZX Legacy Collection (PS4)] */
VGMSTREAM* init_vgmstream_sspr(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* temp_sf = NULL;
uint32_t name_offset, subfile_offset, subfile_size, name_size;
int big_endian;
int total_subsongs, target_subsong = sf->stream_index;
char* extension;
uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL;
/* checks */
if (!check_extensions(sf,"sspr"))
goto fail;
if (!is_id32be(0x00,sf,"SSPR"))
goto fail;
/* Simple (audio only) container used some Capcom games (common engine?).
* Some files come with a .stqr with unknown data (cues?). */
big_endian = guess_endianness32bit(0x04, sf); /* 0x01 (version?) */
read_u32 = big_endian ? read_u32be : read_u32le;
total_subsongs = read_u32(0x08,sf);
if (target_subsong == 0) target_subsong = 1;
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
/* 0x0c: null */
name_offset = read_u32(0x10 + (target_subsong-1) * 0x10 + 0x00,sf);
subfile_offset = read_u32(0x10 + (target_subsong-1) * 0x10 + 0x04,sf);
name_size = read_u32(0x10 + (target_subsong-1) * 0x10 + 0x08,sf);
subfile_size = read_u32(0x10 + (target_subsong-1) * 0x10 + 0x0c,sf);
extension = big_endian ? "at3" : "at9";
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, extension);
if (!temp_sf) goto fail;
vgmstream = init_vgmstream_riff(temp_sf);
if (!vgmstream) goto fail;
vgmstream->num_streams = total_subsongs;
read_string(vgmstream->stream_name,name_size+1, name_offset,sf);
close_streamfile(temp_sf);
return vgmstream;
fail:
close_streamfile(temp_sf);
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -808,8 +808,7 @@ static int get_padding_size(txth_header* txth, int discard_empty);
/* Simple text parser of "key = value" lines.
* The code is meh and error handling not exactly the best. */
static int parse_txth(txth_header* txth) {
off_t txt_offset = 0x00;
off_t file_size = get_streamfile_size(txth->sf_text);
off_t txt_offset, file_size;
/* setup txth defaults */
if (txth->sf_body)
@ -818,14 +817,8 @@ static int parse_txth(txth_header* txth) {
if (txth->target_subsong == 0) txth->target_subsong = 1;
/* skip BOM if needed */
if ((uint16_t)read_16bitLE(0x00, txth->sf_text) == 0xFFFE ||
(uint16_t)read_16bitLE(0x00, txth->sf_text) == 0xFEFF) {
txt_offset = 0x02;
}
else if (((uint32_t)read_32bitBE(0x00, txth->sf_text) & 0xFFFFFF00) == 0xEFBBBF00) {
txt_offset = 0x03;
}
txt_offset = read_bom(txth->sf_text);
file_size = get_streamfile_size(txth->sf_text);
/* read lines */
{
@ -1533,7 +1526,7 @@ static int read_name_table_keyval(txth_header* txth, const char* line, char* key
/* try "(name): (val))" */
ok = sscanf(line, " %[^\t#:] : %[^\t#\r\n] ", key, val);
if (ok == 2) {
;VGM_LOG("TXTH: name %s get\n", key);
//;VGM_LOG("TXTH: name %s get\n", key);
return 1;
}
@ -1541,14 +1534,14 @@ static int read_name_table_keyval(txth_header* txth, const char* line, char* key
key[0] = '\0';
ok = sscanf(line, " : %[^\t#\r\n] ", val);
if (ok == 1) {
;VGM_LOG("TXTH: default get\n");
//;VGM_LOG("TXTH: default get\n");
return 1;
}
/* try "(name)#subsong: (val))" */
ok = sscanf(line, " %[^\t#:]#%i : %[^\t#\r\n] ", key, &subsong, val);
if (ok == 3 && subsong == txth->target_subsong) {
VGM_LOG("TXTH: name %s + subsong %i get\n", key, subsong);
//;VGM_LOG("TXTH: name %s + subsong %i get\n", key, subsong);
return 1;
}
@ -1556,7 +1549,7 @@ static int read_name_table_keyval(txth_header* txth, const char* line, char* key
key[0] = '\0';
ok = sscanf(line, " #%i: %[^\t#\r\n] ", &subsong, val);
if (ok == 2 && subsong == txth->target_subsong) {
VGM_LOG("TXTH: default + subsong %i get\n", subsong);
//;VGM_LOG("TXTH: default + subsong %i get\n", subsong);
return 1;
}
@ -1596,18 +1589,9 @@ static int parse_name_table(txth_header* txth, char* name_list) {
get_streamfile_basename(txth->sf_body, basename, sizeof(basename));
//;VGM_LOG("TXTH: names full=%s, file=%s, base=%s\n", fullname, filename, basename);
txt_offset = 0x00;
txt_offset = read_bom(sf_names);
file_size = get_streamfile_size(sf_names);
/* skip BOM if needed */
if ((uint16_t)read_16bitLE(0x00, sf_names) == 0xFFFE ||
(uint16_t)read_16bitLE(0x00, sf_names) == 0xFEFF) {
txt_offset = 0x02;
}
else if (((uint32_t)read_32bitBE(0x00, sf_names) & 0xFFFFFF00) == 0xEFBBBF00) {
txt_offset = 0x03;
}
/* in case of repeated name tables */
memset(txth->name_values, 0, sizeof(txth->name_values));
txth->name_values_count = 0;

View File

@ -1887,8 +1887,7 @@ fail:
static txtp_header* parse_txtp(STREAMFILE* sf) {
txtp_header* txtp = NULL;
off_t txt_offset = 0x00;
off_t file_size = get_streamfile_size(sf);
off_t txt_offset, file_size;
txtp = calloc(1,sizeof(txtp_header));
@ -1897,16 +1896,8 @@ static txtp_header* parse_txtp(STREAMFILE* sf) {
/* defaults */
txtp->is_segmented = 1;
/* skip BOM if needed */
if (file_size > 0 &&
(read_u16le(0x00, sf) == 0xFFFE || read_u16le(0x00, sf) == 0xFEFF)) {
txt_offset = 0x02;
}
else if ((read_u32be(0x00, sf) & 0xFFFFFF00) == 0xEFBBBF00) {
txt_offset = 0x03;
}
txt_offset = read_bom(sf);
file_size = get_streamfile_size(sf);
/* read and parse lines */
{

View File

@ -895,12 +895,17 @@ STREAMFILE* open_streamfile_by_filename(STREAMFILE* sf, const char* filename) {
sf->get_name(sf, fullname, sizeof(fullname));
//todo normalize separators in a better way, safeops, improve copying
path = strrchr(fullname,DIR_SEPARATOR);
/* check for non-normalized paths first (ex. txth) */
path = strrchr(fullname, '/');
if (!path)
path = strrchr(fullname,'\\');
if (path) {
path[1] = '\0'; /* remove name after separator */
strcpy(partname, filename);
fix_dir_separators(partname);
fix_dir_separators(partname); /* normalize to DIR_SEPARATOR */
/* normalize relative paths as don't work ok in some plugins */
if (partname[0] == '.' && partname[1] == DIR_SEPARATOR) { /* './name' */
@ -996,6 +1001,19 @@ size_t read_line(char* buf, int buf_size, off_t offset, STREAMFILE* sf, int* p_l
return i + extra_bytes;
}
size_t read_bom(STREAMFILE* sf) {
if (read_u16le(0x00, sf) == 0xFFFE ||
read_u16le(0x00, sf) == 0xFEFF) {
return 0x02;
}
if ((read_u32be(0x00, sf) & 0xFFFFFF00) == 0xEFBBBF00) {
return 0x03;
}
return 0x00;
}
size_t read_string(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf) {
size_t pos;
@ -1137,17 +1155,9 @@ STREAMFILE* read_filemap_file_pos(STREAMFILE* sf, int file_num, int* p_pos) {
get_streamfile_filename(sf, filename, sizeof(filename));
txt_offset = 0x00;
txt_offset = read_bom(sf_map);
file_size = get_streamfile_size(sf_map);
/* skip BOM if needed */
if (read_u16le(0x00, sf_map) == 0xFFFE ||
read_u16le(0x00, sf_map) == 0xFEFF) {
txt_offset = 0x02;
} else if ((read_u32be(0x00, sf_map) & 0xFFFFFF00) == 0xEFBBBF00) {
txt_offset = 0x03;
}
/* read lines and find target filename, format is (filename): value1, ... valueN */
while (txt_offset < file_size) {
char line[0x2000];

View File

@ -355,6 +355,9 @@ static inline size_t align_size_to_block(size_t value, size_t block_align) {
* p_line_ok is set to 1 if the complete line was read; pass NULL to ignore. */
size_t read_line(char* buf, int buf_size, off_t offset, STREAMFILE* sf, int* p_line_ok);
/* skip BOM if needed */
size_t read_bom(STREAMFILE* sf);
/* reads a c-string (ANSI only), up to bufsize or NULL, returning size. buf is optional (works as get_string_size). */
size_t read_string(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf);
/* reads a UTF16 string... but actually only as ANSI (discards the upper byte) */

View File

@ -524,6 +524,7 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = {
init_vgmstream_idsp_tose,
init_vgmstream_dsp_kwa,
init_vgmstream_ogv_3rdeye,
init_vgmstream_sspr,
/* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */
init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */