From 9246ead1fc3cc360d51964eb808e9c166cfbcd39 Mon Sep 17 00:00:00 2001 From: bnnm Date: Thu, 8 Jul 2021 22:01:35 +0200 Subject: [PATCH 1/9] Add .d2 extension [Dodonpachi Dai-Ou-Jou (PS2)] --- src/formats.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/formats.c b/src/formats.c index 511d5587..eac58b18 100644 --- a/src/formats.c +++ b/src/formats.c @@ -143,6 +143,7 @@ static const char* extension_list[] = { "cwav", "cxs", + "d2", //txth/reserved [Dodonpachi Dai-Ou-Jou (PS2)] "da", //"dat", //common "data", From 4b88afb23929b4c6946d8b01f6c31b6d369661e6 Mon Sep 17 00:00:00 2001 From: bnnm Date: Thu, 8 Jul 2021 22:02:03 +0200 Subject: [PATCH 2/9] Add HCA key [Dragon Quest Tact (Android)] --- src/meta/hca_keys.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/meta/hca_keys.h b/src/meta/hca_keys.h index 0700949e..b1f3a877 100644 --- a/src/meta/hca_keys.h +++ b/src/meta/hca_keys.h @@ -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] */ From 0854565b927756c8246c5f38acf2e67df9edb9a9 Mon Sep 17 00:00:00 2001 From: bnnm Date: Thu, 8 Jul 2021 22:08:57 +0200 Subject: [PATCH 3/9] Fix some TXTH/TXTP + CLI path issues --- src/streamfile.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/streamfile.c b/src/streamfile.c index 6c187e20..2404fd39 100644 --- a/src/streamfile.c +++ b/src/streamfile.c @@ -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' */ From bbc55c20367b3d572bcbd3e855377bf1f6214b5d Mon Sep 17 00:00:00 2001 From: bnnm Date: Thu, 8 Jul 2021 22:12:35 +0200 Subject: [PATCH 4/9] Add CLI option to write multiple subsongs --- README.md | 43 +++++++++++--------- cli/vgmstream_cli.c | 98 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 107 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 4d650f7f..12bf1e5e 100644 --- a/README.md +++ b/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 diff --git a/cli/vgmstream_cli.c b/cli/vgmstream_cli.c index 993c581f..a623ff5d 100644 --- a/cli/vgmstream_cli.c +++ b/cli/vgmstream_cli.c @@ -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) { From 6fb417810395c48a5b0e15ad6be9d3e4105e35bc Mon Sep 17 00:00:00 2001 From: bnnm Date: Thu, 8 Jul 2021 22:16:43 +0200 Subject: [PATCH 5/9] Optimize PSX padding finder --- src/coding/psx_decoder.c | 45 ++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/coding/psx_decoder.c b/src/coding/psx_decoder.c index f4bb7e43..4b98096e 100644 --- a/src/coding/psx_decoder.c +++ b/src/coding/psx_decoder.c @@ -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; @@ -343,19 +343,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 +377,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 +419,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 +439,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; From ac23eab7eddf28447b828b7acd420b20f94eb837 Mon Sep 17 00:00:00 2001 From: bnnm Date: Thu, 8 Jul 2021 22:17:13 +0200 Subject: [PATCH 6/9] Fix some auto L+R issues [Gift: Prism (PS2)] --- src/coding/psx_decoder.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coding/psx_decoder.c b/src/coding/psx_decoder.c index 4b98096e..70d004c7 100644 --- a/src/coding/psx_decoder.c +++ b/src/coding/psx_decoder.c @@ -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 */ From d7b277a671021815bc54faacd592237899e7dd3c Mon Sep 17 00:00:00 2001 From: bnnm Date: Thu, 8 Jul 2021 22:26:21 +0200 Subject: [PATCH 7/9] cleanup: BOM skip function --- src/meta/txth.c | 32 ++++++++------------------------ src/meta/txtp.c | 15 +++------------ src/streamfile.c | 23 ++++++++++++++--------- src/streamfile.h | 3 +++ 4 files changed, 28 insertions(+), 45 deletions(-) diff --git a/src/meta/txth.c b/src/meta/txth.c index 566c1c4f..16ade7b4 100644 --- a/src/meta/txth.c +++ b/src/meta/txth.c @@ -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; diff --git a/src/meta/txtp.c b/src/meta/txtp.c index e763162c..450d8d07 100644 --- a/src/meta/txtp.c +++ b/src/meta/txtp.c @@ -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 */ { diff --git a/src/streamfile.c b/src/streamfile.c index 2404fd39..d5e79a2c 100644 --- a/src/streamfile.c +++ b/src/streamfile.c @@ -1001,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; @@ -1142,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]; diff --git a/src/streamfile.h b/src/streamfile.h index 148297af..1ab12705 100644 --- a/src/streamfile.h +++ b/src/streamfile.h @@ -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) */ From b299aae547d593937a79f8216826481a1eafd8d6 Mon Sep 17 00:00:00 2001 From: bnnm Date: Fri, 9 Jul 2021 00:06:27 +0200 Subject: [PATCH 8/9] Add Capcom .sspr [Sengoku Basara 4 (PS3/PS4)] --- src/formats.c | 3 +- src/libvgmstream.vcproj | 4 +++ src/libvgmstream.vcxproj | 1 + src/libvgmstream.vcxproj.filters | 3 ++ src/meta/meta.h | 2 ++ src/meta/sspr.c | 55 ++++++++++++++++++++++++++++++++ src/vgmstream.c | 1 + 7 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/meta/sspr.c diff --git a/src/formats.c b/src/formats.c index eac58b18..0baa8965 100644 --- a/src/formats.c +++ b/src/formats.c @@ -438,6 +438,7 @@ static const char* extension_list[] = { "sb6", "sb7", "sbk", + "sbin", "sbr", "sbv", "sm0", @@ -448,7 +449,6 @@ static const char* extension_list[] = { "sm5", "sm6", "sm7", - "sbin", "sc", "scd", "sch", @@ -491,6 +491,7 @@ static const char* extension_list[] = { "ss2", "ssd", //txth/reserved [Zack & Wiki (Wii)] "ssm", + "sspr", "sss", "ster", "sth", diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 1de32156..d17b0c44 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -1706,6 +1706,10 @@ RelativePath=".\meta\sqex_sead.c" > + + diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index 32355de0..5773a53b 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -250,6 +250,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index e32b1700..2c3bc382 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -1636,6 +1636,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files diff --git a/src/meta/meta.h b/src/meta/meta.h index ce0f21c5..c41216ae 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -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*/ diff --git a/src/meta/sspr.c b/src/meta/sspr.c new file mode 100644 index 00000000..f24e1623 --- /dev/null +++ b/src/meta/sspr.c @@ -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; +} diff --git a/src/vgmstream.c b/src/vgmstream.c index 530856d0..bd5f3209 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -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 */ From 273dc80504d9837fd49f8d9b18bbfa2d461c818a Mon Sep 17 00:00:00 2001 From: bnnm Date: Fri, 9 Jul 2021 00:06:33 +0200 Subject: [PATCH 9/9] Doc --- doc/TXTH.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/doc/TXTH.md b/doc/TXTH.md index b155a80e..bcdfc60a 100644 --- a/doc/TXTH.md +++ b/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