mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-12-01 01:27:20 +01:00
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:
commit
a87d040a3a
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -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: ''
|
||||
|
@ -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: ''
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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")
|
||||
|
11
doc/TXTH.md
11
doc/TXTH.md
@ -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
|
||||
```
|
||||
|
||||
|
63
doc/USAGE.md
63
doc/USAGE.md
@ -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
|
||||
|
@ -468,6 +468,7 @@ static const char* extension_list[] = {
|
||||
"scd",
|
||||
"sch",
|
||||
"sd9",
|
||||
"sdp", //txth/reserved [Metal Gear Arcade (AC)]
|
||||
"sdf",
|
||||
"sdt",
|
||||
"seb",
|
||||
|
@ -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]);
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user