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
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] "
labels: bug
assignees: ''

View File

@ -1,6 +1,6 @@
---
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]"
labels: enhancement
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_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("-cv","--command-volume", help="sets volume")
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()
@ -134,10 +135,15 @@ def main():
len_segments = len(segments)
with open(name,"w+") as ftxtp:
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:
ftxtp.write(segment + " #e\n")
txtp_line = "%s #e%s\n" % (segment, command_inline)
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:
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
# - PSX_bf PlayStation ADPCM with bad flags
# * 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)
# * For many XBOX games, and some PC games
# * 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)
# * For rare PS2 games [Sweet Legacy (PS2), Hooligan (PS2)]
# - 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)
# * For some 3DS games and many iOS games
# * 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`:
- `default`: values 0 or 0xFFFF/0xFFFFFFFF (-1) disable looping, but not 0xFF (loop endlessly)
- `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
```
@ -595,10 +598,10 @@ data_size = @0x100 # useless as num_samples is already transformed
### Redefining values
Some commands alter the function of all next commands and can be redefined as needed:
```
samples_type = bytes
sample_type = bytes
num_samples = @0x10
samples_type = sample
sample_type = sample
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,
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
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
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)
*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
automatically. You need to manually refresh it by selecting songs and doing
Note that vgmstream currently requires at least foobar v1.5 to run.
#### 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)**.
@ -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,
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.
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,
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`
(or any other similar case).
#### Missing subsongs
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
@ -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)
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)
*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
(`-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
`.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

View File

@ -468,6 +468,7 @@ static const char* extension_list[] = {
"scd",
"sch",
"sd9",
"sdp", //txth/reserved [Metal Gear Arcade (AC)]
"sdf",
"sdt",
"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"
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 };
/* 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 };
/* 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 };
/* 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_"
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 };
/* 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"
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:
// - Battle: Los Angeles
// - Guitar Hero: Warriors of Rock, DJ hero FSB
@ -108,8 +111,7 @@ typedef struct {
static const fsbkey_info fsbkey_list[] = {
{ 0,0, sizeof(key_dj2),key_dj2 },
{ 0,0, sizeof(key_dfp),key_dfp },//FSB4
{ 1,0, sizeof(key_dfp),key_dfp },//untested
{ 1,1, sizeof(key_dfp),key_dfp },//untested
{ 1,0, sizeof(key_dfp),key_dfp },//FSB5
{ 1,0, sizeof(key_npp),key_npp },//FSB5
{ 1,0, sizeof(key_sms),key_sms },//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
{ 1,0, sizeof(key_xxc),key_xxc },//untested
{ 1,1, sizeof(key_xxc),key_xxc },//untested
{ 0,0, sizeof(key_mwr),key_mwr },//untested
{ 0,1, sizeof(key_mwr),key_mwr },//untested
{ 1,0, sizeof(key_mwr),key_mwr },//untested
{ 1,1, sizeof(key_mwr),key_mwr },//untested
{ 1,0, sizeof(key_mwr),key_mwr },//FSB5
{ 0,0, 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,1, 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_fg2),key_fg2 },// 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]);

View File

@ -49,6 +49,7 @@ typedef enum {
PCM_FLOAT_LE,
IMA_HV,
PCM8_SB,
HEVAG,
UNKNOWN = 99,
} txth_codec_t;
@ -217,7 +218,8 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
uint32_t interleave = 0;
switch(txth.codec) {
case PSX:
case PSX_bf: interleave = 0x10; break;
case PSX_bf:
case HEVAG: interleave = 0x10; break;
case NGC_DSP: interleave = 0x08; break;
case PCM16LE:
case PCM16BE: interleave = 0x02; break;
@ -235,6 +237,8 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
/* type to coding conversion */
switch (txth.codec) {
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 NGC_DTK: coding = coding_NGC_DTK; break;
case PCM16LE: coding = coding_PCM16LE; break;
@ -254,7 +258,6 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
case AICA: coding = coding_AICA; break;
case MSADPCM: coding = coding_MSADPCM; 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 APPLE_IMA4: coding = coding_APPLE_IMA4; break;
#ifdef VGM_USE_FFMPEG
@ -320,6 +323,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
case coding_SDX2:
case coding_PSX:
case coding_PSX_badflags:
case coding_HEVAG:
case coding_DVI_IMA:
case coding_IMA:
case coding_HV_IMA:
@ -351,6 +355,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
if (!txth.interleave && (
coding == coding_PSX ||
coding == coding_PSX_badflags ||
coding == coding_HEVAG ||
coding == coding_IMA_int ||
coding == coding_DVI_IMA_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,"PCM_FLOAT_LE")) return PCM_FLOAT_LE;
else if (is_string(val,"IMA_HV")) return IMA_HV;
else if (is_string(val,"HEVAG")) return HEVAG;
/* special handling */
else if (is_string(val,"name_value")) 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) {
if (txth->loop_flag == 0xFF || txth->loop_flag == 0xFFFF || txth->loop_flag == 0xFFFFFFFF)
txth->loop_flag = 0;
else if (txth->loop_flag == 0)
txth->loop_flag = 1;
}
else if (txth->loop_behavior == INVERTED) {
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);
case PSX:
case PSX_bf:
case HEVAG:
return ps_bytes_to_samples(bytes, txth->channels);
case PCM16BE:
case PCM16LE:

View File

@ -193,53 +193,37 @@ static int is_xmplay() {
return 0;
}
/* Adds ext to Winamp's extension list */
static void add_extension(char* dst, int dst_len, const char* ext) {
char buf[EXT_BUFFER_SIZE];
char ext_upp[EXT_BUFFER_SIZE];
int ext_len, written;
int i,j;
if (dst_len <= 1)
return;
static int add_extension(char* dst, int dst_len, const char* ext) {
int ext_len;
ext_len = strlen(ext);
if (dst_len <= ext_len + 1)
return 0;
/* find end of dst (double \0), saved in i */
for (i = 0; i < dst_len - 2 && (dst[i] || dst[i+1]); i++)
;
strcpy(dst, ext); /* seems winamp uppercases this if needed */
dst[ext_len] = ';';
/* check if end reached or not enough room to add */
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';
return ext_len + 1;
}
/* Creates Winamp's extension list, a single string that ends with \0\0.
* Each extension must be in this format: "extension\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. */
* Each extension must be in this format: "extensions\0Description\0"
*
* 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) {
const char** ext_list;
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[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);
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)
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 */
if (settings.gain_type == REPLAYGAIN_ALBUM
&& 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 */
build_extension_list(working_extension_list, EXTENSION_LIST_SIZE);
build_extension_list(working_extension_list, sizeof(working_extension_list));
}
/* called at program quit */
@ -394,26 +402,34 @@ int winamp_IsOurFile(const in_char *fn) {
char filename_utf8[PATH_LIMIT];
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?).
* Then passes "hi.mp3" (no path) as a last resort if no plugin plays the file. This makes non-playable files
* show time 0:00 and use Winamp's dialog (kinda annoying). Subsongs' fake filenames that remain blank (good).
*
* 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) {
/* Detect repeat retries and fake "hi." calls as they are useless for our detection.
* before only ignored "hi's" when commons exts where on but who wants unplayable files reporting 0:00. */
if (wa_strcmp(fn, info_fn) == 0) { //TODO may need to check file size to invalidate cache
//;vgm_logi("winamp_IsOurFile: repeated call\n");
return info_valid;
}
if (settings.exts_common_on && wa_strcmp(fn, wa_L("hi.mp3")) == 0) {
//vgm_logi("winamp_IsOurFile: ignored fakefile\n");
if (/*settings.exts_common_on &&*/ wa_strncmp(fn, wa_L("hi."), 3) == 0) {
//;vgm_logi("winamp_IsOurFile: ignored fakefile\n");
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_common = settings.exts_common_on;
@ -421,16 +437,9 @@ int winamp_IsOurFile(const in_char *fn) {
//;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
* means "default", that being: let other plugins check the file; if no plugin claims it by
* returning 1 Winamp will try to match file<>plugin via extension_list. So it's common for
* 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 */
/* Return 1 if we actually handle the format or 0 to let other plugins handle it. Checking the
* extension alone isn't enough, as we may hijack stuff like in_vgm's *.vgm, so also try to
* open/get info from the file (slower so keep some cache) */
info_valid = 0; /* may not be playable */
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);
}
//;vgm_logi("winamp_IsOurFile: accepted\n");
info_valid = 1;
close_vgmstream(infostream);
@ -629,7 +639,7 @@ void winamp_SetPan(int pan) {
/* display info box (ALT+3) */
int winamp_InfoBox(const in_char *fn, HWND hwnd) {
char description[1024] = {0}, tmp[1024] = {0};
char description[1024] = {0};
TCHAR tbuf[1024] = {0};
double tmpVolume = 1.0;
@ -659,8 +669,11 @@ int winamp_InfoBox(const in_char *fn, HWND hwnd) {
tmpVolume = get_album_gain_volume(fn);
}
if (tmpVolume != 1.0) {
char tmp[128] = {0};
snprintf(tmp, sizeof(tmp), "\nvolume: %.6f\n", tmpVolume);
concatn(sizeof(description), description, tmp);
}
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 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_tagfile_info(filename);
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 */
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);
//;{ vgm_logi("winampGetExtendedFileInfo: file %s\n", filename); }
ok = winampGetExtendedFileInfo_common(filename_wchar, metadata, ret, retlen);
if (ok == 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);
//;{ 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);
if (ok == 0)
return 0;
@ -1115,10 +1134,10 @@ short xsample_buffer[SAMPLE_BUFFER_SIZE*2 * VGMSTREAM_MAX_CHANNELS];
/* open the file and prepares to decode */
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;
//;{ 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 */
xvgmstream = init_vgmstream_winamp_fileinfo(fn);
@ -1154,7 +1173,7 @@ static void *winampGetExtendedRead_open_common(in_char *fn, int *size, int *bps,
return xvgmstream; /* handle passed to other extended functions */
}
__declspec(dllexport) void *winampGetExtendedRead_open(const char *fn, int *size, int *bps, int *nch, int *srate) {
__declspec(dllexport) void* winampGetExtendedRead_open(const char *fn, int *size, int *bps, int *nch, int *srate) {
in_char filename_wchar[PATH_LIMIT];
wa_char_to_ichar(filename_wchar, PATH_LIMIT, fn);
@ -1162,7 +1181,7 @@ __declspec(dllexport) void *winampGetExtendedRead_open(const char *fn, int *size
return winampGetExtendedRead_open_common(filename_wchar, size, bps, nch, srate);
}
__declspec(dllexport) void *winampGetExtendedRead_openW(const wchar_t *fn, int *size, int *bps, int *nch, int *srate) {
__declspec(dllexport) void* winampGetExtendedRead_openW(const wchar_t *fn, int *size, int *bps, int *nch, int *srate) {
in_char filename_ichar[PATH_LIMIT];
wa_wchar_to_ichar(filename_ichar, PATH_LIMIT, fn);
@ -1239,7 +1258,7 @@ __declspec(dllexport) void winampGetExtendedRead_close(void *handle) {
/* other winamp sekrit exports: */
#if 0
__declspec(dllexport) void winampAddUnifiedFileInfoPane(?) {
?
}
winampGetExtendedRead_open_float
winampGetExtendedRead_openW_float
void winampAddUnifiedFileInfoPane
#endif

View File

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