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 ## Usage
There are multiple end-user bits: There are multiple end-user bits:
- a command line decoder called "test.exe/vgmstream-cli" - a command line decoder called *test.exe/vgmstream-cli*
- a Winamp plugin called "in_vgmstream" - a Winamp plugin called *in_vgmstream*
- a foobar2000 component called "foo_input_vgmstream" - a foobar2000 component called *foo_input_vgmstream*
- an XMPlay plugin called "xmp-vgmstream" - an XMPlay plugin called *xmp-vgmstream*
- an Audacious plugin called "libvgmstream" - an Audacious plugin called *libvgmstream*
- a command line player called "vgmstream123" - 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 above components are what you use to actually get sound. See *components* below for
explanations about each one. 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: 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 -m file.adx`: print info but don't decode
- `test.exe -i -o file_noloop.wav file.hca`: convert without looping - `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 -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` - `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 Available commands are printed when run with no flags. Note that you can also
achieve similar results for other plugins using TXTP, described later. achieve similar results for other plugins using TXTP, described later.
With files multiple subsongs you need to specify manually subsong (by design, to avoid Output filename in `-o` may use wildcards:
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:
- `?s`: sets current subsong (or 0 if format doesn't have subsongs) - `?s`: sets current subsong (or 0 if format doesn't have subsongs)
- `?0Ns`: same, but left pads subsong with up to `N` zeroes - `?0Ns`: same, but left pads subsong with up to `N` zeroes
- `?n`: internal stream name, or input filename if format doesn't have name - `?n`: internal stream name, or input filename if format doesn't have name
- `?f`: input filename - `?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) ### 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. more functions, plus doesn't modify original data.
Usage example (used when opening an unknown file named `bgm_01.pcm`): Usage example (used when opening an unknown file named `bgm_01.pcm`):
**.pcm.txth** **.pcm.txth**
``` ```
codec = PCM16LE codec = PCM16LE
@ -421,6 +425,7 @@ per-file configurations like number of loops, remove unneeded channels,
force looping, and many other features. force looping, and many other features.
Usage examples (open directly, name can be set freely): Usage examples (open directly, name can be set freely):
**bgm01-full.txtp** **bgm01-full.txtp**
``` ```
# plays 2 files as a single one # plays 2 files as a single one
@ -599,6 +604,7 @@ enabled in preferences):
### TXTP matching ### TXTP matching
To ease *TXTP* config, tags with plain files will match `.txtp` with config, and tags To ease *TXTP* config, tags with plain files will match `.txtp` with config, and tags
with `.txtp` config also match plain files: with `.txtp` config also match plain files:
**!tags.m3u** **!tags.m3u**
``` ```
# @TITLE Title1 # @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. Since it matches when a tag is found, some cases that depend on order won't work.
You can disable this feature manually then: You can disable this feature manually then:
**!tags.m3u** **!tags.m3u**
``` ```
# $EXACTMATCH # $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\n"
" -E: force end-to-end looping even if file has real loop points\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 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" " -m: print metadata only, don't decode\n"
" -L: append a smpl chunk and create a looping wav\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" " -2 N: only output the Nth (first is 0) set of stereo channels\n"
@ -103,7 +104,8 @@ typedef struct {
int print_batchvar; int print_batchvar;
int write_lwav; int write_lwav;
int only_stereo; int only_stereo;
int stream_index; int subsong_index;
int subsong_end;
double loop_count; double loop_count;
double fade_time; double fade_time;
@ -145,7 +147,7 @@ static int parse_config(cli_config* cfg, int argc, char** argv) {
opterr = 0; opterr = 0;
/* read config */ /* 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 #ifdef HAVE_JSON
"VI" "VI"
#endif #endif
@ -207,10 +209,17 @@ static int parse_config(cli_config* cfg, int argc, char** argv) {
cfg->ignore_fade = 1; cfg->ignore_fade = 1;
break; break;
case 's': 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; break;
case 't': case 't':
cfg->tag_filename= optarg; cfg->tag_filename = optarg;
break; break;
case 'T': case 'T':
cfg->show_title = 1; 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_file(cli_config* cfg);
static int convert_subsongs(cli_config* cfg);
static int write_file(VGMSTREAM* vgmstream, cli_config* cfg); static int write_file(VGMSTREAM* vgmstream, cli_config* cfg);
int main(int argc, char** argv) { int main(int argc, char** argv) {
cli_config cfg = {0}; cli_config cfg = {0};
int i, res, ok; int i, res, ok;
@ -582,11 +593,19 @@ int main(int argc, char** argv) {
if (cfg.outfilename_config) if (cfg.outfilename_config)
cfg.outfilename = NULL; cfg.outfilename = NULL;
res = convert_file(&cfg); if (cfg.subsong_index > 0 && cfg.subsong_end != 0) {
//if (!res) goto fail; /* keep on truckin' */ res = convert_subsongs(&cfg);
if (res) ok = 1; /* return ok if at least one succeeds, for programs that check result code */ //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) if (!ok)
goto fail; goto fail;
@ -595,6 +614,40 @@ fail:
return EXIT_FAILURE; 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) { static int convert_file(cli_config* cfg) {
VGMSTREAM* vgmstream = NULL; VGMSTREAM* vgmstream = NULL;
char outfilename_temp[PATH_LIMIT]; char outfilename_temp[PATH_LIMIT];
@ -623,7 +676,7 @@ static int convert_file(cli_config* cfg) {
goto fail; goto fail;
} }
sf->stream_index = cfg->stream_index; sf->stream_index = cfg->subsong_index;
vgmstream = init_vgmstream_from_STREAMFILE(sf); vgmstream = init_vgmstream_from_STREAMFILE(sf);
close_streamfile(sf); close_streamfile(sf);
@ -631,6 +684,13 @@ static int convert_file(cli_config* cfg) {
fprintf(stderr, "failed opening %s\n", cfg->infilename); fprintf(stderr, "failed opening %s\n", cfg->infilename);
goto fail; 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 */ /* 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) { if (cfg->outfilename_config) {
/* special substitution */ /* special substitution */
replace_filename(outfilename_temp, sizeof(outfilename_temp), cfg->outfilename_config, cfg->infilename, vgmstream); replace_filename(outfilename_temp, sizeof(outfilename_temp), cfg->outfilename_config, cfg->infilename, vgmstream);
cfg->outfilename = outfilename_temp; cfg->outfilename = outfilename_temp;
} }
else if (!cfg->outfilename) {
/* note that outfilename_temp must persist outside this block, hence the external array */ if (!cfg->outfilename)
strcpy(outfilename_temp, cfg->infilename); goto fail;
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) */
}
/* don't overwrite itself! */ /* don't overwrite itself! */
if (strcmp(cfg->outfilename, cfg->infilename) == 0) { if (strcmp(cfg->outfilename, cfg->infilename) == 0) {

View File

@ -486,7 +486,6 @@ id_value = 2
id_check = @0x00 # 2ch only id_check = @0x00 # 2ch only
... #some settings for stereo ... #some settings for stereo
``` ```
*.4ch.txth* *.4ch.txth*
``` ```
@ -494,16 +493,20 @@ id_value = 4
id_check = @0x00 # 4ch only id_check = @0x00 # 4ch only
... #different settings for 4ch ... #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 ## 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. 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. 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. `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 # normal priority
data_size = @0x10 * 0x800 + 0x800 data_size = @0x10 * 0x800 + 0x800
@ -731,7 +734,7 @@ num_samples = data_size
### Base offset chaining ### Base offset chaining
Some formats read an offset to another part of the file, then another offset, then other, etc. 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 base_offset = @0x10 #sets current at 0x1000
channels = @0x04 #reads at 0x1004 (base_offset + 0x04) channels = @0x04 #reads at 0x1004 (base_offset + 0x04)
@ -766,7 +769,7 @@ num_samples = data_size
``` ```
codec = PSX codec = PSX
interleave = 0x10 interleave = 0x10
sample_rate = 24000 sample_rate = 22050
channels = 1 channels = 1
padding_size = auto-empty padding_size = auto-empty
num_samples = data_size num_samples = data_size
@ -1188,6 +1191,7 @@ chunk_start = 0x1f84
chunk_data_size = 0x20000 chunk_data_size = 0x20000
chunk_size = 0x21000 chunk_size = 0x21000
padding_size = auto
num_samples = data_size num_samples = data_size
``` ```
@ -1214,6 +1218,7 @@ chunk_header_size = name_value3
chunk_data_size = name_value4 chunk_data_size = name_value4
chunk_size = 0x21000 chunk_size = 0x21000
padding_size = auto
num_samples = data_size num_samples = data_size
# base_offset = 0x1F40 # 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) */ /* 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}; uint8_t frame[0x50] = {0};
off_t frame_offset; off_t frame_offset;
int i, frames_in, sample_count = 0; 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 * - 0x7 (0111): End marker and don't decode
* - 0x8+(1NNN): Not valid * - 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 num_samples = 0, loop_start = 0, loop_end = 0;
int loop_start_found = 0, loop_end_found = 0; int loop_start_found = 0, loop_end_found = 0;
off_t offset = start_offset; 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] != 0x00 /* ignore blank frame */
&& buf[0] != 0x0c /* ignore silent frame */ && buf[0] != 0x0c /* ignore silent frame */
&& buf[0] != 0x3c /* ignore some L-R tracks with different end flags */ && 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 */ /* 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 */ 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); 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); 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) { 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; off_t min_offset, offset, read_offset = 0;
size_t frame_size = 0x10; size_t frame_size = 0x10;
size_t padding_size = 0; size_t padding_size = 0;
size_t interleave_consumed = 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)) 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; uint8_t flag;
int is_empty = 0; 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; offset -= frame_size;
f1 = read_32bitBE(offset+0x00,streamFile); f1 = get_u32be(buf+buf_pos+0x00);
f2 = read_32bitBE(offset+0x04,streamFile); f2 = get_u32be(buf+buf_pos+0x04);
f3 = read_32bitBE(offset+0x08,streamFile); f3 = get_u32be(buf+buf_pos+0x08);
f4 = read_32bitBE(offset+0x0c,streamFile); f4 = get_u32be(buf+buf_pos+0x0c);
flag = (f1 >> 16) & 0xFF; flag = (f1 >> 16) & 0xFF;
if (f1 == 0 && f2 == 0 && f3 == 0 && f4 == 0) 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) { if (interleave_consumed == interleave) {
interleave_consumed = 0; interleave_consumed = 0;
offset -= interleave * (channels - 1); offset -= interleave * (channels - 1);
buf_pos -= interleave * (channels - 1);
} }
} }
//;VGM_LOG("PSX PAD: total size %x\n", padding_size);
return 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 */ /* 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; off_t max_offset = offset + max;
if (max_offset > get_streamfile_size(streamFile)) if (max_offset > get_streamfile_size(sf))
max_offset = get_streamfile_size(streamFile); max_offset = get_streamfile_size(sf);
while (offset < max_offset) { while (offset < max_offset) {
uint8_t predictor = (read_8bit(offset+0x00,streamFile) >> 4) & 0x0f; uint8_t predictor = (read_8bit(offset+0x00,sf) >> 4) & 0x0f;
uint8_t flags = read_8bit(offset+0x01,streamFile); uint8_t flags = read_8bit(offset+0x01,sf);
if (predictor > 5 || flags > 7) { if (predictor > 5 || flags > 7) {
return 0; return 0;

View File

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

View File

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

View File

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

View File

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

View File

@ -407,6 +407,9 @@ static const hcakey_info hcakey_list[] = {
/* maimai DX Splash (AC) */ /* maimai DX Splash (AC) */
{9170825592834449000}, // 7F4551499DF55E68 {9170825592834449000}, // 7F4551499DF55E68
/* Dragon Quest Tact (Android) */
{3234477171400153310}, // 2CE32BD9B36A98DE
/* D4DJ Groovy Mix (Android) [base files] */ /* D4DJ Groovy Mix (Android) [base files] */
{393410674916959300}, // 0575ACECA945A444 {393410674916959300}, // 0575ACECA945A444
/* D4DJ Groovy Mix (Android) [music_* files, per-song later mixed with subkey] */ /* 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_ogv_3rdeye(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_sspr(STREAMFILE* sf);
#endif /*_META_H*/ #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. /* Simple text parser of "key = value" lines.
* The code is meh and error handling not exactly the best. */ * The code is meh and error handling not exactly the best. */
static int parse_txth(txth_header* txth) { static int parse_txth(txth_header* txth) {
off_t txt_offset = 0x00; off_t txt_offset, file_size;
off_t file_size = get_streamfile_size(txth->sf_text);
/* setup txth defaults */ /* setup txth defaults */
if (txth->sf_body) if (txth->sf_body)
@ -818,14 +817,8 @@ static int parse_txth(txth_header* txth) {
if (txth->target_subsong == 0) txth->target_subsong = 1; if (txth->target_subsong == 0) txth->target_subsong = 1;
/* skip BOM if needed */ txt_offset = read_bom(txth->sf_text);
if ((uint16_t)read_16bitLE(0x00, txth->sf_text) == 0xFFFE || file_size = get_streamfile_size(txth->sf_text);
(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;
}
/* read lines */ /* read lines */
{ {
@ -1533,7 +1526,7 @@ static int read_name_table_keyval(txth_header* txth, const char* line, char* key
/* try "(name): (val))" */ /* try "(name): (val))" */
ok = sscanf(line, " %[^\t#:] : %[^\t#\r\n] ", key, val); ok = sscanf(line, " %[^\t#:] : %[^\t#\r\n] ", key, val);
if (ok == 2) { if (ok == 2) {
;VGM_LOG("TXTH: name %s get\n", key); //;VGM_LOG("TXTH: name %s get\n", key);
return 1; return 1;
} }
@ -1541,14 +1534,14 @@ static int read_name_table_keyval(txth_header* txth, const char* line, char* key
key[0] = '\0'; key[0] = '\0';
ok = sscanf(line, " : %[^\t#\r\n] ", val); ok = sscanf(line, " : %[^\t#\r\n] ", val);
if (ok == 1) { if (ok == 1) {
;VGM_LOG("TXTH: default get\n"); //;VGM_LOG("TXTH: default get\n");
return 1; return 1;
} }
/* try "(name)#subsong: (val))" */ /* try "(name)#subsong: (val))" */
ok = sscanf(line, " %[^\t#:]#%i : %[^\t#\r\n] ", key, &subsong, val); ok = sscanf(line, " %[^\t#:]#%i : %[^\t#\r\n] ", key, &subsong, val);
if (ok == 3 && subsong == txth->target_subsong) { 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; return 1;
} }
@ -1556,7 +1549,7 @@ static int read_name_table_keyval(txth_header* txth, const char* line, char* key
key[0] = '\0'; key[0] = '\0';
ok = sscanf(line, " #%i: %[^\t#\r\n] ", &subsong, val); ok = sscanf(line, " #%i: %[^\t#\r\n] ", &subsong, val);
if (ok == 2 && subsong == txth->target_subsong) { 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; 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)); get_streamfile_basename(txth->sf_body, basename, sizeof(basename));
//;VGM_LOG("TXTH: names full=%s, file=%s, base=%s\n", fullname, filename, 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); 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 */ /* in case of repeated name tables */
memset(txth->name_values, 0, sizeof(txth->name_values)); memset(txth->name_values, 0, sizeof(txth->name_values));
txth->name_values_count = 0; txth->name_values_count = 0;

View File

@ -1887,8 +1887,7 @@ fail:
static txtp_header* parse_txtp(STREAMFILE* sf) { static txtp_header* parse_txtp(STREAMFILE* sf) {
txtp_header* txtp = NULL; txtp_header* txtp = NULL;
off_t txt_offset = 0x00; off_t txt_offset, file_size;
off_t file_size = get_streamfile_size(sf);
txtp = calloc(1,sizeof(txtp_header)); txtp = calloc(1,sizeof(txtp_header));
@ -1897,16 +1896,8 @@ static txtp_header* parse_txtp(STREAMFILE* sf) {
/* defaults */ /* defaults */
txtp->is_segmented = 1; txtp->is_segmented = 1;
txt_offset = read_bom(sf);
/* skip BOM if needed */ file_size = get_streamfile_size(sf);
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;
}
/* read and parse lines */ /* 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)); sf->get_name(sf, fullname, sizeof(fullname));
//todo normalize separators in a better way, safeops, improve copying //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) { if (path) {
path[1] = '\0'; /* remove name after separator */ path[1] = '\0'; /* remove name after separator */
strcpy(partname, filename); 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 */ /* normalize relative paths as don't work ok in some plugins */
if (partname[0] == '.' && partname[1] == DIR_SEPARATOR) { /* './name' */ 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; 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 read_string(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf) {
size_t pos; 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)); get_streamfile_filename(sf, filename, sizeof(filename));
txt_offset = 0x00; txt_offset = read_bom(sf_map);
file_size = get_streamfile_size(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 */ /* read lines and find target filename, format is (filename): value1, ... valueN */
while (txt_offset < file_size) { while (txt_offset < file_size) {
char line[0x2000]; 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. */ * 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); 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). */ /* 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); 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) */ /* 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_idsp_tose,
init_vgmstream_dsp_kwa, init_vgmstream_dsp_kwa,
init_vgmstream_ogv_3rdeye, init_vgmstream_ogv_3rdeye,
init_vgmstream_sspr,
/* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */ /* 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 */ init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */