Merge pull request #1188 from bnnm/winamp-txth-etc

- Add FSB key
- Fix TXTH loop behavior positive to include 0
- winamp: fix open dialog extensions and tweaks
- audacious: always reject unplayable files
- Add .sdp extension
- Add TXTH codec "HEVAG"
- txtp_segmenter: add inline command
This commit is contained in:
bnnm 2022-08-06 22:39:57 +02:00 committed by GitHub
commit a87d040a3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 203 additions and 99 deletions

View File

@ -1,6 +1,6 @@
--- ---
name: Bug report name: Bug report
about: Tell us if something is broken (please include multiple samples and which game is affected). about: Tell us if something is broken. **Please include multiple samples and which game is affected**.
title: "[Bug] " title: "[Bug] "
labels: bug labels: bug
assignees: '' assignees: ''

View File

@ -1,6 +1,6 @@
--- ---
name: Feature or format request name: Feature or format request
about: Suggest a feature or a new format that you think vgmstream should play (please include multiple samples and which game is affected). about: Suggest a feature or a new format that you think vgmstream should play. **Please include multiple samples and which game is affected**.
title: "[Request]" title: "[Request]"
labels: enhancement labels: enhancement
assignees: '' assignees: ''

View File

@ -130,7 +130,24 @@ bool VgmstreamPlugin::is_our_file(const char * filename, VFSFile & file) {
cfg.accept_unknown = settings.exts_unknown_on; cfg.accept_unknown = settings.exts_unknown_on;
cfg.accept_common = settings.exts_common_on; cfg.accept_common = settings.exts_common_on;
return vgmstream_ctx_is_valid(filename, &cfg) > 0 ? true : false;
int ok = vgmstream_ctx_is_valid(filename, &cfg);
if (!ok) {
return false;
}
// just in case reject non-supported files, to avoid hijacking certain files like .vgm
// (other plugins should have higher priority though)
STREAMFILE* sf = open_vfs(filename);
if (!sf) return false;
VGMSTREAM* infostream = init_vgmstream_from_STREAMFILE(sf);
if (!infostream) {
close_streamfile(sf);
return false;
}
return true;
} }

View File

@ -40,6 +40,7 @@ def parse():
parser.add_argument("-cle","--command-loop-end", help="sets loop end segment") parser.add_argument("-cle","--command-loop-end", help="sets loop end segment")
parser.add_argument("-cv","--command-volume", help="sets volume") parser.add_argument("-cv","--command-volume", help="sets volume")
parser.add_argument("-c","--command", help="sets any command (free text)") parser.add_argument("-c","--command", help="sets any command (free text)")
parser.add_argument("-ci","--command-inline", help="sets any inline command (free text)")
return parser.parse_args() return parser.parse_args()
@ -134,10 +135,15 @@ def main():
len_segments = len(segments) len_segments = len(segments)
with open(name,"w+") as ftxtp: with open(name,"w+") as ftxtp:
for i, segment in enumerate(segments): for i, segment in enumerate(segments):
command_inline = ''
if args.command_inline:
command_inline = args.command_inline.replace("\\n", "\n")
if args.command_loop_force and len_segments == 1: if args.command_loop_force and len_segments == 1:
ftxtp.write(segment + " #e\n") txtp_line = "%s #e%s\n" % (segment, command_inline)
else: else:
ftxtp.write(segment + "\n") txtp_line = "%s%s\n" % (segment, command_inline)
ftxtp.write(txtp_line)
if args.command_loop_auto or args.command_loop_force and len_segments > 1: if args.command_loop_auto or args.command_loop_force and len_segments > 1:
ftxtp.write("loop_mode = auto\n") ftxtp.write("loop_mode = auto\n")

View File

@ -70,6 +70,9 @@ as explained below, but often will use default values. Accepted codec strings:
# * Interleave is multiple of 0x10 (default), often +0x1000 # * Interleave is multiple of 0x10 (default), often +0x1000
# - PSX_bf PlayStation ADPCM with bad flags # - PSX_bf PlayStation ADPCM with bad flags
# * Variation with garbage data, for rare PS2 games # * Variation with garbage data, for rare PS2 games
# - HEVAG Vita/PS4 ADPCM
# * For some Vita/PS4 games
# * Interleave is multiple of 0x10 (default)
# - XBOX Xbox IMA ADPCM (mono/stereo) # - XBOX Xbox IMA ADPCM (mono/stereo)
# * For many XBOX games, and some PC games # * For many XBOX games, and some PC games
# * Special interleave is multiple of 0x24 (mono) or 0x48 (stereo) # * Special interleave is multiple of 0x24 (mono) or 0x48 (stereo)
@ -151,7 +154,7 @@ as explained below, but often will use default values. Accepted codec strings:
# - OKI16 OKI ADPCM with 16-bit output (not VOX/Dialogic 12-bit) # - OKI16 OKI ADPCM with 16-bit output (not VOX/Dialogic 12-bit)
# * For rare PS2 games [Sweet Legacy (PS2), Hooligan (PS2)] # * For rare PS2 games [Sweet Legacy (PS2), Hooligan (PS2)]
# - OKI4S OKI ADPCM with 16-bit output and adjusted tables # - OKI4S OKI ADPCM with 16-bit output and adjusted tables
# * For later Konami rhythm games # * For later Konami arcade games [Gitadora (AC), Metal Gear Arcade (AC)]
# - AAC Advanced Audio Coding (raw outside .mp4) # - AAC Advanced Audio Coding (raw outside .mp4)
# * For some 3DS games and many iOS games # * For some 3DS games and many iOS games
# * Should set skip_samples (typically 1024 but varies, 2112 is also common) # * Should set skip_samples (typically 1024 but varies, 2112 is also common)
@ -313,7 +316,7 @@ Special values:
Sometimes games give loop flags different meaning, so behavior can be tweaked by defining `loop_behavior` before `loop_flag`: Sometimes games give loop flags different meaning, so behavior can be tweaked by defining `loop_behavior` before `loop_flag`:
- `default`: values 0 or 0xFFFF/0xFFFFFFFF (-1) disable looping, but not 0xFF (loop endlessly) - `default`: values 0 or 0xFFFF/0xFFFFFFFF (-1) disable looping, but not 0xFF (loop endlessly)
- `negative`: values 0xFF/0xFFFF/0xFFFFFFFF (-1) enable looping - `negative`: values 0xFF/0xFFFF/0xFFFFFFFF (-1) enable looping
- `positive`: values 0xFF/0xFFFF/0xFFFFFFFF (-1) disable looping - `positive`: values 0xFF/0xFFFF/0xFFFFFFFF (-1) disable looping (0 also enables it)
- `inverted`: values not 0 disable looping - `inverted`: values not 0 disable looping
``` ```
@ -595,10 +598,10 @@ data_size = @0x100 # useless as num_samples is already transformed
### Redefining values ### Redefining values
Some commands alter the function of all next commands and can be redefined as needed: Some commands alter the function of all next commands and can be redefined as needed:
``` ```
samples_type = bytes sample_type = bytes
num_samples = @0x10 num_samples = @0x10
samples_type = sample sample_type = sample
loop_end_sample = @0x14 loop_end_sample = @0x14
``` ```

