mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-31 20:25:23 +01:00
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:
commit
79f81ac3ff
43
README.md
43
README.md
@ -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
|
||||
|
@ -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) {
|
||||
|
17
doc/TXTH.md
17
doc/TXTH.md
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
|
@ -1706,6 +1706,10 @@
|
||||
RelativePath=".\meta\sqex_sead.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\sspr.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\sthd.c"
|
||||
>
|
||||
|
@ -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" />
|
||||
|
@ -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>
|
||||
|
@ -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] */
|
||||
|
@ -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
55
src/meta/sspr.c
Normal 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;
|
||||
}
|
@ -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;
|
||||
|
@ -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 */
|
||||
{
|
||||
|
@ -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];
|
||||
|
@ -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) */
|
||||
|
@ -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 */
|
||||
|
Loading…
x
Reference in New Issue
Block a user