View File

@ -71,22 +71,62 @@ Default output filename is `?f.wav`, or `?f#?s.wav` if you set subsongs (`-s/-S`
*Windows*: drop the `in_vgmstream.dll` in your Winamp Plugins directory, *Windows*: drop the `in_vgmstream.dll` in your Winamp Plugins directory,
and follow the above instructions for installing needed extra files. and follow the above instructions for installing needed extra files.
*Others*: may be possible to use through *Wine* *Others*: may be possible to use through *Wine*.
Once installed, supported files should be playable. There is a simple config Once installed, supported files should be playable. There is a simple config
menu to tweak some options too. If the *Preferences... > Plug-ins > Input* shows menu to tweak some options too. If the *Preferences... > Plug-ins > Input* shows
vgmstream as *"NOT LOADED"* that means extra DLL files aren't in the correct vgmstream as *"NOT LOADED"* that means extra DLL files aren't in the correct
place. place.
#### Plugin priority
An (uncommon) issue is clashing extensions. When opening a file, Winamp first
asks all plugins if they support the file. Here vgmstream accepts files it can
play and rejects anything it can't, but if no plugin "claims" the file (and most
don't), Winamp will just pass it to the *first* `.dll` in the plugin folder that
reports the extension. Since vgmstream supports tons of extensions sometimes it
may receive files it can't play (even after rejecting them before). This oddness
can be solved by renaming the plugins' `.dll` so vgmstream goes *last*.
For example, vgmstream ignores sequenced `.vgm` but supports streamed `.vgm` (another
format). If your *in_vgm* plugin version doesn't "claim" sequenced `.vgm` Winamp
may send it to vgmstream by mistake (so won't be playable), depending on how it's
named. Here vgmstream has higher priority and fail:
```
in_vgmstream.dll
in_vgmW.dll
```
And here has lower and will be playable:
```
in_vgm.dll
in_vgmstream.dll
```
Note the above is also affected by vgmstream's options *Enable common exts* (vgmstream
will accept and play common files like `.wav` or `.ogg`), and *Enable unknown exts* (will
try to play files outside the known extension list, which is often possible through *TXTH*).
### foo_input_vgmstream (foobar2000 plugin) ### foo_input_vgmstream (foobar2000 plugin)
*Windows*: every file should be installed automatically when opening the `.fb2k-component` *Windows*: every file should be installed automatically when opening the `.fb2k-component`
bundle bundle.
*Others*: may be possible to use through *Wine* *Others*: may be possible to use through *Wine*.
A known quirk is that when loop options or tags change, playlist info won't refresh Note that vgmstream currently requires at least foobar v1.5 to run.
automatically. You need to manually refresh it by selecting songs and doing
#### Plugin priority
If multiple plugins supports the same format, which plugin is used depends on config.
You can change plugin's priority in **options > Playback > Decoding**. Due to the
huge amount of supported formats, you may want to set it low enough.
Note the above is also affected by vgmstream's options *Enable common exts* (vgmstream
will accept and play common files like `.wav` or `.ogg`), and *Enable unknown exts* (will
try to play files outside the known extension list, which is often possible through *TXTH*).
#### Playlist issues
A known quirk is that when loop options or tags change, playlist time/info won't
update automatically. You need to manually refresh it by selecting songs and doing
**shift + right click > Tagging > Reload info from file(s)**. **shift + right click > Tagging > Reload info from file(s)**.
@ -94,17 +134,21 @@ automatically. You need to manually refresh it by selecting songs and doing
*Windows*: drop the `xmp-vgmstream.dll` in your XMPlay plugins directory, *Windows*: drop the `xmp-vgmstream.dll` in your XMPlay plugins directory,
and follow the above instructions for installing the other files needed. and follow the above instructions for installing the other files needed.
*Others*: may be possible to use through *Wine* *Others*: may be possible to use through *Wine*.
Note that this has less features compared to *in_vgmstream* and has no config. Note that this has less features compared to *in_vgmstream* and has no config.
Since XMPlay supports Winamp plugins you may also use `in_vgmstream.dll` instead. Since XMPlay supports Winamp plugins you may also use `in_vgmstream.dll` instead.
#### Plugin priority
Because the XMPlay MP3 decoder incorrectly tries to play some vgmstream extensions, Because the XMPlay MP3 decoder incorrectly tries to play some vgmstream extensions,
you need to manually fix it by going to **options > plugins > input > vgmstream** you need to manually fix it by going to **options > plugins > input > vgmstream**
and in the "priority filetypes" put: `ahx,asf,awc,ckd,fsb,genh,lwav,msf,p3d,rak,scd,txth,xvag` and in the "priority filetypes" put: `ahx,asf,awc,ckd,fsb,genh,lwav,msf,p3d,rak,scd,txth,xvag`
(or any other similar case).
#### Missing subsongs
XMPlay cannot support vgmstream's type of mixed subsongs due to player limitations XMPlay cannot support vgmstream's type of mixed subsongs due to player limitations
(with neither *xmp-vgmstream* nor *in_vgmstream* plugins), try using *TXTP* instead (explained below). (with neither *xmp-vgmstream* nor *in_vgmstream* plugins). You can make one *TXTP*
per subsong to play them instead (explained below).
### Audacious plugin ### Audacious plugin
@ -113,6 +157,10 @@ XMPlay cannot support vgmstream's type of mixed subsongs due to player limitatio
*Others*: needs to be manually built. Instructions can be found in [BUILD.md](BUILD.md) *Others*: needs to be manually built. Instructions can be found in [BUILD.md](BUILD.md)
document in vgmstream's source code (can be done with CMake or autotools). document in vgmstream's source code (can be done with CMake or autotools).
#### Plugin priority
vgmstream sets its priority on compile time, low enough for most other plugins to
go first (but not all). Can be changed with `AUDACIOUS_VGMSTREAM_PRIORITY`.
### vgmstream123 (command line player) ### vgmstream123 (command line player)
*Windows/Linux*: needs to be manually built. Instructions can be found in the *Windows/Linux*: needs to be manually built. Instructions can be found in the
@ -124,6 +172,7 @@ The program is meant to be a simple stand-alone player, supporting playback of
vgmstream files through libao. Most options should be similar to CLI's vgmstream files through libao. Most options should be similar to CLI's
(`-m`, `-i`, `-s N` and so on, though not fully equivalent), use `-h` for full info. (`-m`, `-i`, `-s N` and so on, though not fully equivalent), use `-h` for full info.
#### Extra features
On Linux, files compressed with gzip/bzip2/xz also work, as identified by a On Linux, files compressed with gzip/bzip2/xz also work, as identified by a
`.gz/.bz2/.xz` extension. The file will be decompressed to a temp dir using the `.gz/.bz2/.xz` extension. The file will be decompressed to a temp dir using the
respective utility program (which must be installed and accessible) and then respective utility program (which must be installed and accessible) and then

View File

@ -468,6 +468,7 @@ static const char* extension_list[] = {
"scd", "scd",
"sch", "sch",
"sd9", "sd9",
"sdp", //txth/reserved [Metal Gear Arcade (AC)]
"sdf", "sdf",
"sdt", "sdt",
"seb", "seb",

View File

@ -32,13 +32,13 @@ static const uint8_t key_mkx[] = { 0x39,0x39,0x36,0x31,0x36,0x34,0x42,0x35,0x46,
/* Xian Xia Chuan (PC) */ //"gat@tcqs2010" /* Xian Xia Chuan (PC) */ //"gat@tcqs2010"
static const uint8_t key_xxc[] = { 0x67,0x61,0x74,0x40,0x74,0x63,0x71,0x73,0x32,0x30,0x31,0x30 }; static const uint8_t key_xxc[] = { 0x67,0x61,0x74,0x40,0x74,0x63,0x71,0x73,0x32,0x30,0x31,0x30 };
/* Mirror War Reincarnation of Holiness (PC) */ //"logicsounddesignmwsdev" /* Mirror War: Reincarnation of Holiness (PC) */ //"logicsounddesignmwsdev"
static const uint8_t key_mwr[] = { 0x6C,0x6F,0x67,0x69,0x63,0x73,0x6F,0x75,0x6E,0x64,0x64,0x65,0x73,0x69,0x67,0x6E,0x6D,0x77,0x73,0x64,0x65,0x76 }; static const uint8_t key_mwr[] = { 0x6C,0x6F,0x67,0x69,0x63,0x73,0x6F,0x75,0x6E,0x64,0x64,0x65,0x73,0x69,0x67,0x6E,0x6D,0x77,0x73,0x64,0x65,0x76 };
/* Need for Speed Shift 2 Unleashed (PC demo?) */ //"p&oACY^c4LK5C2v^x5nIO6kg5vNH$tlj" /* Need for Speed Shift 2 Unleashed (PC demo?) */ //"p&oACY^c4LK5C2v^x5nIO6kg5vNH$tlj"
static const uint8_t key_n2u[] = { 0x70,0x26,0x6F,0x41,0x43,0x59,0x5E,0x63,0x34,0x4C,0x4B,0x35,0x43,0x32,0x76,0x5E,0x78,0x35,0x6E,0x49,0x4F,0x36,0x6B,0x67,0x35,0x76,0x4E,0x48,0x24,0x74,0x6C,0x6A }; static const uint8_t key_n2u[] = { 0x70,0x26,0x6F,0x41,0x43,0x59,0x5E,0x63,0x34,0x4C,0x4B,0x35,0x43,0x32,0x76,0x5E,0x78,0x35,0x6E,0x49,0x4F,0x36,0x6B,0x67,0x35,0x76,0x4E,0x48,0x24,0x74,0x6C,0x6A };
/* Critter Crunch, Superbrothers: Sword & Sworcery */ //"j1$Mk0Libg3#apEr42mo" /* Critter Crunch (PC), Superbrothers: Sword & Sworcery (PC) */ //"j1$Mk0Libg3#apEr42mo"
static const uint8_t key_ccr[] = { 0x6A,0x31,0x24,0x4D,0x6B,0x30,0x4C,0x69,0x62,0x67,0x33,0x23,0x61,0x70,0x45,0x72,0x34,0x32,0x6D,0x6F }; static const uint8_t key_ccr[] = { 0x6A,0x31,0x24,0x4D,0x6B,0x30,0x4C,0x69,0x62,0x67,0x33,0x23,0x61,0x70,0x45,0x72,0x34,0x32,0x6D,0x6F };
/* Cyphers */ //"@kdj43nKDN^k*kj3ndf02hd95nsl(NJG" /* Cyphers */ //"@kdj43nKDN^k*kj3ndf02hd95nsl(NJG"
@ -50,7 +50,7 @@ static const uint8_t key_xdz[] = { 0x58,0x69,0x61,0x79,0x75,0x77,0x75,0x36,0x39,
/* Ji Feng Zhi Ren / Kritika Online */ //"kri_tika_5050_" /* Ji Feng Zhi Ren / Kritika Online */ //"kri_tika_5050_"
static const uint8_t key_jzz[] = { 0x6B,0x72,0x69,0x5F,0x74,0x69,0x6B,0x61,0x5F,0x35,0x30,0x35,0x30,0x5F }; static const uint8_t key_jzz[] = { 0x6B,0x72,0x69,0x5F,0x74,0x69,0x6B,0x61,0x5F,0x35,0x30,0x35,0x30,0x5F };
/* Invisible Inc. */ //"mint78run52" /* Invisible Inc. (PC?) */ //"mint78run52"
static const uint8_t key_inv[] = { 0x6D,0x69,0x6E,0x74,0x37,0x38,0x72,0x75,0x6E,0x35,0x32 }; static const uint8_t key_inv[] = { 0x6D,0x69,0x6E,0x74,0x37,0x38,0x72,0x75,0x6E,0x35,0x32 };
/* Guitar Hero 3 */ //"5atu6w4zaw" /* Guitar Hero 3 */ //"5atu6w4zaw"
@ -92,6 +92,9 @@ static const uint8_t key_fg2[] = { 0x2c,0x26,0x2e,0x58,0x5a,0x38,0x5d,0x66,0x4c,
/* Achilles: Legends Untold (PC) */ //"Achilles_0_15_DpG" /* Achilles: Legends Untold (PC) */ //"Achilles_0_15_DpG"
static const uint8_t key_alu[] = { 0x41,0x63,0x68,0x69,0x6C,0x6C,0x65,0x73,0x5F,0x30,0x5F,0x31,0x35,0x5F,0x44,0x70,0x47 }; static const uint8_t key_alu[] = { 0x41,0x63,0x68,0x69,0x6C,0x6C,0x65,0x73,0x5F,0x30,0x5F,0x31,0x35,0x5F,0x44,0x70,0x47 };
/* Cult of the Lamb Demo (PC) */ //"4FB8CC894515617939F4E1B7D50972D27213B8E6"
static const uint8_t key_col[] = { 0x34,0x46,0x42,0x38,0x43,0x43,0x38,0x39,0x34,0x35,0x31,0x35,0x36,0x31,0x37,0x39,0x33,0x39,0x46,0x34,0x45,0x31,0x42,0x37,0x44,0x35,0x30,0x39,0x37,0x32,0x44,0x32,0x37,0x32,0x31,0x33,0x42,0x38,0x45,0x36 };
// Unknown: // Unknown:
// - Battle: Los Angeles // - Battle: Los Angeles
// - Guitar Hero: Warriors of Rock, DJ hero FSB // - Guitar Hero: Warriors of Rock, DJ hero FSB
@ -108,8 +111,7 @@ typedef struct {
static const fsbkey_info fsbkey_list[] = { static const fsbkey_info fsbkey_list[] = {
{ 0,0, sizeof(key_dj2),key_dj2 }, { 0,0, sizeof(key_dj2),key_dj2 },
{ 0,0, sizeof(key_dfp),key_dfp },//FSB4 { 0,0, sizeof(key_dfp),key_dfp },//FSB4
{ 1,0, sizeof(key_dfp),key_dfp },//untested { 1,0, sizeof(key_dfp),key_dfp },//FSB5
{ 1,1, sizeof(key_dfp),key_dfp },//untested
{ 1,0, sizeof(key_npp),key_npp },//FSB5 { 1,0, sizeof(key_npp),key_npp },//FSB5
{ 1,0, sizeof(key_sms),key_sms },//FSB5 { 1,0, sizeof(key_sms),key_sms },//FSB5
{ 1,0, sizeof(key_gfs),key_gfs },//FSB5 { 1,0, sizeof(key_gfs),key_gfs },//FSB5
@ -121,14 +123,9 @@ static const fsbkey_info fsbkey_list[] = {
{ 0,1, sizeof(key_xxc),key_xxc },//untested { 0,1, sizeof(key_xxc),key_xxc },//untested
{ 1,0, sizeof(key_xxc),key_xxc },//untested { 1,0, sizeof(key_xxc),key_xxc },//untested
{ 1,1, sizeof(key_xxc),key_xxc },//untested { 1,1, sizeof(key_xxc),key_xxc },//untested
{ 0,0, sizeof(key_mwr),key_mwr },//untested { 1,0, sizeof(key_mwr),key_mwr },//FSB5
{ 0,1, sizeof(key_mwr),key_mwr },//untested
{ 1,0, sizeof(key_mwr),key_mwr },//untested
{ 1,1, sizeof(key_mwr),key_mwr },//untested
{ 0,0, sizeof(key_n2u),key_n2u },//untested { 0,0, sizeof(key_n2u),key_n2u },//untested
{ 0,1, sizeof(key_n2u),key_n2u },//untested { 0,1, sizeof(key_n2u),key_n2u },//untested
{ 1,0, sizeof(key_n2u),key_n2u },//untested
{ 1,1, sizeof(key_n2u),key_n2u },//untested
{ 0,0, sizeof(key_ccr),key_ccr },//untested { 0,0, sizeof(key_ccr),key_ccr },//untested
{ 0,1, sizeof(key_ccr),key_ccr },//untested { 0,1, sizeof(key_ccr),key_ccr },//untested
{ 1,0, sizeof(key_ccr),key_ccr },//untested { 1,0, sizeof(key_ccr),key_ccr },//untested
@ -168,6 +165,7 @@ static const fsbkey_info fsbkey_list[] = {
{ 1,0, sizeof(key_fg1),key_fg1 },// FSB5 { 1,0, sizeof(key_fg1),key_fg1 },// FSB5
{ 1,0, sizeof(key_fg2),key_fg2 },// FSB5 { 1,0, sizeof(key_fg2),key_fg2 },// FSB5
{ 1,0, sizeof(key_alu),key_alu },// FSB5 { 1,0, sizeof(key_alu),key_alu },// FSB5
{ 1,0, sizeof(key_col),key_col },// FSB5
}; };
static const int fsbkey_list_count = sizeof(fsbkey_list) / sizeof(fsbkey_list[0]); static const int fsbkey_list_count = sizeof(fsbkey_list) / sizeof(fsbkey_list[0]);

View File

@ -49,6 +49,7 @@ typedef enum {
PCM_FLOAT_LE, PCM_FLOAT_LE,
IMA_HV, IMA_HV,
PCM8_SB, PCM8_SB,
HEVAG,
UNKNOWN = 99, UNKNOWN = 99,
} txth_codec_t; } txth_codec_t;
@ -217,7 +218,8 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
uint32_t interleave = 0; uint32_t interleave = 0;
switch(txth.codec) { switch(txth.codec) {
case PSX: case PSX:
case PSX_bf: interleave = 0x10; break; case PSX_bf:
case HEVAG: interleave = 0x10; break;
case NGC_DSP: interleave = 0x08; break; case NGC_DSP: interleave = 0x08; break;
case PCM16LE: case PCM16LE:
case PCM16BE: interleave = 0x02; break; case PCM16BE: interleave = 0x02; break;
@ -235,6 +237,8 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
/* type to coding conversion */ /* type to coding conversion */
switch (txth.codec) { switch (txth.codec) {
case PSX: coding = coding_PSX; break; case PSX: coding = coding_PSX; break;
case PSX_bf: coding = coding_PSX_badflags; break;
case HEVAG: coding = coding_HEVAG; break;
case XBOX: coding = coding_XBOX_IMA; break; case XBOX: coding = coding_XBOX_IMA; break;
case NGC_DTK: coding = coding_NGC_DTK; break; case NGC_DTK: coding = coding_NGC_DTK; break;
case PCM16LE: coding = coding_PCM16LE; break; case PCM16LE: coding = coding_PCM16LE; break;
@ -254,7 +258,6 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
case AICA: coding = coding_AICA; break; case AICA: coding = coding_AICA; break;
case MSADPCM: coding = coding_MSADPCM; break; case MSADPCM: coding = coding_MSADPCM; break;
case NGC_DSP: coding = coding_NGC_DSP; break; case NGC_DSP: coding = coding_NGC_DSP; break;
case PSX_bf: coding = coding_PSX_badflags; break;
case MS_IMA: coding = coding_MS_IMA; break; case MS_IMA: coding = coding_MS_IMA; break;
case APPLE_IMA4: coding = coding_APPLE_IMA4; break; case APPLE_IMA4: coding = coding_APPLE_IMA4; break;
#ifdef VGM_USE_FFMPEG #ifdef VGM_USE_FFMPEG
@ -320,6 +323,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
case coding_SDX2: case coding_SDX2:
case coding_PSX: case coding_PSX:
case coding_PSX_badflags: case coding_PSX_badflags:
case coding_HEVAG:
case coding_DVI_IMA: case coding_DVI_IMA:
case coding_IMA: case coding_IMA:
case coding_HV_IMA: case coding_HV_IMA:
@ -351,6 +355,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
if (!txth.interleave && ( if (!txth.interleave && (
coding == coding_PSX || coding == coding_PSX ||
coding == coding_PSX_badflags || coding == coding_PSX_badflags ||
coding == coding_HEVAG ||
coding == coding_IMA_int || coding == coding_IMA_int ||
coding == coding_DVI_IMA_int || coding == coding_DVI_IMA_int ||
coding == coding_SDX2_int || coding == coding_SDX2_int ||
@ -977,6 +982,7 @@ static txth_codec_t parse_codec(txth_header* txth, const char* val) {
else if (is_string(val,"CP_YM")) return CP_YM; else if (is_string(val,"CP_YM")) return CP_YM;
else if (is_string(val,"PCM_FLOAT_LE")) return PCM_FLOAT_LE; else if (is_string(val,"PCM_FLOAT_LE")) return PCM_FLOAT_LE;
else if (is_string(val,"IMA_HV")) return IMA_HV; else if (is_string(val,"IMA_HV")) return IMA_HV;
else if (is_string(val,"HEVAG")) return HEVAG;
/* special handling */ /* special handling */
else if (is_string(val,"name_value")) return txth->name_values[0]; else if (is_string(val,"name_value")) return txth->name_values[0];
else if (is_string(val,"name_value1")) return txth->name_values[0]; else if (is_string(val,"name_value1")) return txth->name_values[0];
@ -1202,6 +1208,8 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, cha
else if (txth->loop_behavior == POSITIVE) { else if (txth->loop_behavior == POSITIVE) {
if (txth->loop_flag == 0xFF || txth->loop_flag == 0xFFFF || txth->loop_flag == 0xFFFFFFFF) if (txth->loop_flag == 0xFF || txth->loop_flag == 0xFFFF || txth->loop_flag == 0xFFFFFFFF)
txth->loop_flag = 0; txth->loop_flag = 0;
else if (txth->loop_flag == 0)
txth->loop_flag = 1;
} }
else if (txth->loop_behavior == INVERTED) { else if (txth->loop_behavior == INVERTED) {
txth->loop_flag = (txth->loop_flag == 0); txth->loop_flag = (txth->loop_flag == 0);
@ -2045,6 +2053,7 @@ static int get_bytes_to_samples(txth_header* txth, uint32_t bytes) {
return dsp_bytes_to_samples(bytes, txth->channels); return dsp_bytes_to_samples(bytes, txth->channels);
case PSX: case PSX:
case PSX_bf: case PSX_bf:
case HEVAG:
return ps_bytes_to_samples(bytes, txth->channels); return ps_bytes_to_samples(bytes, txth->channels);
case PCM16BE: case PCM16BE:
case PCM16LE: case PCM16LE:

View File

@ -193,53 +193,37 @@ static int is_xmplay() {
return 0; return 0;
} }
/* Adds ext to Winamp's extension list */ /* Adds ext to Winamp's extension list */
static void add_extension(char* dst, int dst_len, const char* ext) { static int add_extension(char* dst, int dst_len, const char* ext) {
char buf[EXT_BUFFER_SIZE]; int ext_len;
char ext_upp[EXT_BUFFER_SIZE];
int ext_len, written;
int i,j;
if (dst_len <= 1)
return;
ext_len = strlen(ext); ext_len = strlen(ext);
if (dst_len <= ext_len + 1)
return 0;
/* find end of dst (double \0), saved in i */ strcpy(dst, ext); /* seems winamp uppercases this if needed */
for (i = 0; i < dst_len - 2 && (dst[i] || dst[i+1]); i++) dst[ext_len] = ';';
;
/* check if end reached or not enough room to add */ return ext_len + 1;
if (i == dst_len - 2 || i + EXT_BUFFER_SIZE+2 > dst_len - 2 || ext_len * 3 + 20+2 > EXT_BUFFER_SIZE) {
dst[i] = '\0';
dst[i+1] = '\0';
return;
}
if (i > 0)
i++;
/* uppercase ext */
for (j = 0; j < ext_len; j++)
ext_upp[j] = toupper(ext[j]);
ext_upp[j] = '\0';
/* copy new extension + double null terminate */
/* ex: "vgmstream\0vgmstream Audio File (*.VGMSTREAM)\0" */
written = snprintf(buf,sizeof(buf)-1, "%s%c%s Audio File (*.%s)%c", ext,'\0',ext_upp,ext_upp,'\0');
for (j = 0; j < written; i++,j++)
dst[i] = buf[j];
dst[i] = '\0';
dst[i+1] = '\0';
} }
/* Creates Winamp's extension list, a single string that ends with \0\0. /* Creates Winamp's extension list, a single string that ends with \0\0.
* Each extension must be in this format: "extension\0Description\0" * Each extension must be in this format: "extensions\0Description\0"
* The list is used to accept extensions by default when IsOurFile returns 0, and to register file types. *
* It could be ignored/empty and just detect in IsOurFile instead. */ * The list is used to accept extensions by default when IsOurFile returns 0, to register file
* types, and in the open dialog's type combo. Format actually can be:
* - "ext1;ext2;...\0EXTS Audio Files (*.ext1; *.ext2; *...\0", //single line with all
* - "ext1\0EXT1 Audio File (*.ext1)\0ext2\0EXT2 Audio File (*.ext2)\0...", //multiple lines
* Open dialog's text (including all plugin's "Description") seems limited to old MAX_PATH 260
* (max size for "extensions" checks seems ~0x40000 though). Given vgmstream's huge number
* of exts, use single line to (probably) work properly with dialogs (used to be multi line).
*/
static void build_extension_list(char* winamp_list, int winamp_list_size) { static void build_extension_list(char* winamp_list, int winamp_list_size) {
const char** ext_list; const char** ext_list;
size_t ext_list_len; size_t ext_list_len;
int i; int i, written;
int description_size = 0x100; /* reserved max at the end */
winamp_list[0] = '\0'; winamp_list[0] = '\0';
winamp_list[1] = '\0'; winamp_list[1] = '\0';
@ -247,7 +231,29 @@ static void build_extension_list(char* winamp_list, int winamp_list_size) {
ext_list = vgmstream_get_formats(&ext_list_len); ext_list = vgmstream_get_formats(&ext_list_len);
for (i = 0; i < ext_list_len; i++) { for (i = 0; i < ext_list_len; i++) {
add_extension(winamp_list, winamp_list_size, ext_list[i]); int used = add_extension(winamp_list, winamp_list_size - description_size, ext_list[i]);
if (used <= 0) {
vgm_logi("build_extension_list: not enough buf for all exts\n");
break;
}
winamp_list += used;
winamp_list_size -= used;
}
if (i > 0) {
winamp_list[-1] = '\0'; /* last "ext;" to "ext\0" */
}
/* generic description for the info dialog since we can't really show everything */
written = snprintf(winamp_list, winamp_list_size - 2, "vgmstream Audio Files%c", '\0');
/* should end with double \0 */
if (written < 0) {
winamp_list[0] = '\0';
winamp_list[1] = '\0';
}
else {
winamp_list[written + 0] = '\0';
winamp_list[written + 1] = '\0';
} }
} }
@ -299,6 +305,8 @@ static double get_album_gain_volume(const in_char* fn) {
if (settings.gain_type == REPLAYGAIN_NONE) if (settings.gain_type == REPLAYGAIN_NONE)
return 1.0; return 1.0;
//;{ char f8[PATH_LIMIT]; wa_ichar_to_char(f8,PATH_LIMIT,(in_char*)fn); vgm_logi("get_album_gain_volume: file %s\n", f8); }
replaygain[0] = '\0'; /* reset each time to make sure we read actual tags */ replaygain[0] = '\0'; /* reset each time to make sure we read actual tags */
if (settings.gain_type == REPLAYGAIN_ALBUM if (settings.gain_type == REPLAYGAIN_ALBUM
&& winampGetExtendedFileInfo_common((in_char*)fn, "replaygain_album_gain", replaygain, sizeof(replaygain)) && winampGetExtendedFileInfo_common((in_char*)fn, "replaygain_album_gain", replaygain, sizeof(replaygain))
@ -379,7 +387,7 @@ void winamp_Init() {
} }
/* dynamically make a list of supported extensions */ /* dynamically make a list of supported extensions */
build_extension_list(working_extension_list, EXTENSION_LIST_SIZE); build_extension_list(working_extension_list, sizeof(working_extension_list));
} }
/* called at program quit */ /* called at program quit */
@ -394,26 +402,34 @@ int winamp_IsOurFile(const in_char *fn) {
char filename_utf8[PATH_LIMIT]; char filename_utf8[PATH_LIMIT];
int valid; int valid;
/* Winamp file opening 101:
* - load modules from plugin dir, in NTFS order (mostly alphabetical *.dll but not 100% like explorer)
* > plugin list in options is ordered by description so doesn't reflect this priority
* - make path to file
* - find first module that returns 1 in "IsOurFile" (continue otherwise)
* > generally plugins just return 0 there as it's meant for protocols (a few do check the file's header there)
* - find first module that reports that supports file extension (see build_extension_list)
* > this means plugin priority affects who hijacks the file, for shared extensions
* - if no result, retry the above 2 with "hi." + default extension (from config, default .mp3, path if not set?)
* > seems skipped when doing playlist manipulation/subsongs
* ! if module/vgmstream is given the file (even if can't play it) Winamp will call GetInfo and stop if not valid info is returned
* ! on init seems Winamp calls IsOurFile with "cda://" protocol, but should be ignored by is_valid()
*/
/* Winamp has a bizarre behavior that seemingly retries files twice (not when subsongs are added to the playlist?). /* Detect repeat retries and fake "hi." calls as they are useless for our detection.
* Then passes "hi.mp3" (no path) as a last resort if no plugin plays the file. This makes non-playable files * before only ignored "hi's" when commons exts where on but who wants unplayable files reporting 0:00. */
* show time 0:00 and use Winamp's dialog (kinda annoying). Subsongs' fake filenames that remain blank (good). if (wa_strcmp(fn, info_fn) == 0) { //TODO may need to check file size to invalidate cache
*
* When allowing common_exts pretend to accept that fake mp3, so later fails on winamp_open/getfileinfo. Worked like
* this before when infostream wasn't tested, by mistake though, but who wants unplayable files reporting 0:00. */
//TODO may need to check file size to invalidate cache
if (wa_strcmp(fn, info_fn) == 0) {
//;vgm_logi("winamp_IsOurFile: repeated call\n"); //;vgm_logi("winamp_IsOurFile: repeated call\n");
return info_valid; return info_valid;
} }
if (settings.exts_common_on && wa_strcmp(fn, wa_L("hi.mp3")) == 0) { if (/*settings.exts_common_on &&*/ wa_strncmp(fn, wa_L("hi."), 3) == 0) {
//vgm_logi("winamp_IsOurFile: ignored fakefile\n"); //;vgm_logi("winamp_IsOurFile: ignored fakefile\n");
return 1; return 1;
} }
cfg.skip_standard = 1; /* validated by Winamp */ cfg.skip_standard = 0; /* validated by Winamp after IsOurFile, reject just in case */
cfg.accept_unknown = settings.exts_unknown_on; cfg.accept_unknown = settings.exts_unknown_on;
cfg.accept_common = settings.exts_common_on; cfg.accept_common = settings.exts_common_on;
@ -421,16 +437,9 @@ int winamp_IsOurFile(const in_char *fn) {
//;vgm_logi("winamp_IsOurFile: %s\n", filename_utf8); //;vgm_logi("winamp_IsOurFile: %s\n", filename_utf8);
/* Returning 1 here means we'll handle the format (even if getinfo/play fail later), while 0 /* Return 1 if we actually handle the format or 0 to let other plugins handle it. Checking the
* means "default", that being: let other plugins check the file; if no plugin claims it by * extension alone isn't enough, as we may hijack stuff like in_vgm's *.vgm, so also try to
* returning 1 Winamp will try to match file<>plugin via extension_list. So it's common for * open/get info from the file (slower so keep some cache) */
* other plugins to just return 0 here (a few do check the file's header, like in_vgm).
*
* This generally works but plugins may hijack one of vgmstream's extensions (.wav would never
* be playable even with exts_common_on). Also, we can't just check the extension, to avoid
* hijacking stuff like in_vgm's *.vgm. So, vgmstream should try to check the file's format (slower).
*
* Somehow Winamp calls with "cda://" protocol on init, but should be ignored by is_valid */
info_valid = 0; /* may not be playable */ info_valid = 0; /* may not be playable */
wa_strncpy(info_fn, fn, PATH_LIMIT); /* copy now for repeat calls */ wa_strncpy(info_fn, fn, PATH_LIMIT); /* copy now for repeat calls */
@ -467,6 +476,7 @@ int winamp_IsOurFile(const in_char *fn) {
get_title(info_title,GETFILEINFO_TITLE_LENGTH, fn, infostream); get_title(info_title,GETFILEINFO_TITLE_LENGTH, fn, infostream);
} }
//;vgm_logi("winamp_IsOurFile: accepted\n");
info_valid = 1; info_valid = 1;
close_vgmstream(infostream); close_vgmstream(infostream);
@ -629,7 +639,7 @@ void winamp_SetPan(int pan) {
/* display info box (ALT+3) */ /* display info box (ALT+3) */
int winamp_InfoBox(const in_char *fn, HWND hwnd) { int winamp_InfoBox(const in_char *fn, HWND hwnd) {
char description[1024] = {0}, tmp[1024] = {0}; char description[1024] = {0};
TCHAR tbuf[1024] = {0}; TCHAR tbuf[1024] = {0};
double tmpVolume = 1.0; double tmpVolume = 1.0;
@ -659,8 +669,11 @@ int winamp_InfoBox(const in_char *fn, HWND hwnd) {
tmpVolume = get_album_gain_volume(fn); tmpVolume = get_album_gain_volume(fn);
} }
if (tmpVolume != 1.0) {
char tmp[128] = {0};
snprintf(tmp, sizeof(tmp), "\nvolume: %.6f\n", tmpVolume); snprintf(tmp, sizeof(tmp), "\nvolume: %.6f\n", tmpVolume);
concatn(sizeof(description), description, tmp); concatn(sizeof(description), description, tmp);
}
concatn(sizeof(description), description, "\n" PLUGIN_INFO); concatn(sizeof(description), description, "\n" PLUGIN_INFO);
@ -1000,10 +1013,12 @@ static int winampGetExtendedFileInfo_common(in_char* filename, char *metadata, c
int i, tag_found; int i, tag_found;
int max_len; int max_len;
//;{ char f8[PATH_LIMIT]; wa_ichar_to_char(f8,PATH_LIMIT,filename); vgm_logi("winampGetExtendedFileInfo_common: file %s\n", f8); }
/* load list current tags, if necessary */ /* load list current tags, if necessary */
load_tagfile_info(filename); load_tagfile_info(filename);
if (!last_tags.loaded) /* tagfile not found, fail so default get_title takes over */ if (!last_tags.loaded) /* tagfile not found, fail so default get_title takes over */
goto fail; return 0; //goto fail;
/* always called (value in ms), must return ok so other tags get called */ /* always called (value in ms), must return ok so other tags get called */
if (strcasecmp(metadata, "length") == 0) { if (strcasecmp(metadata, "length") == 0) {
@ -1063,6 +1078,8 @@ __declspec (dllexport) int winampGetExtendedFileInfo(char *filename, char *metad
wa_char_to_ichar(filename_wchar,PATH_LIMIT, filename); wa_char_to_ichar(filename_wchar,PATH_LIMIT, filename);
//;{ vgm_logi("winampGetExtendedFileInfo: file %s\n", filename); }
ok = winampGetExtendedFileInfo_common(filename_wchar, metadata, ret, retlen); ok = winampGetExtendedFileInfo_common(filename_wchar, metadata, ret, retlen);
if (ok == 0) if (ok == 0)
return 0; return 0;
@ -1081,6 +1098,8 @@ __declspec (dllexport) int winampGetExtendedFileInfoW(wchar_t *filename, char *m
wa_wchar_to_ichar(filename_ichar,PATH_LIMIT, filename); wa_wchar_to_ichar(filename_ichar,PATH_LIMIT, filename);
//;{ char f8[PATH_LIMIT]; wa_ichar_to_char(f8,PATH_LIMIT,filename); vgm_logi("winampGetExtendedFileInfoW: file %s\n", f8); }
ok = winampGetExtendedFileInfo_common(filename_ichar, metadata, ret_utf8,2048); ok = winampGetExtendedFileInfo_common(filename_ichar, metadata, ret_utf8,2048);
if (ok == 0) if (ok == 0)
return 0; return 0;
@ -1118,7 +1137,7 @@ short xsample_buffer[SAMPLE_BUFFER_SIZE*2 * VGMSTREAM_MAX_CHANNELS];
static void* winampGetExtendedRead_open_common(in_char *fn, int *size, int *bps, int *nch, int *srate) { static void* winampGetExtendedRead_open_common(in_char *fn, int *size, int *bps, int *nch, int *srate) {
VGMSTREAM* xvgmstream = NULL; VGMSTREAM* xvgmstream = NULL;
//;{ char f8[PATH_LIMIT]; wa_ichar_to_char(f8,PATH_LIMIT,fn); vgm_logi("Winamp: open common file %s\n", f8); } //;{ char f8[PATH_LIMIT]; wa_ichar_to_char(f8,PATH_LIMIT,fn); vgm_logi("winampGetExtendedRead_open_common: open common file %s\n", f8); }
/* open the stream */ /* open the stream */
xvgmstream = init_vgmstream_winamp_fileinfo(fn); xvgmstream = init_vgmstream_winamp_fileinfo(fn);
@ -1239,7 +1258,7 @@ __declspec(dllexport) void winampGetExtendedRead_close(void *handle) {
/* other winamp sekrit exports: */ /* other winamp sekrit exports: */
#if 0 #if 0
__declspec(dllexport) void winampAddUnifiedFileInfoPane(?) { winampGetExtendedRead_open_float
? winampGetExtendedRead_openW_float
} void winampAddUnifiedFileInfoPane
#endif #endif

View File

@ -91,6 +91,7 @@ extern winamp_log_t* walog;
//todo there must be a better way to handle unicode... //todo there must be a better way to handle unicode...
#ifdef UNICODE_INPUT_PLUGIN #ifdef UNICODE_INPUT_PLUGIN
#define wa_strcmp wcscmp #define wa_strcmp wcscmp
#define wa_strncmp wcsncmp
#define wa_strcpy wcscpy #define wa_strcpy wcscpy
#define wa_strncpy wcsncpy #define wa_strncpy wcsncpy
#define wa_strcat wcscat #define wa_strcat wcscat
@ -104,6 +105,7 @@ extern winamp_log_t* walog;
#define wa_L(x) L ##x #define wa_L(x) L ##x
#else #else
#define wa_strcmp strcmp #define wa_strcmp strcmp
#define wa_strncmp strncmp
#define wa_strcpy strcpy #define wa_strcpy strcpy
#define wa_strncpy strncpy #define wa_strncpy strncpy
#define wa_strcat strcat #define wa_strcat strcat