From 75dd10dd296ae398f82fb3210b5fb94dcbbce6b7 Mon Sep 17 00:00:00 2001 From: RamonUnch <74856804+RamonUnch@users.noreply.github.com> Date: Tue, 15 Dec 2020 18:58:46 +0100 Subject: [PATCH] Add files via upload --- http.c | 3314 +++++++++++++++++++++++++++++++++++++++++++++++++++ http.h | 59 + in2.h | 103 ++ in_opus.c | 1027 ++++++++++++++++ infobox.c | 627 ++++++++++ infobox.h | 14 + resample.c | 887 ++++++++++++++ resample.h | 258 ++++ resource.h | 43 + resource.rc | 89 ++ utf_ansi.c | 599 ++++++++++ wa_ipc.h | 2470 ++++++++++++++++++++++++++++++++++++++ winerrno.h | 92 ++ wspiapi.c | 243 ++++ wspiapi.h | 203 ++++ 15 files changed, 10028 insertions(+) create mode 100644 http.c create mode 100644 http.h create mode 100644 in2.h create mode 100644 in_opus.c create mode 100644 infobox.c create mode 100644 infobox.h create mode 100644 resample.c create mode 100644 resample.h create mode 100644 resource.h create mode 100644 resource.rc create mode 100644 utf_ansi.c create mode 100644 wa_ipc.h create mode 100644 winerrno.h create mode 100644 wspiapi.c create mode 100644 wspiapi.h diff --git a/http.c b/http.c new file mode 100644 index 0000000..93de708 --- /dev/null +++ b/http.c @@ -0,0 +1,3314 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE libopusfile SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE libopusfile SOURCE CODE IS (C) COPYRIGHT 2012 * + * by the Xiph.Org Foundation and contributors http://www.xiph.org/ * + ******************************************************************** + * This file was heavily modified by Rymond Gillibert mail me at: * + * raymond_gillibert@yahoo.fr * + * I Mostly removed all SSL/TSL related stuf in order to build the * + * URL support for libopusfile-0.11 without requiring openSSL In * + * addition some modifications were made to be able to build on * + * Windows 95 without Windows Socket 2 upgrade * + * * + ********************************************************************/ + + +#define OP_ENABLE_HTTP 1 + +#include "http.h" +#include +#include +#include +#include + +#include "wspiapi.h" // This is a modified version + +//////////////////////////////// +/*RFCs referenced in this file: +* RFC 761: DOD Standard Transmission Control Protocol +* RFC 1535: A Security Problem and Proposed Correction With Widely Deployed DNS +* Software +* RFC 1738: Uniform Resource Locators (URL) +* RFC 1945: Hypertext Transfer Protocol -- HTTP/1.0 +* RFC 2068: Hypertext Transfer Protocol -- HTTP/1.1 +* RFC 2145: Use and Interpretation of HTTP Version Numbers +* RFC 2246: The TLS Protocol Version 1.0 +* RFC 2459: Internet X.509 Public Key Infrastructure Certificate and +* Certificate Revocation List (CRL) Profile +* RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1 +* RFC 2617: HTTP Authentication: Basic and Digest Access Authentication +* RFC 2817: Upgrading to TLS Within HTTP/1.1 +* RFC 2818: HTTP Over TLS +* RFC 3492: Punycode: A Bootstring encoding of Unicode for Internationalized +* Domain Names in Applications (IDNA) +* RFC 3986: Uniform Resource Identifier (URI): Generic Syntax +* RFC 3987: Internationalized Resource Identifiers (IRIs) +* RFC 4343: Domain Name System (DNS) Case Insensitivity Clarification +* RFC 5894: Internationalized Domain Names for Applications (IDNA): +* Background, Explanation, and Rationale +* RFC 6066: Transport Layer Security (TLS) Extensions: Extension Definitions +* RFC 6125: Representation and Verification of Domain-Based Application Service +* Identity within Internet Public Key Infrastructure Using X.509 (PKIX) +* Certificates in the Context of Transport Layer Security (TLS) +* RFC 6555: Happy Eyeballs: Success with Dual-Stack Hosts +**/ + +typedef struct OpusParsedURL OpusParsedURL; +typedef struct OpusStringBuf OpusStringBuf; +typedef struct OpusHTTPConn OpusHTTPConn; +typedef struct OpusHTTPStream OpusHTTPStream; + +static char *op_string_range_dup(const char *_start, const char *_end) +{ + size_t len; + char *ret; + OP_ASSERT(_start <= _end); + len = _end - _start; + /*This is to help avoid overflow elsewhere, later. */ + if (len >= INT_MAX) + return NULL; + ret = _ogg_malloc(sizeof(*ret) * (len + 1)); + if (ret != NULL) { + ret = (char *) memcpy(ret, _start, sizeof(*ret) * (len)); + ret[len] = '\0'; + } + return ret; +} + +static char *op_string_dup(const char *_s) +{ + return op_string_range_dup(_s, _s + strlen(_s)); +} + +static char *op_string_tolower(char *_s) +{ + int i; + for (i = 0; _s[i] != '\0'; i++) { + int c; + c = _s[i]; + if (c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + _s[i] = (char) c; + } + return _s; +} + +/*URI character classes (from RFC 3986).*/ +#define OP_URL_ALPHA \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +#define OP_URL_DIGIT "0123456789" +#define OP_URL_HEXDIGIT "0123456789ABCDEFabcdef" + +/*Not a character class, but the characters allowed in .*/ +#define OP_URL_SCHEME OP_URL_ALPHA OP_URL_DIGIT "+-." +#define OP_URL_GEN_DELIMS "#/:?@[]" +#define OP_URL_SUB_DELIMS "!$&'()*+,;=" +#define OP_URL_RESERVED OP_URL_GEN_DELIMS OP_URL_SUB_DELIMS +#define OP_URL_UNRESERVED OP_URL_ALPHA OP_URL_DIGIT "-._~" +/*Not a character class, but the characters allowed in .*/ +#define OP_URL_PCT_ENCODED "%" +/*Not a character class or production rule, but for convenience.*/ +#define OP_URL_PCHAR_BASE \ + OP_URL_UNRESERVED OP_URL_PCT_ENCODED OP_URL_SUB_DELIMS +#define OP_URL_PCHAR OP_URL_PCHAR_BASE ":@" +/*Not a character class, but the characters allowed in and .*/ +#define OP_URL_PCHAR_NA OP_URL_PCHAR_BASE ":" +/*Not a character class, but the characters allowed in .*/ +#define OP_URL_PCHAR_NC OP_URL_PCHAR_BASE "@" +/*Not a character clsss, but the characters allowed in .*/ +#define OP_URL_PATH OP_URL_PCHAR "/" +/*Not a character class, but the characters allowed in / .*/ +#define OP_URL_QUERY_FRAG OP_URL_PCHAR "/?" + +/* Check the <% HEXDIG HEXDIG> escapes of a URL for validity. + * Return: 0 if valid, or a negative value (OP_FALSE) on failure. + * RFC 3986 says %00 "should be rejected if the application is not + * expecting to receive raw data within a component." + */ +static int op_validate_url_escapes(const char *_s) +{ + int i; + for (i = 0; _s[i]; i++) { + if (_s[i] == '%') { + if (!isxdigit(_s[i + 1]) || !isxdigit(_s[i + 2]) + || (_s[i + 1] == '0' && _s[i + 2] == '0')) { + return OP_FALSE; + } else { + i += 2; + } + } + } + return 0; +} + +/* Convert a hex digit to its actual value. + * _c: The hex digit to convert. + * Presumed to be valid ('0'...'9', 'A'...'F', or 'a'...'f'). + * Return: The value of the digit, in the range [0,15]. + */ +static int op_hex_value(int _c) +{ + return (_c >= 'a') ? _c - 'a' + 10 : (_c >='A') ? _c - 'A' + 10 : _c - '0'; +} + +/* Unescape all the <% HEXDIG HEXDIG> sequences in a string in-place. + * This does no validity checking. + */ +static char *op_unescape_url_component(char *_s) +{ + int i, j; + for (i = j = 0; _s[i]; i++, j++) { + if (_s[i] == '%') { + _s[i] = (char) (op_hex_value(_s[i+1]) << 4 + | op_hex_value(_s[i+2])); + i+=2; + } + } + return _s; +} + +/* Parse a file: URL. + * This code is not meant to be fast: strspn() with large sets is likely to be + * slow, but it is very convenient. + * It is meant to be RFC 1738-compliant (as updated by RFC 3986). + */ +static const char *op_parse_file_url(const char *_src) +{ + const char *scheme_end; + const char *path; + const char *path_end; + scheme_end = _src + strspn(_src, OP_URL_SCHEME); + if ( *scheme_end != ':' + || scheme_end - _src != 4 || op_strncasecmp(_src, "file", 4) != 0) { + /*Unsupported protocol. */ + return NULL; + } + /*Make sure all escape sequences are valid to simplify unescaping later. */ + if (op_validate_url_escapes(scheme_end + 1) < 0) + return NULL; + if (scheme_end[1] == '/' && scheme_end[2] == '/') { + const char *host; + /* file: URLs can have a host! + * Yeah, I was surprised, too, but that's what RFC 1738 says. + * It also says, "The file URL scheme is unusual in that it does not specify + * an Internet protocol or access method for such files; as such, its + * utility in network protocols between hosts is limited," which is a mild + * understatement.*/ + host = scheme_end + 3; + /*The empty host is what we expect. */ + if (*host == '/') { + path = host; + } else { + const char *host_end; + char host_buf[28]; + /* RFC 1738 says localhost "is interpreted as `the machine from which the + * URL is being interpreted,'" so let's check for it.*/ + host_end = host + strspn(host, OP_URL_PCHAR_BASE); + /* No allowed. + * This also rejects IP-Literals.*/ + if (*host_end != '/') + return NULL; + /* An escaped "localhost" can take at most 27 characters. */ + if (host_end - host > 27) + return NULL; + memcpy(host_buf, host, sizeof(*host_buf) * (host_end - host)); + host_buf[host_end - host] = '\0'; + op_unescape_url_component(host_buf); + op_string_tolower(host_buf); + /* Some other host: give up. */ + if (strcmp(host_buf, "localhost") != 0) + return NULL; + path = host_end; + } + } else { + path = scheme_end + 1; + } + + path_end = path + strspn(path, OP_URL_PATH); + /* This will reject a or component, too. + * I don't know what to do with queries, but a temporal fragment would at + * least make sense. + * RFC 1738 pretty clearly defines a that's equivalent to the + * RFC 3986 component for other schemes, but not the file: scheme, + * so I'm going to just reject it. */ + + if (*path_end != '\0') + return NULL; + + return path; +} + +#if defined(OP_ENABLE_HTTP) +# if defined(_WIN32) +# include +# include +# include "winerrno.h" + +typedef SOCKET op_sock; + +# define OP_INVALID_SOCKET (INVALID_SOCKET) + +/* Vista and later support WSAPoll(), but we don't want to rely on that. + * Instead we re-implement it badly using select(). + * Unfortunately, they define a conflicting struct pollfd, so we only define our + * own if it looks like that one has not already been defined. + */ +#if !defined(POLLIN) +# define POLLRDNORM (0x0100) /*Equivalent to POLLIN.*/ +# define POLLRDBAND (0x0200) /*Priority band data can be read.*/ +# define POLLIN (POLLRDNORM|POLLRDBAND) /*There is data to read.*/ +# define POLLPRI (0x0400) /*There is urgent data to read.*/ +# define POLLWRNORM (0x0010) /*Equivalent to POLLOUT.*/ +# define POLLOUT (POLLWRNORM) /*Writing now will not block.*/ +# define POLLWRBAND (0x0020) /*Priority data may be written.*/ +# define POLLERR (0x0001) /*Error condition (output only).*/ +# define POLLHUP (0x0002) /*Hang up (output only).*/ +# define POLLNVAL (0x0004) /*Invalid request: fd not open (output only).*/ + +struct pollfd { + op_sock fd; /*File descriptor. */ + short events; /*Requested events.*/ + short revents; /*Returned events. */ +}; +#endif /*POLLIN*/ +/* But Winsock never defines nfds_t (it's simply hard-coded to ULONG).*/ +typedef unsigned long nfds_t; + +/* The usage of FD_SET() below is O(N^2). + * This is okay because select() is limited to 64 sockets in Winsock, anyway. + * In practice, we only ever call it with one or two sockets. + */ +static int op_poll_win32(struct pollfd *_fds, nfds_t _nfds, int _timeout) +{ + struct timeval tv; + fd_set ifds; + fd_set ofds; + fd_set efds; + nfds_t i; + int ret; + FD_ZERO(&ifds); + FD_ZERO(&ofds); + FD_ZERO(&efds); + for (i = 0; i < _nfds; i++) { + _fds[i].revents = 0; + if (_fds[i].events & POLLIN) + FD_SET(_fds[i].fd, &ifds); + if (_fds[i].events & POLLOUT) + FD_SET(_fds[i].fd, &ofds); + FD_SET(_fds[i].fd, &efds); + } + if (_timeout >= 0) { + tv.tv_sec = _timeout / 1000; + tv.tv_usec = (_timeout % 1000) * 1000; + } + ret = select(-1, &ifds, &ofds, &efds, _timeout < 0 ? NULL : &tv); + if (ret > 0) { + for (i = 0; i < _nfds; i++) { + if (FD_ISSET(_fds[i].fd, &ifds)) + _fds[i].revents |= POLLIN; + if (FD_ISSET(_fds[i].fd, &ofds)) + _fds[i].revents |= POLLOUT; + /* This isn't correct: there are several different things that might have + * happened to a fd in efds, but I don't know a good way to distinguish + * them without more context from the caller. + * It's okay, because we don't actually check any of these bits, we just + * need _some_ bit set.*/ + if (FD_ISSET(_fds[i].fd, &efds)) + _fds[i].revents |= POLLHUP; + } + } + return ret; +} + +/*We define op_errno() to make it clear that it's not an l-value like normal + * errno is. + */ +# define op_errno() (WSAGetLastError()?WSAGetLastError()-WSABASEERR:0) +# define op_reset_errno() (WSASetLastError(0)) + +/* The remaining functions don't get an op_ prefix even though they only + * operate on sockets, because we don't use non-socket I/O here, and this + * minimizes the changes needed to deal with Winsock. + */ +# define close(_fd) closesocket(_fd) +/* This takes an int for the address length, even though the value is of type + * socklen_t (defined as an unsigned integer type with at least 32 bits).*/ +# define connect(_fd,_addr,_addrlen) \ + (((_addrlen)>(socklen_t)INT_MAX)? \ + WSASetLastError(WSA_NOT_ENOUGH_MEMORY),-1: \ + connect(_fd,_addr,(int)(_addrlen))) +/* This relies on sizeof(u_long)==sizeof(int), which is always true on both + * Win32 and Win64.*/ +# define ioctl(_fd,_req,_arg) ioctlsocket(_fd,_req,(u_long *)(_arg)) +# define getsockopt(_fd,_level,_name,_val,_len) \ + getsockopt(_fd,_level,_name,(char *)(_val),_len) +# define setsockopt(_fd,_level,_name,_val,_len) \ + setsockopt(_fd,_level,_name,(const char *)(_val),_len) +# define poll(_fds,_nfds,_timeout) op_poll_win32(_fds,_nfds,_timeout) + +# if defined(_MSC_VER) +typedef ptrdiff_t ssize_t; +# endif + +# else +/*Normal Berkeley sockets.*/ +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +typedef int op_sock; + +# define OP_INVALID_SOCKET (-1) + +//# define op_errno() (errno) +//# define op_reset_errno() (errno=0) + +# endif /* _WIN32 */ +# include + +/* The maximum number of simultaneous connections. + * RFC 2616 says this SHOULD NOT be more than 2, but everyone on the modern web + * ignores that (e.g., IE 8 bumped theirs up from 2 to 6, Firefox uses 15). + * If it makes you feel better, we'll only ever actively read from one of these + * at a time. + * The others are kept around mainly to avoid slow-starting a new connection + * when seeking, and time out rapidly. + */ +# define OP_NCONNS_MAX (4) + +/* The amount of time before we attempt to re-resolve the host. + * This is 10 minutes, as recommended in RFC 6555 for expiring cached connection + * results for dual-stack hosts. + */ +# define OP_RESOLVE_CACHE_TIMEOUT_MS (10*60*(opus_int32)1000) + +/* The number of redirections at which we give up. + * The value here is the current default in Firefox. + * RFC 2068 mandated a maximum of 5, but RFC 2616 relaxed that to "a client + * SHOULD detect infinite redirection loops." + * Fortunately, 20 is less than infinity. + */ +# define OP_REDIRECT_LIMIT (20) + +/* The initial size of the buffer used to read a response message (before the + * body). + */ +# define OP_RESPONSE_SIZE_MIN (510) +/* The maximum size of a response message (before the body). + * Responses larger than this will be discarded. + * I've seen a real server return 20 kB of data for a 302 Found response. + * Increasing this beyond 32kB will cause problems on platforms with a 16-bit int. + */ +# define OP_RESPONSE_SIZE_MAX (32766) + +/* The number of milliseconds we will allow a connection to sit idle before we + * refuse to resurrect it. + * Apache as of 2.2 has reduced its default timeout to 5 seconds (from 15), so + * that's what we'll use here. + */ +# define OP_CONNECTION_IDLE_TIMEOUT_MS (5*1000) + +/*The number of milliseconds we will wait to send or receive data before giving + up. (30 seconds) */ +# define OP_POLL_TIMEOUT_MS (30*1000) + +/*We will always attempt to read ahead at least this much in preference to + opening a new connection.*/ +# define OP_READAHEAD_THRESH_MIN (32*(opus_int32)1024) + +/*The amount of data to request after a seek. + This is a trade-off between read throughput after a seek vs. the the ability + to quickly perform another seek with the same connection.*/ +# define OP_PIPELINE_CHUNK_SIZE (32*(opus_int32)1024) + +/*Subsequent chunks are requested with larger and larger sizes until they pass + this threshold, after which we just ask for the rest of the resource.*/ +# define OP_PIPELINE_CHUNK_SIZE_MAX (1024*(opus_int32)1024) + +/*This is the maximum number of requests we'll make with a single connection. + Many servers will simply disconnect after we attempt some number of requests, + possibly without sending a Connection: close header, meaning we won't + discover it until we try to read beyond the end of the current chunk. + We can reconnect when that happens, but this is slow. + Instead, we impose a limit ourselves (set to the default for Apache + installations and thus likely the most common value in use).*/ +# define OP_PIPELINE_MAX_REQUESTS (100) + +/*This should be the number of requests, starting from a chunk size of + OP_PIPELINE_CHUNK_SIZE and doubling each time, until we exceed + OP_PIPELINE_CHUNK_SIZE_MAX and just request the rest of the file. + We won't reuse a connection when seeking unless it has at least this many + requests left, to reduce the chances we'll have to open a new connection + while reading forward afterwards.*/ +# define OP_PIPELINE_MIN_REQUESTS (7) + +/*Is this an https URL? + For now we can simply check the last letter of the scheme.*/ +//# define OP_URL_IS_SSL(_url) ((_url)->scheme[4]=='s') +# define OP_URL_IS_SSL(_url) 0 +/*Does this URL use the default port for its scheme?*/ +# define OP_URL_IS_DEFAULT_PORT(_url) ( (!OP_URL_IS_SSL(_url)&&(_url)->port==80) \ + ||(OP_URL_IS_SSL(_url)&&(_url)->port==443) ) + +struct OpusParsedURL { + /*Either "http" or "https". */ + char *scheme; + /*The user name from the component, or NULL. */ + char *user; + /*The password from the component, or NULL. */ + char *pass; + /*The component. + This may not be NULL. */ + char *host; + /*The and components. + This may not be NULL. */ + char *path; + /*The component. + This is set to the default port if the URL did not contain one. */ + unsigned port; +}; + +/* Parse a URL. + * This code is not meant to be fast: strspn() with large sets is likely to be + * slow, but it is very convenient. + * It is meant to be RFC 3986-compliant. + * We currently do not support IRIs + * (Internationalized Resource Identifiers, RFC 3987). + * Callers should translate them to URIs first. + */ +static int op_parse_url_impl(OpusParsedURL * _dst, const char *_src) +{ + const char *scheme_end; + const char *authority; + const char *userinfo_end; + const char *user; + const char *user_end; + const char *pass; + const char *hostport; + const char *hostport_end; + const char *host_end; + const char *port; + opus_int32 port_num; + const char *port_end; + const char *path; + const char *path_end; + const char *uri_end; + + scheme_end = _src + strspn(_src, OP_URL_SCHEME); + + if ( (*scheme_end != ':') + || (scheme_end - _src < 4) + || (scheme_end - _src > 5) + || op_strncasecmp(_src, "https", (int) (scheme_end - _src)) != 0) { + /*Unsupported protocol. */ + return OP_EIMPL; + } else if (scheme_end[1] != '/' || scheme_end[2] != '/') { + /*We require an component. */ + return OP_EINVAL; + } + + authority = scheme_end + 3; + + /*Make sure all escape sequences are valid to simplify unescaping later. */ + if (op_validate_url_escapes(authority) < 0) + return OP_EINVAL; + + /*Look for a component. */ + userinfo_end = authority + strspn(authority, OP_URL_PCHAR_NA); + if (*userinfo_end == '@') { + /*Found one. */ + user = authority; + /* Look for a password (yes, clear-text passwords are deprecated, I know, + * but what else are people supposed to use? use SSL if you care). */ + user_end = authority + strspn(authority, OP_URL_PCHAR_BASE); + if (*user_end == ':') + pass = user_end + 1; + else + pass = NULL; + hostport = userinfo_end + 1; + } else { + /*We shouldn't have to initialize user_end, but gcc is too dumb to figure + out that user!=NULL below means we didn't take this else branch. */ + user = user_end = NULL; + pass = NULL; + hostport = authority; + } + /*Try to figure out where the component ends. */ + if (hostport[0] == '[') { + hostport++; + /*We have an , which can contain colons. */ + hostport_end = host_end = + hostport + strspn(hostport, OP_URL_PCHAR_NA); + if (*hostport_end++ != ']') + return OP_EINVAL; + } + /* Currently we don't support IDNA (RFC 5894), because I don't want to deal + * with the policy about which domains should not be internationalized to + * avoid confusing similarities. + * Give this API Punycode (RFC 3492) domain names instead. + */ + else + hostport_end = host_end = + hostport + strspn(hostport, OP_URL_PCHAR_BASE); + /*TODO: Validate host. */ + /*Is there a port number? */ + port_num = -1; + if (*hostport_end == ':') { + int i; + port = hostport_end + 1; + port_end = port + strspn(port, OP_URL_DIGIT); + path = port_end; + /*Not part of RFC 3986, but require port numbers in the range 0...65535. */ + if (port_end - port > 0) { + while (*port == '0') + port++; + if (port_end - port > 5) + return OP_EINVAL; + port_num = 0; + for (i = 0; i < port_end - port; i++) + port_num = port_num * 10 + port[i] - '0'; + if (port_num > 65535) + return OP_EINVAL; + } + } else + path = hostport_end; + path_end = path + strspn(path, OP_URL_PATH); + /*If the path is not empty, it must begin with a '/'. */ + if (path_end > path && path[0] != '/') + return OP_EINVAL; + /*Consume the component, if any (right now we don't split this out + from the component). */ + if (*path_end == '?') + path_end = path_end + strspn(path_end, OP_URL_QUERY_FRAG); + /*Discard the component, if any. + This doesn't get sent to the server. + Some day we should add support for Media Fragment URIs + . */ + if (*path_end == '#') + uri_end = path_end + 1 + strspn(path_end + 1, OP_URL_QUERY_FRAG); + else + uri_end = path_end; + /*If there's anything left, this was not a valid URL. */ + if (*uri_end != '\0') + return OP_EINVAL; + _dst->scheme = op_string_range_dup(_src, scheme_end); + if (_dst->scheme == NULL) + return OP_EFAULT; + op_string_tolower(_dst->scheme); + if (user != NULL) { + _dst->user = op_string_range_dup(user, user_end); + if (_dst->user == NULL) + return OP_EFAULT; + op_unescape_url_component(_dst->user); + /*Unescaping might have created a ':' in the username. + That's not allowed by RFC 2617's Basic Authentication Scheme. */ + if (strchr(_dst->user, ':') != NULL) + return OP_EINVAL; + } else + _dst->user = NULL; + if (pass != NULL) { + _dst->pass = op_string_range_dup(pass, userinfo_end); + if (_dst->pass == NULL) + return OP_EFAULT; + op_unescape_url_component(_dst->pass); + } else + _dst->pass = NULL; + _dst->host = op_string_range_dup(hostport, host_end); + if (_dst->host == NULL) + return OP_EFAULT; + if (port_num < 0) { + if (_src[4] == 's') + port_num = 443; + else + port_num = 80; + } + _dst->port = (unsigned) port_num; + /*RFC 2616 says an empty component is equivalent to "/", and we + MUST use the latter in the Request-URI. + Reserve space for the slash here. */ + if (path == path_end || path[0] == '?') + path--; + _dst->path = op_string_range_dup(path, path_end); + if (_dst->path == NULL) + return OP_EFAULT; + /*And force-set it here. */ + _dst->path[0] = '/'; + return 0; +} + +static void op_parsed_url_init(OpusParsedURL * _url) +{ + memset(_url, 0, sizeof(*_url)); +} + +static void op_parsed_url_clear(OpusParsedURL * _url) +{ + _ogg_free(_url->scheme); + _ogg_free(_url->user); + _ogg_free(_url->pass); + _ogg_free(_url->host); + _ogg_free(_url->path); +} + +static int op_parse_url(OpusParsedURL * _dst, const char *_src) +{ + OpusParsedURL url; + int ret; + op_parsed_url_init(&url); + ret = op_parse_url_impl(&url, _src); + if (ret < 0) + op_parsed_url_clear(&url); + else + *_dst = url; + return ret; +} + +/* A buffer to hold growing strings. + * The main purpose of this is to consolidate allocation checks and simplify + * cleanup on a failed allocation. + */ +struct OpusStringBuf { + char *buf; + int nbuf; + int cbuf; +}; + +static void op_sb_init(OpusStringBuf * _sb) +{ + _sb->buf = NULL; + _sb->nbuf = 0; + _sb->cbuf = 0; +} + +static void op_sb_clear(OpusStringBuf * _sb) +{ + _ogg_free(_sb->buf); +} + +/* Make sure we have room for at least _capacity characters (plus 1 more for the + * terminating NUL). + */ +static int op_sb_ensure_capacity(OpusStringBuf * _sb, int _capacity) +{ + char *buf; + int cbuf; + buf = _sb->buf; + cbuf = _sb->cbuf; + if (_capacity >= cbuf - 1) { + if (cbuf > (INT_MAX - 1) >> 1) + return OP_EFAULT; + if (_capacity >= INT_MAX - 1) + return OP_EFAULT; + cbuf = OP_MAX(2 * cbuf + 1, _capacity + 1); + buf = _ogg_realloc(buf, sizeof(*buf) * cbuf); + if (buf == NULL) + return OP_EFAULT; + _sb->buf = buf; + _sb->cbuf = cbuf; + } + return 0; +} + +/* Increase the capacity of the buffer, but not to more than _max_size + * characters (plus 1 more for the terminating NUL). + */ +static int op_sb_grow(OpusStringBuf * _sb, int _max_size) +{ + char *buf; + int cbuf; + buf = _sb->buf; + cbuf = _sb->cbuf; + OP_ASSERT(_max_size <= INT_MAX - 1); + cbuf = cbuf <= (_max_size - 1) >> 1 ? 2 * cbuf + 1 : _max_size + 1; + buf = _ogg_realloc(buf, sizeof(*buf) * cbuf); + if (buf == NULL) + return OP_EFAULT; + _sb->buf = buf; + _sb->cbuf = cbuf; + return 0; +} + +static int op_sb_append(OpusStringBuf * _sb, const char *_s, int _len) +{ + char *buf; + int nbuf; + int ret; + nbuf = _sb->nbuf; + if (nbuf > INT_MAX - _len) + return OP_EFAULT; + ret = op_sb_ensure_capacity(_sb, nbuf + _len); + if (ret < 0) + return ret; + buf = _sb->buf; + memcpy(buf + nbuf, _s, sizeof(*buf) * _len); + nbuf += _len; + buf[nbuf] = '\0'; + _sb->nbuf = nbuf; + return 0; +} + +static int op_sb_append_string(OpusStringBuf * _sb, const char *_s) +{ + size_t len; + len = strlen(_s); + if (len > (size_t) INT_MAX) + return OP_EFAULT; + return op_sb_append(_sb, _s, (int) len); +} + +static int op_sb_append_port(OpusStringBuf * _sb, unsigned _port) +{ + char port_buf[7]; + OP_ASSERT(_port <= 65535U); + sprintf(port_buf, ":%u", _port); + return op_sb_append_string(_sb, port_buf); +} + +static int op_sb_append_nonnegative_int64(OpusStringBuf * _sb, opus_int64 _i) +{ + char digit; + int nbuf_start; + int ret; + OP_ASSERT(_i >= 0); + nbuf_start = _sb->nbuf; + ret = 0; + do { + digit = '0' + _i % 10; + ret |= op_sb_append(_sb, &digit, 1); + _i /= 10; + } + while (_i > 0); + if (ret >= 0) { + char *buf; + int nbuf_end; + buf = _sb->buf; + nbuf_end = _sb->nbuf - 1; + /*We've added the digits backwards. + Reverse them. */ + while (nbuf_start < nbuf_end) { + digit = buf[nbuf_start]; + buf[nbuf_start] = buf[nbuf_end]; + buf[nbuf_end] = digit; + nbuf_start++; + nbuf_end--; + } + } + return ret; +} + +static struct addrinfo *op_resolve(const char *_host, unsigned _port) +{ + struct addrinfo *addrs; + struct addrinfo hints; + char service[6]; + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; +#if defined(AI_NUMERICSERV) + hints.ai_flags = AI_NUMERICSERV; +#endif + OP_ASSERT(_port <= 65535U); + sprintf(service, "%u", _port); + if ((!WspiapiLegacyGetAddrInfo(_host, service, &hints, &addrs))) + return addrs; + return NULL; +} + +static int op_sock_set_nonblocking(op_sock _fd, int _nonblocking) +{ +#if !defined(_WIN32) + int flags; + flags = fcntl(_fd, F_GETFL); + if (flags < 0) + return flags; + if (_nonblocking) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + return fcntl(_fd, F_SETFL, flags); +#else + return ioctl(_fd, FIONBIO, &_nonblocking); +#endif +} + +/* Disable/enable write coalescing if we can. + * We always send whole requests at once and always parse the response headers + * before sending another one, so normally write coalescing just causes added + * delay. + */ +static void op_sock_set_tcp_nodelay(op_sock _fd, int _nodelay) +{ +# if defined(TCP_NODELAY)&&(defined(IPPROTO_TCP)||defined(SOL_TCP)) +# if defined(IPPROTO_TCP) +# define OP_SO_LEVEL IPPROTO_TCP +# else +# define OP_SO_LEVEL SOL_TCP +# endif + /*It doesn't really matter if this call fails, but it would be interesting + to hit a case where it does. */ + OP_ALWAYS_TRUE(!setsockopt(_fd, OP_SO_LEVEL, TCP_NODELAY, + &_nodelay, sizeof(_nodelay))); +# endif +} + +#if defined(_WIN32) +static void op_init_winsock() +{ + static LONG count; + static WSADATA wsadata; + if (InterlockedIncrement(&count) == 1) + WSAStartup(0x0202, &wsadata); +} +#endif + +/*A single physical connection to an HTTP server. + We may have several of these open at once.*/ +struct OpusHTTPConn { + /*The current position indicator for this connection. */ + opus_int64 pos; + /*The position where the current request will end, or -1 if we're reading + until EOF (an unseekable stream or the initial HTTP/1.0 request). */ + opus_int64 end_pos; + /*The position where next request we've sent will start, or -1 if we haven't + sent the next request yet. */ + opus_int64 next_pos; + /*The end of the next request or -1 if we requested the rest of the resource. + This is only set to a meaningful value if next_pos is not -1. */ + opus_int64 next_end; + /*The SSL connection, if this is https. */ +// SSL *ssl_conn; + /*The next connection in either the LRU or free list. */ + OpusHTTPConn *next; + /*The last time we blocked for reading from this connection. */ + struct _timeb read_time; + /*The number of bytes we've read since the last time we blocked. */ + opus_int64 read_bytes; + /*The estimated throughput of this connection, in bytes/s. */ + opus_int64 read_rate; + /*The socket we're reading from. */ + op_sock fd; + /*The number of remaining requests we are allowed on this connection. */ + int nrequests_left; + /*The chunk size to use for pipelining requests. */ + opus_int32 chunk_size; +}; + +static void op_http_conn_init(OpusHTTPConn * _conn) +{ + _conn->next_pos = -1; +// _conn->ssl_conn=NULL; + _conn->next = NULL; + _conn->fd = OP_INVALID_SOCKET; +} + +static void op_http_conn_clear(OpusHTTPConn * _conn) +{ + /*SSL frees the BIO for us. */ + if (_conn->fd != OP_INVALID_SOCKET) + close(_conn->fd); +} + +/*The global stream state.*/ +struct OpusHTTPStream { + /*The list of connections. */ + OpusHTTPConn conns[OP_NCONNS_MAX]; + /*The context object used as a framework for TLS/SSL functions. */ +// SSL_CTX *ssl_ctx; + /*The cached session to reuse for future connections. */ +// SSL_SESSION *ssl_session; + /*The LRU list (ordered from MRU to LRU) of currently connected + connections. */ + OpusHTTPConn *lru_head; + /*The free list. */ + OpusHTTPConn *free_head; + /*The URL to connect to. */ + OpusParsedURL url; + /*Information about the address we connected to. */ + struct addrinfo addr_info; + /*The address we connected to. */ + union { + struct sockaddr s; + struct sockaddr_in v4; + struct sockaddr_in6 v6; + } addr; + /*The last time we re-resolved the host. */ + struct _timeb resolve_time; + /*A buffer used to build HTTP requests. */ + OpusStringBuf request; + /*A buffer used to build proxy CONNECT requests. */ + OpusStringBuf proxy_connect; + /*A buffer used to receive the response headers. */ + OpusStringBuf response; + /*The Content-Length, if specified, or -1 otherwise. + This will always be specified for seekable streams. */ + opus_int64 content_length; + /*The position indicator used when no connection is active. */ + opus_int64 pos; + /*The host we actually connected to. */ + char *connect_host; + /*The port we actually connected to. */ + unsigned connect_port; + /*The connection we're currently reading from. + This can be -1 if no connection is active. */ + int cur_conni; + /*Whether or not the server supports range requests. */ + int seekable; + /*Whether or not the server supports HTTP/1.1 with persistent connections. */ + int pipeline; + /*Whether or not we should skip certificate checks. */ + int skip_certificate_check; + /*The offset of the tail of the request. + Only the offset in the Range: header appears after this, allowing us to + quickly edit the request to ask for a new range. */ + int request_tail; + /*The estimated time required to open a new connection, in milliseconds. */ + opus_int32 connect_rate; +}; + +static void op_http_stream_init(OpusHTTPStream * _stream) +{ + OpusHTTPConn **pnext; + int ci; + pnext = &_stream->free_head; + for (ci = 0; ci < OP_NCONNS_MAX; ci++) { + op_http_conn_init(_stream->conns + ci); + *pnext = _stream->conns + ci; + pnext = &_stream->conns[ci].next; + } + _stream->lru_head = NULL; + op_parsed_url_init(&_stream->url); + op_sb_init(&_stream->request); + op_sb_init(&_stream->proxy_connect); + op_sb_init(&_stream->response); + _stream->connect_host = NULL; + _stream->seekable = 0; +} + +/* Close the connection and move it to the free list. + * _stream: The stream containing the free list. + * _conn: The connection to close. + * _pnext: The linked-list pointer currently pointing to this connection. + * _gracefully: Whether or not to shut down cleanly. + */ +static void op_http_conn_close(OpusHTTPStream * _stream, + OpusHTTPConn * _conn, + OpusHTTPConn ** _pnext, int _gracefully) +{ + /* If we don't shut down gracefully, the server MUST NOT re-use our session + * according to RFC 2246, because it can't tell the difference between an + * abrupt close and a truncation attack. + * So we shut down gracefully if we can. + * However, we will not wait if this would block (it's not worth the savings + * from session resumption to do so). + * Clients (that's us) MAY resume a TLS session that ended with an incomplete + * close, according to RFC 2818, so there's no reason to make sure the server + * shut things down gracefully. + */ + op_http_conn_clear(_conn); + _conn->next_pos = -1; + _conn->fd = OP_INVALID_SOCKET; + OP_ASSERT(*_pnext == _conn); + *_pnext = _conn->next; + _conn->next = _stream->free_head; + _stream->free_head = _conn; +} + +static void op_http_stream_clear(OpusHTTPStream * _stream) +{ + while (_stream->lru_head != NULL) { + op_http_conn_close(_stream, _stream->lru_head, &_stream->lru_head, + 0); + } + op_sb_clear(&_stream->response); + op_sb_clear(&_stream->proxy_connect); + op_sb_clear(&_stream->request); + if (_stream->connect_host != _stream->url.host) + _ogg_free(_stream->connect_host); + op_parsed_url_clear(&_stream->url); +} + +static int op_http_conn_write_fully(OpusHTTPConn * _conn, const char *_buf, int _buf_size) +{ + struct pollfd fd; + fd.fd = _conn->fd; + while (_buf_size > 0) { + int err; + + ssize_t ret; + op_reset_errno(); + ret = send(fd.fd, _buf, _buf_size, 0); + if (ret > 0) { + _buf += ret; + OP_ASSERT(ret <= _buf_size); + _buf_size -= (int) ret; + continue; + } + + err = op_errno(); + if (err != EAGAIN && err != EWOULDBLOCK) + return OP_FALSE; + fd.events = POLLOUT; + + if (poll(&fd, 1, OP_POLL_TIMEOUT_MS) <= 0) + return OP_FALSE; + } + return 0; +} + +static int op_http_conn_estimate_available(OpusHTTPConn * _conn) +{ + int available; + int ret; + ret = ioctl(_conn->fd, FIONREAD, &available); + if (ret < 0) + available = 0; + + return available; +} + +static opus_int32 op_time_diff_ms(const struct _timeb *_end, + const struct _timeb *_start) +{ + opus_int64 dtime; + dtime = _end->time - (opus_int64) _start->time; + OP_ASSERT(_end->millitm < 1000); + OP_ASSERT(_start->millitm < 1000); + if (dtime > (OP_INT32_MAX - 1000) / 1000) + return OP_INT32_MAX; + if (dtime < (OP_INT32_MIN + 1000) / 1000) + return OP_INT32_MIN; + return (opus_int32) dtime *1000 + _end->millitm - _start->millitm; +} + +/*Update the read rate estimate for this connection.*/ +static void op_http_conn_read_rate_update(OpusHTTPConn * _conn) +{ + struct _timeb read_time; + opus_int32 read_delta_ms; + opus_int64 read_delta_bytes; + opus_int64 read_rate; + read_delta_bytes = _conn->read_bytes; + if (read_delta_bytes <= 0) + return; + _ftime(&read_time); + read_delta_ms = op_time_diff_ms(&read_time, &_conn->read_time); + read_rate = _conn->read_rate; + read_delta_ms = OP_MAX(read_delta_ms, 1); + read_rate += (read_delta_bytes * 1000 / read_delta_ms - read_rate + 4) >> 3; + _conn->read_time = read_time; + _conn->read_bytes = 0; + _conn->read_rate = read_rate; +} + +/* Tries to read from the given connection. + * [out] _buf: Returns the data read. + * _buf_size: The size of the buffer. + * _blocking: Whether or not to block until some data is retrieved. + * Return: A positive number of bytes read on success. + * 0: The read would block, or the connection was closed. + * OP_EREAD: There was a fatal read error. + */ +static int op_http_conn_read(OpusHTTPConn * _conn, char *_buf, int _buf_size, int _blocking) +{ + struct pollfd fd; + int nread; + int nread_unblocked; + fd.fd = _conn->fd; + nread = nread_unblocked = 0; + /* RFC 2818 says "client implementations MUST treat any premature closes as + * errors and the data received as potentially truncated," so we make very + * sure to report read errors upwards. + */ + do { + int err; + ssize_t ret; + + op_reset_errno(); + ret = recv(fd.fd, _buf + nread, _buf_size - nread, 0); + OP_ASSERT(ret <= _buf_size - nread); + if (ret > 0) { + /*Read some data. + Keep going to see if there's more. */ + OP_ASSERT(ret <= _buf_size - nread); + nread += (int) ret; + nread_unblocked += (int) ret; + continue; + } + /* If we already read some data or the connection was closed, + * return right now. */ + if (ret == 0 || nread > 0) + break; + err = op_errno(); + if (err != EAGAIN && err != EWOULDBLOCK) + return OP_EREAD; + fd.events = POLLIN; + + _conn->read_bytes += nread_unblocked; + op_http_conn_read_rate_update(_conn); + nread_unblocked = 0; + if (!_blocking) + break; + /*Need to wait to get any data at all. */ + if (poll(&fd, 1, OP_POLL_TIMEOUT_MS) <= 0) + return OP_EREAD; + } while (nread < _buf_size); + + _conn->read_bytes += nread_unblocked; + return nread; +} + +/* Tries to look at the pending data for a connection without consuming it. + * [out] _buf: Returns the data at which we're peeking. + * _buf_size: The size of the buffer. + */ +static int op_http_conn_peek(OpusHTTPConn * _conn, char *_buf, int _buf_size) +{ + struct pollfd fd; + int ret; + fd.fd = _conn->fd; + for (;;) { + int err; + + op_reset_errno(); + ret = (int) recv(fd.fd, _buf, _buf_size, MSG_PEEK); + + /*Either saw some data or the connection was closed. */ + if (ret >= 0) + return ret; + err = op_errno(); + if (err != EAGAIN && err != EWOULDBLOCK) + return 0; + fd.events = POLLIN; + + /*Need to wait to get any data at all. */ + if (poll(&fd, 1, OP_POLL_TIMEOUT_MS) <= 0) + return 0; + } +} + +/* When parsing response headers, RFC 2616 mandates that all lines end in CR LF. + * However, even in the year 2012, I have seen broken servers use just a LF. + * This is the evil that Postel's advice from RFC 761 breeds. + * + * Reads the entirety of a response to an HTTP request into the response buffer. + * Actual parsing and validation is done later. + * Return: The number of bytes in the response on success, OP_EREAD if the + * connection was closed before reading any data, or another negative + * value on any other error. + */ +static int op_http_conn_read_response(OpusHTTPConn * _conn, OpusStringBuf * _response) +{ + int ret; + _response->nbuf = 0; + ret = op_sb_ensure_capacity(_response, OP_RESPONSE_SIZE_MIN); + if (ret < 0) + return ret; + for (;;) { + char *buf; + int size; + int capacity; + int read_limit; + int terminated; + size = _response->nbuf; + capacity = _response->cbuf - 1; + if (size >= capacity) { + ret = op_sb_grow(_response, OP_RESPONSE_SIZE_MAX); + if (ret < 0) + return ret; + capacity = _response->cbuf - 1; + /*The response was too large. + This prevents a bad server from running us out of memory. */ + if (size >= capacity) + return OP_EIMPL; + } + buf = _response->buf; + ret = op_http_conn_peek(_conn, buf + size, capacity - size); + if (ret <= 0) + return size <= 0 ? OP_EREAD : OP_FALSE; + /* We read some data. */ + + /* Make sure the starting characters are "HTTP". + * Otherwise we could wind up waiting for a response from something that is + * not an HTTP server until we time out. */ + if (size < 4 && op_strncasecmp(buf, "HTTP", OP_MIN(size + ret, 4)) != 0) { + return OP_FALSE; + } + /*How far can we read without passing the "\r\n\r\n" terminator? */ + buf[size + ret] = '\0'; + terminated = 0; + for (read_limit = OP_MAX(size - 3, 0); read_limit < size + ret; read_limit++) { + /*We don't look for the leading '\r' thanks to broken servers. */ + if (buf[read_limit] == '\n') { + if (buf[read_limit + 1] == '\r' + && buf[read_limit + 2] == '\n') { + terminated = 3; + break; + } else if (buf[read_limit + 1] == '\n') { + /*This case is for broken servers. */ + terminated = 2; + break; + } + } + } + read_limit += terminated; + OP_ASSERT(size <= read_limit); + OP_ASSERT(read_limit <= size + ret); + /* Actually consume that data. */ + ret = op_http_conn_read(_conn, buf + size, read_limit - size, 1); + if (ret <= 0) + return OP_FALSE; + size += ret; + buf[size] = '\0'; + _response->nbuf = size; + /*We found the terminator and read all the data up to and including it. */ + if (terminated && size >= read_limit) + return size; + } + return OP_EIMPL; +} + +# define OP_HTTP_DIGIT "0123456789" + +/* The Reason-Phrase is not allowed to contain control characters, except + * horizontal tab (HT: \011).*/ + +# define OP_HTTP_CREASON_PHRASE \ + "\001\002\003\004\005\006\007\010\012\013\014\015\016\017\020\021" \ + "\022\023\024\025\026\027\030\031\032\033\034\035\036\037\177" + +# define OP_HTTP_CTLS \ + "\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020" \ + "\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\177" + +/*This also includes '\t', but we get that from OP_HTTP_CTLS.*/ +# define OP_HTTP_SEPARATORS " \"(),/:;<=>?@[\\]{}" + +/* TEXT can also include LWS, but that has structure, so we parse it + * separately.*/ +# define OP_HTTP_CTOKEN OP_HTTP_CTLS OP_HTTP_SEPARATORS + +/*Return: The amount of linear white space (LWS) at the start of _s.*/ +static int op_http_lwsspn(const char *_s) +{ + int i; + for (i = 0;;) { + if (_s[0] == '\r' && _s[1] == '\n' + && (_s[2] == '\t' || _s[2] == ' ')) + i += 3; + /*This case is for broken servers. */ + else if (_s[0] == '\n' && (_s[1] == '\t' || _s[1] == ' ')) + i += 2; + else if (_s[i] == '\t' || _s[i] == ' ') + i++; + else + return i; + } +} + +static char *op_http_parse_status_line(int *_v1_1_compat, + char **_status_code, + char *_response) +{ + char *next; + char *status_code; + int v1_1_compat; + size_t d; + /* RFC 2616 Section 6.1 does not say if the tokens in the Status-Line can be + * separated by optional LWS, but since it specifically calls out where + * spaces are to be placed and that CR and LF are not allowed except at the + * end, we are assuming extra LWS is not allowed. */ + /* We already validated that this starts with "HTTP" */ + OP_ASSERT(op_strncasecmp(_response, "HTTP", 4) == 0); + next = _response + 4; + if (*next++ != '/') + return NULL; + d = strspn(next, OP_HTTP_DIGIT); + /*"Leading zeros MUST be ignored by recipients." */ + while (*next == '0') { + next++; + OP_ASSERT(d > 0); + d--; + } + /*We only support version 1.x */ + if (d != 1 || *next++ != '1') + return NULL; + if (*next++ != '.') + return NULL; + d = strspn(next, OP_HTTP_DIGIT); + if (d <= 0) + return NULL; + /*"Leading zeros MUST be ignored by recipients." */ + while (*next == '0') { + next++; + OP_ASSERT(d > 0); + d--; + } + /* We don't need to parse the version number. + * Any non-zero digit means it's at least 1. */ + v1_1_compat = d > 0; + next += d; + if (*next++ != ' ') + return NULL; + status_code = next; + d = strspn(next, OP_HTTP_DIGIT); + if (d != 3) + return NULL; + next += d; + /*The Reason-Phrase can be empty, but the space must be here. */ + if (*next++ != ' ') + return NULL; + next += strcspn(next, OP_HTTP_CREASON_PHRASE); + /*We are not mandating this be present thanks to broken servers. */ + if (*next == '\r') + next++; + if (*next++ != '\n') + return NULL; + if (_v1_1_compat != NULL) + *_v1_1_compat = v1_1_compat; + *_status_code = status_code; + return next; +} + +/* Get the next response header. + * [out] _header: The header token, NUL-terminated, with leading and trailing + * whitespace stripped, and converted to lower case (to simplify + * case-insensitive comparisons), or NULL if there are no more + * response headers. + * [out] _cdr: The remaining contents of the header, excluding the initial + * colon (':') and the terminating CRLF ("\r\n"), + * NUL-terminated, and with leading and trailing whitespace + * stripped, or NULL if there are no more response headers. + * [inout] _s: On input, this points to the start of the current line of the + * response headers. + * On output, it points to the start of the first line following + * this header, or NULL if there are no more response headers. + * Return: 0 on success, or a negative value on failure. + */ +static int op_http_get_next_header(char **_header, char **_cdr, char **_s) +{ + char *header; + char *header_end; + char *cdr; + char *cdr_end; + char *next; + size_t d; + next = *_s; + /*The second case is for broken servers. */ + if ((next[0] == '\r' && next[1] == '\n') || next[0] == '\n') { + /*No more headers. */ + *_header = NULL; + *_cdr = NULL; + *_s = NULL; + return 0; + } + header = next + op_http_lwsspn(next); + d = strcspn(header, OP_HTTP_CTOKEN); + if (d <= 0) + return OP_FALSE; + header_end = header + d; + next = header_end + op_http_lwsspn(header_end); + if (*next++ != ':') + return OP_FALSE; + next += op_http_lwsspn(next); + cdr = next; + do { + cdr_end = next + strcspn(next, OP_HTTP_CTLS); + next = cdr_end + op_http_lwsspn(cdr_end); + } + while (next > cdr_end); + /*We are not mandating this be present thanks to broken servers. */ + if (*next == '\r') + next++; + if (*next++ != '\n') + return OP_FALSE; + *header_end = '\0'; + *cdr_end = '\0'; + /*Field names are case-insensitive. */ + op_string_tolower(header); + *_header = header; + *_cdr = cdr; + *_s = next; + return 0; +} + +static opus_int64 op_http_parse_nonnegative_int64(const char **_next, + const char *_cdr) +{ + const char *next; + opus_int64 ret; + int i; + next = _cdr + strspn(_cdr, OP_HTTP_DIGIT); + *_next = next; + if (next <= _cdr) + return OP_FALSE; + while (*_cdr == '0') + _cdr++; + if (next - _cdr > 19) + return OP_EIMPL; + ret = 0; + for (i = 0; i < next - _cdr; i++) { + int digit; + digit = _cdr[i] - '0'; + /*Check for overflow. */ + if (ret > (OP_INT64_MAX - 9) / 10 + (digit <= 7)) + return OP_EIMPL; + ret = ret * 10 + digit; + } + return ret; +} + +static opus_int64 op_http_parse_content_length(const char *_cdr) +{ + const char *next; + opus_int64 content_length; + content_length = op_http_parse_nonnegative_int64(&next, _cdr); + if (*next != '\0') + return OP_FALSE; + return content_length; +} + +static int op_http_parse_content_range(opus_int64 * _first, + opus_int64 * _last, + opus_int64 * _length, + const char *_cdr) +{ + opus_int64 first; + opus_int64 last; + opus_int64 length; + size_t d; + if (!op_strncasecmp(_cdr, "bytes", 5)) + return OP_FALSE; + _cdr += 5; + d = op_http_lwsspn(_cdr); + if (d <= 0) + return OP_FALSE; + _cdr += d; + if (*_cdr != '*') { + first = op_http_parse_nonnegative_int64(&_cdr, _cdr); + if (first < 0) + return (int) first; + _cdr += op_http_lwsspn(_cdr); + if (*_cdr++ != '-') + return OP_FALSE; + _cdr += op_http_lwsspn(_cdr); + last = op_http_parse_nonnegative_int64(&_cdr, _cdr); + if (last < 0) + return (int) last; + _cdr += op_http_lwsspn(_cdr); + } else { + /*This is for a 416 response (Requested range not satisfiable). */ + first = last = -1; + _cdr++; + } + if (*_cdr++ != '/') + return OP_FALSE; + if (*_cdr != '*') { + length = op_http_parse_nonnegative_int64(&_cdr, _cdr); + if (length < 0) + return (int) length; + } else { + /*The total length is unspecified. */ + _cdr++; + length = -1; + } + if (*_cdr != '\0') + return OP_FALSE; + if (last < first) + return OP_FALSE; + if (length >= 0 && last >= length) + return OP_FALSE; + *_first = first; + *_last = last; + *_length = length; + return 0; +} + +/* Parse the Connection response header and look for a "close" token. + * Return: 1 if a "close" token is found, 0 if it's not found, and a negative + * value on error. + */ +static int op_http_parse_connection(char *_cdr) +{ + size_t d; + int ret; + ret = 0; + for (;;) { + d = strcspn(_cdr, OP_HTTP_CTOKEN); + if (d <= 0) + return OP_FALSE; + if (op_strncasecmp(_cdr, "close", (int) d) == 0) + ret = 1; + /*We're supposed to strip and ignore any headers mentioned in the + Connection header if this response is from an HTTP/1.0 server (to + work around forwarding of hop-by-hop headers by old proxies), but the + only hop-by-hop header we look at is Connection itself. + Everything else is a well-defined end-to-end header, and going back and + undoing the things we did based on already-examined headers would be + hard (since we only scan them once, in a destructive manner). + Therefore we just ignore all the other tokens. */ + _cdr += d; + d = op_http_lwsspn(_cdr); + if (d <= 0) + break; + _cdr += d; + } + return (*_cdr != '\0') ? OP_FALSE : ret; +} + +/*Try to start a connection to the next address in the given list of a given + * type. + * _fd: The socket to connect with. + * [inout] _addr: A pointer to the list of addresses. + * This will be advanced to the first one that matches the given + * address family (possibly the current one). + * _ai_family: The address family to connect to. + * Return: 1 If the connection was successful. + * 0 If the connection is in progress. + * OP_FALSE If the connection failed and there were no more addresses + * left to try. + * *_addr will be set to NULL in this case. + */ +static int op_sock_connect_next(op_sock _fd, + struct addrinfo **_addr, int _ai_family) +{ + struct addrinfo *addr; + int err; + for (addr = *_addr;; addr = addr->ai_next) { + /*Move to the next address of the requested type. */ + for (; addr != NULL && addr->ai_family != _ai_family; + addr = addr->ai_next); + *_addr = addr; + /*No more: failure. */ + if (addr == NULL) + return OP_FALSE; + if (connect(_fd, addr->ai_addr, addr->ai_addrlen) >= 0) + return 1; + err = op_errno(); + /*Winsock will set WSAEWOULDBLOCK. */ + if (err == EINPROGRESS || err == EWOULDBLOCK) + return 0; + } +} + +/*The number of address families to try connecting to simultaneously.*/ +# define OP_NPROTOS (2) + +/////////////////////////////////////////////////////////////////////// +static int op_http_connect_impl(OpusHTTPStream * _stream, + OpusHTTPConn * _conn, + struct addrinfo *_addrs, + struct _timeb *_start_time) +{ + struct addrinfo *addr; + struct addrinfo *addrs[OP_NPROTOS]; + struct pollfd fds[OP_NPROTOS]; + int ai_family; + int nprotos; + int ret; + int pi; + int pj; + + for (pi = 0; pi < OP_NPROTOS; pi++) + addrs[pi] = NULL; + /* Try connecting via both IPv4 and IPv6 simultaneously, and keep the first + * one that succeeds. + * Start by finding the first address from each family. + * We order the first connection attempts in the same order the address + * families were returned in the DNS records in accordance with RFC 6555. + */ + for (addr = _addrs, nprotos = 0; addr != NULL && nprotos < OP_NPROTOS; + addr = addr->ai_next) { + if (addr->ai_family == AF_INET6 || addr->ai_family == AF_INET) { + OP_ASSERT(addr->ai_addrlen <= + OP_MAX(sizeof(struct sockaddr_in6), + sizeof(struct sockaddr_in))); + /*If we've seen this address family before, skip this address for now. */ + for (pi = 0; pi < nprotos; pi++) + if (addrs[pi]->ai_family == addr->ai_family) + break; + if (pi < nprotos) + continue; + addrs[nprotos++] = addr; + } + } + /*Pop the connection off the free list and put it on the LRU list. */ + OP_ASSERT(_stream->free_head == _conn); + _stream->free_head = _conn->next; + _conn->next = _stream->lru_head; + _stream->lru_head = _conn; + _ftime(_start_time); + _conn->read_time = *_start_time; + _conn->read_bytes = 0; + _conn->read_rate = 0; + /* Try to start a connection to each protocol. + * RFC 6555 says it is RECOMMENDED that connection attempts be paced + * 150...250 ms apart "to balance human factors against network load", but + * that "stateful algorithms" (that's us) "are expected to be more + * aggressive". + * We are definitely more aggressive: we don't pace at all. + */ + for (pi = 0; pi < nprotos; pi++) { + ai_family = addrs[pi]->ai_family; + fds[pi].fd = + socket(ai_family, SOCK_STREAM, addrs[pi]->ai_protocol); + fds[pi].events = POLLOUT; + if (fds[pi].fd != OP_INVALID_SOCKET) { + if (op_sock_set_nonblocking(fds[pi].fd, 1) >= 0) { + ret = + op_sock_connect_next(fds[pi].fd, addrs + pi, + ai_family); + if (ret > 0) { + /*It succeeded right away (technically possible), so stop. */ + nprotos = pi + 1; + break; + } + /*Otherwise go on to the next protocol, and skip the clean-up below. */ + else if (ret == 0) + continue; + /*Tried all the addresses for this protocol. */ + } + /*Clean up the socket. */ + close(fds[pi].fd); + } + /*Remove this protocol from the list. */ + memmove(addrs + pi, addrs + pi + 1, + sizeof(*addrs) * (nprotos - pi - 1)); + nprotos--; + pi--; + } + /*Wait for one of the connections to finish. */ + while (pi >= nprotos && nprotos > 0 + && poll(fds, nprotos, OP_POLL_TIMEOUT_MS) > 0) { + for (pi = 0; pi < nprotos; pi++) { + socklen_t errlen; + int err; + /*Still waiting... */ + if (!fds[pi].revents) + continue; + errlen = sizeof(err); + /*Some platforms will return the pending error in &err and return 0. + Others will put it in errno and return -1. */ + ret = + getsockopt(fds[pi].fd, SOL_SOCKET, SO_ERROR, &err, + &errlen); + if (ret < 0) + err = op_errno(); + /*Success! */ + if (err == 0 || err == EISCONN) + break; + /*Move on to the next address for this protocol. */ + ai_family = addrs[pi]->ai_family; + addrs[pi] = addrs[pi]->ai_next; + ret = op_sock_connect_next(fds[pi].fd, addrs + pi, ai_family); + /*It succeeded right away, so stop. */ + if (ret > 0) + break; + /*Otherwise go on to the next protocol, and skip the clean-up below. */ + else if (ret == 0) + continue; + /*Tried all the addresses for this protocol. + Remove it from the list. */ + close(fds[pi].fd); + memmove(fds + pi, fds + pi + 1, + sizeof(*fds) * (nprotos - pi - 1)); + memmove(addrs + pi, addrs + pi + 1, + sizeof(*addrs) * (nprotos - pi - 1)); + nprotos--; + pi--; + } + } + /*Close all the other sockets. */ + for (pj = 0; pj < nprotos; pj++) + if (pi != pj) + close(fds[pj].fd); + /*If none of them succeeded, we're done. */ + if (pi >= nprotos) + return OP_FALSE; + /*Save this address for future connection attempts. */ + if (addrs[pi] != &_stream->addr_info) { + memcpy(&_stream->addr_info, addrs[pi], sizeof(_stream->addr_info)); + _stream->addr_info.ai_addr = &_stream->addr.s; + _stream->addr_info.ai_next = NULL; + memcpy(&_stream->addr, addrs[pi]->ai_addr, addrs[pi]->ai_addrlen); + } + + /*Just a normal non-SSL connection. */ + + _conn->fd = fds[pi].fd; + _conn->nrequests_left = OP_PIPELINE_MAX_REQUESTS; + /*Disable write coalescing. + We always send whole requests at once and always parse the response headers + before sending another one. */ + op_sock_set_tcp_nodelay(fds[pi].fd, 1); + + return 0; +} + +/////////////////////////////////////////////////////////////////////// +static int op_http_connect(OpusHTTPStream * _stream, OpusHTTPConn * _conn, + struct addrinfo *_addrs, + struct _timeb *_start_time) +{ + struct _timeb resolve_time; + struct addrinfo *new_addrs; + int ret; + /*Re-resolve the host if we need to (RFC 6555 says we MUST do so + occasionally). */ + new_addrs = NULL; + _ftime(&resolve_time); + if (_addrs != &_stream->addr_info || op_time_diff_ms(&resolve_time, + &_stream-> + resolve_time) >= + OP_RESOLVE_CACHE_TIMEOUT_MS) { + new_addrs = + op_resolve(_stream->connect_host, _stream->connect_port); + if (new_addrs != NULL) { + _addrs = new_addrs; + _stream->resolve_time = resolve_time; + } else if (_addrs == NULL) + return OP_FALSE; + } + ret = op_http_connect_impl(_stream, _conn, _addrs, _start_time); + if (new_addrs != NULL) + WspiapiLegacyFreeAddrInfo(new_addrs); + return ret; +} + +# define OP_BASE64_LENGTH(_len) (((_len)+2)/3*4) + +static const char BASE64_TABLE[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', + 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '+', '/' +}; + +static char *op_base64_encode(char *_dst, const char *_src, int _len) +{ + unsigned s0; + unsigned s1; + unsigned s2; + int ngroups; + int i; + ngroups = _len / 3; + for (i = 0; i < ngroups; i++) { + s0 = _src[3 * i + 0]; + s1 = _src[3 * i + 1]; + s2 = _src[3 * i + 2]; + _dst[4 * i + 0] = BASE64_TABLE[s0 >> 2]; + _dst[4 * i + 1] = BASE64_TABLE[(s0 & 3) << 4 | s1 >> 4]; + _dst[4 * i + 2] = BASE64_TABLE[(s1 & 15) << 2 | s2 >> 6]; + _dst[4 * i + 3] = BASE64_TABLE[s2 & 63]; + } + _len -= 3 * i; + if (_len == 1) { + s0 = _src[3 * i + 0]; + _dst[4 * i + 0] = BASE64_TABLE[s0 >> 2]; + _dst[4 * i + 1] = BASE64_TABLE[(s0 & 3) << 4]; + _dst[4 * i + 2] = '='; + _dst[4 * i + 3] = '='; + i++; + } else if (_len == 2) { + s0 = _src[3 * i + 0]; + s1 = _src[3 * i + 1]; + _dst[4 * i + 0] = BASE64_TABLE[s0 >> 2]; + _dst[4 * i + 1] = BASE64_TABLE[(s0 & 3) << 4 | s1 >> 4]; + _dst[4 * i + 2] = BASE64_TABLE[(s1 & 15) << 2]; + _dst[4 * i + 3] = '='; + i++; + } + _dst[4 * i] = '\0'; + return _dst + 4 * i; +} + +/* Construct an HTTP authorization header using RFC 2617's Basic Authentication + * Scheme and append it to the given string buffer. + */ +static int op_sb_append_basic_auth_header(OpusStringBuf * _sb, + const char *_header, + const char *_user, + const char *_pass) +{ + size_t user_len; + size_t pass_len; + int user_pass_len; + int base64_len; + int nbuf_total; + int ret; + + ret = op_sb_append_string(_sb, _header); + ret |= op_sb_append(_sb, ": Basic ", 8); + user_len = strlen(_user); + pass_len = strlen(_pass); + if (user_len > (size_t) INT_MAX) + return OP_EFAULT; + if (pass_len > INT_MAX - user_len) + return OP_EFAULT; + if ((int)(user_len + pass_len) > (INT_MAX >> 2) * 3 - 3) + return OP_EFAULT; + user_pass_len = (int) (user_len + pass_len) + 1; + base64_len = OP_BASE64_LENGTH(user_pass_len); + /*Stick "user:pass" at the end of the buffer so we can Base64 encode it + in-place. */ + nbuf_total = _sb->nbuf; + if (base64_len > INT_MAX - nbuf_total) + return OP_EFAULT; + nbuf_total += base64_len; + ret |= op_sb_ensure_capacity(_sb, nbuf_total); + if (ret < 0) + return ret; + _sb->nbuf = nbuf_total - user_pass_len; + OP_ALWAYS_TRUE(!op_sb_append(_sb, _user, (int) user_len)); + OP_ALWAYS_TRUE(!op_sb_append(_sb, ":", 1)); + OP_ALWAYS_TRUE(!op_sb_append(_sb, _pass, (int) pass_len)); + op_base64_encode(_sb->buf + nbuf_total - base64_len, + _sb->buf + nbuf_total - user_pass_len, user_pass_len); + return op_sb_append(_sb, "\r\n", 2); +} + +static int op_http_allow_pipelining(const char *_server) +{ + /*Servers known to do bad things with pipelined requests. + This list is taken from Gecko's nsHttpConnection::SupportsPipelining() (in + netwerk/protocol/http/nsHttpConnection.cpp). */ + static const char *BAD_SERVERS[] = { + "EFAServer/", + "Microsoft-IIS/4.", + "Microsoft-IIS/5.", + "Netscape-Enterprise/3.", + "Netscape-Enterprise/4.", + "Netscape-Enterprise/5.", + "Netscape-Enterprise/6.", + "WebLogic 3.", + "WebLogic 4.", + "WebLogic 5.", + "WebLogic 6.", + "Winstone Servlet Engine v0." + }; +# define NBAD_SERVERS ((int)(sizeof(BAD_SERVERS)/sizeof(*BAD_SERVERS))) + if (*_server >= 'E' && *_server <= 'W') { + int si; + for (si = 0; si < NBAD_SERVERS; si++) { + if (strncmp(_server, BAD_SERVERS[si], strlen(BAD_SERVERS[si])) + == 0) { + return 0; + } + } + } + return 1; +# undef NBAD_SERVERS +} + +static int op_http_stream_open(OpusHTTPStream * _stream, const char *_url, + int _skip_certificate_check, + const char *_proxy_host, + unsigned _proxy_port, + const char *_proxy_user, + const char *_proxy_pass, + OpusServerInfo * _info) +{ + struct addrinfo *addrs; + int nredirs; + int ret; +#if defined(_WIN32) + op_init_winsock(); +#endif + ret = op_parse_url(&_stream->url, _url); + if (ret < 0) + return ret; + if (_proxy_host != NULL) { + if (_proxy_port > 65535U) + return OP_EINVAL; + _stream->connect_host = op_string_dup(_proxy_host); + _stream->connect_port = _proxy_port; + } else { + _stream->connect_host = _stream->url.host; + _stream->connect_port = _stream->url.port; + } + addrs = NULL; + for (nredirs = 0; nredirs < OP_REDIRECT_LIMIT; nredirs++) { + OpusParsedURL next_url; + struct _timeb start_time; + struct _timeb end_time; + char *next; + char *status_code; + int minor_version_pos; + int v1_1_compat; + + /*Actually make the connection. */ + ret = + op_http_connect(_stream, _stream->conns + 0, addrs, + &start_time); + if (ret < 0) + return ret; + /*Build the request to send. */ + _stream->request.nbuf = 0; + ret = op_sb_append(&_stream->request, "GET ", 4); + ret |= op_sb_append_string(&_stream->request, + _proxy_host != + NULL ? _url : _stream->url.path); + /* Send HTTP/1.0 by default for maximum compatibility (so we don't have to + * re-try if HTTP/1.1 fails, though it shouldn't, even for a 1.0 server). + * This means we aren't conditionally compliant with RFC 2145, because we + * violate the requirement that "An HTTP client SHOULD send a request + * version equal to the highest version for which the client is at least + * conditionally compliant...". + * According to RFC 2145, that means we can't claim any compliance with any + * IETF HTTP specification. + */ + ret |= op_sb_append(&_stream->request, " HTTP/1.0\r\n", 11); + /* Remember where this is so we can upgrade to HTTP/1.1 if the server + * supports it. + */ + minor_version_pos = _stream->request.nbuf - 3; + ret |= op_sb_append(&_stream->request, "Host: ", 6); + ret |= op_sb_append_string(&_stream->request, _stream->url.host); + if (!OP_URL_IS_DEFAULT_PORT(&_stream->url)) { + ret |= op_sb_append_port(&_stream->request, _stream->url.port); + } + ret |= op_sb_append(&_stream->request, "\r\n", 2); + /*User-Agents have been a bad idea, so send as little as possible. + RFC 2616 requires at least one token in the User-Agent, which must have + at least one character. */ + ret |= op_sb_append(&_stream->request, "User-Agent: .\r\n", 15); + if (_proxy_host != NULL && !OP_URL_IS_SSL(&_stream->url) + && _proxy_user != NULL && _proxy_pass != NULL) { + ret |= op_sb_append_basic_auth_header(&_stream->request, + "Proxy-Authorization", + _proxy_user, + _proxy_pass); + } + if (_stream->url.user != NULL && _stream->url.pass != NULL) { + ret |= op_sb_append_basic_auth_header(&_stream->request, + "Authorization", + _stream->url.user, + _stream->url.pass); + } + /* Always send a Referer [sic] header. + * It's common to refuse to serve a resource unless one is present. + * We just use the relative "/" URI to suggest we came from the same domain, + * as this is the most common check. + * This might violate RFC 2616's mandate that the field "MUST NOT be sent if + * the Request-URI was obtained from a source that does not have its own + * URI, such as input from the user keyboard," but we don't really have any + * way to know. + */ + /*TODO: Should we update this on redirects? */ + ret |= op_sb_append(&_stream->request, "Referer: /\r\n", 12); + /*Always send a Range request header to find out if we're seekable. + This requires an HTTP/1.1 server to succeed, but we'll still get what we + want with an HTTP/1.0 server that ignores this request header. */ + ret |= op_sb_append(&_stream->request, "Range: bytes=0-\r\n", 17); + /*Remember where this is so we can append offsets to it later. */ + _stream->request_tail = _stream->request.nbuf - 4; + ret |= op_sb_append(&_stream->request, "\r\n", 2); + if (ret < 0) + return ret; + ret = op_http_conn_write_fully(_stream->conns + 0, + _stream->request.buf, + _stream->request.nbuf); + if (ret < 0) + return ret; + ret = + op_http_conn_read_response(_stream->conns + 0, + &_stream->response); + if (ret < 0) + return ret; + _ftime(&end_time); + next = op_http_parse_status_line(&v1_1_compat, &status_code, + _stream->response.buf); + if (next == NULL) + return OP_FALSE; + if (status_code[0] == '2') { + opus_int64 content_length; + opus_int64 range_length; + int pipeline_supported; + int pipeline_disabled; + /*We only understand 20x codes. */ + if (status_code[1] != '0') + return OP_FALSE; + content_length = -1; + range_length = -1; + /*Pipelining must be explicitly enabled. */ + pipeline_supported = 0; + pipeline_disabled = 0; + for (;;) { + char *header; + char *cdr; + ret = op_http_get_next_header(&header, &cdr, &next); + if (ret < 0) + return ret; + if (header == NULL) + break; + if (strcmp(header, "content-length") == 0) { + /*Two Content-Length headers? */ + if (content_length >= 0) + return OP_FALSE; + content_length = op_http_parse_content_length(cdr); + if (content_length < 0) + return (int) content_length; + /*Make sure the Content-Length and Content-Range headers match. */ + if (range_length >= 0 && content_length != range_length) { + return OP_FALSE; + } + } else if (strcmp(header, "content-range") == 0) { + opus_int64 range_first; + opus_int64 range_last; + /*Two Content-Range headers? */ + if (range_length >= 0) + return OP_FALSE; + ret = + op_http_parse_content_range(&range_first, + &range_last, + &range_length, cdr); + if (ret < 0) + return ret; + /*"A response with satus code 206 (Partial Content) MUST NOT + include a Content-Range field with a byte-range-resp-spec of + '*'." */ + if (status_code[2] == '6' && (range_first < 0 || range_last < 0)) { + return OP_FALSE; + } + /*We asked for the entire resource. */ + if (range_length >= 0) { + /*Quit if we didn't get it. */ + if (range_last >= 0 && range_last != range_length - 1) { + return OP_FALSE; + } + } + /*If there was no length, use the end of the range. */ + else if (range_last >= 0) + range_length = range_last + 1; + /*Make sure the Content-Length and Content-Range headers match. */ + if (content_length >= 0 && content_length != range_length) { + return OP_FALSE; + } + } else if (strcmp(header, "connection") == 0) { + /*According to RFC 2616, if an HTTP/1.1 application does not support + * pipelining, it "MUST include the 'close' connection option in + * every message." + *Therefore, if we receive one in the initial response, disable + * pipelining entirely. + *The server still might support it (e.g., we might just have hit the + * request limit for a temporary child process), but if it doesn't + * and we assume it does, every time we cross a chunk boundary we'll + * error out and reconnect, adding lots of latency.*/ + ret = op_http_parse_connection(cdr); + if (ret < 0) + return ret; + pipeline_disabled |= ret; + } else if (strcmp(header, "server") == 0) { + /*If we got a Server response header, and it wasn't from a known-bad + * server, enable pipelining, as long as it's at least HTTP/1.1. + *According to RFC 2145, the server is supposed to respond with the + * highest minor version number it supports unless it is known or + * suspected that we incorrectly implement the HTTP specification. + *So it should send back at least HTTP/1.1, despite our HTTP/1.0 + * request.*/ + pipeline_supported = v1_1_compat; + if (v1_1_compat) + pipeline_disabled |= + !op_http_allow_pipelining(cdr); + if (_info != NULL && _info->server == NULL) + _info->server = op_string_dup(cdr); + } + /* Collect station information headers if the caller requested it. + * If there's more than one copy of a header, the first one wins. + */ + else if (_info != NULL) { + if (strcmp(header, "content-type") == 0) { + if (_info->content_type == NULL) { + _info->content_type = op_string_dup(cdr); + } + } else if (header[0] == 'i' && header[1] == 'c' + && (header[2] == 'e' || header[2] == 'y') + && header[3] == '-') { + if (strcmp(header + 4, "name") == 0) { + if (_info->name == NULL) + _info->name = op_string_dup(cdr); + } else if (strcmp(header + 4, "description") == 0) { + if (_info->description == NULL) + _info->description = op_string_dup(cdr); + } else if (strcmp(header + 4, "genre") == 0) { + if (_info->genre == NULL) + _info->genre = op_string_dup(cdr); + } else if (strcmp(header + 4, "url") == 0) { + if (_info->url == NULL) + _info->url = op_string_dup(cdr); + } else if (strcmp(header, "icy-br") == 0 + || strcmp(header, "ice-bitrate") == 0) { + if (_info->bitrate_kbps < 0) { + opus_int64 bitrate_kbps; + /*Just re-using this function to parse a random unsigned + integer field. */ + bitrate_kbps = + op_http_parse_content_length(cdr); + if (bitrate_kbps >= 0 + && bitrate_kbps <= OP_INT32_MAX) { + _info->bitrate_kbps = + (opus_int32) bitrate_kbps; + } + } + } else if (strcmp(header, "icy-pub") == 0 + || strcmp(header, "ice-public") == 0) { + if (_info->is_public < 0 + && (cdr[0] == '0' || cdr[0] == '1') + && cdr[1] == '\0') { + _info->is_public = cdr[0] - '0'; + } + } + } + } + } + switch (status_code[2]) { + /*200 OK */ + case '0': + break; + /*203 Non-Authoritative Information */ + case '3': + break; + /*204 No Content */ + case '4':{ + if (content_length > 0) { + return OP_FALSE; + } + } + break; + /*206 Partial Content */ + case '6':{ + /*No Content-Range header. */ + if (range_length < 0) + return OP_FALSE; + content_length = range_length; + /*The server supports range requests for this resource. + We can seek. */ + _stream->seekable = 1; + } + break; + /*201 Created: the response "SHOULD include an entity containing a list + * of resource characteristics and location(s)," but not an Opus file. + *202 Accepted: the response "SHOULD include an indication of request's + * current status and either a pointer to a status monitor or some + * estimate of when the user can expect the request to be fulfilled," + * but not an Opus file. + *205 Reset Content: this "MUST NOT include an entity," meaning no Opus + * file. + *207...209 are not yet defined, so we don't know how to handle them. + */ + default: + return OP_FALSE; + } + _stream->content_length = content_length; + _stream->pipeline = pipeline_supported && !pipeline_disabled; + /*Pipelining requires HTTP/1.1 persistent connections. */ + if (_stream->pipeline) + _stream->request.buf[minor_version_pos] = '1'; + _stream->conns[0].pos = 0; + _stream->conns[0].end_pos = + _stream->seekable ? content_length : -1; + _stream->conns[0].chunk_size = -1; + _stream->cur_conni = 0; + _stream->connect_rate = + op_time_diff_ms(&end_time, &start_time); + _stream->connect_rate = OP_MAX(_stream->connect_rate, 1); + if (_info != NULL) + _info->is_ssl = OP_URL_IS_SSL(&_stream->url); + /*The URL has been successfully opened. */ + return 0; + } + /* Shouldn't get 1xx; 4xx and 5xx are both failures (and we don't retry). + * Everything else is undefined.*/ + else if (status_code[0] != '3') + return OP_FALSE; + /*We have some form of redirect request. */ + /*We only understand 30x codes. */ + if (status_code[1] != '0') + return OP_FALSE; + switch (status_code[2]) { + /* 300 Multiple Choices: "If the server has a preferred choice of + * representation, it SHOULD include the specific URI for that + * representation in the Location field," otherwise we'll fail.*/ + case '0': + case '1': /* 301 Moved Permanently */ + case '2': /* 302 Found */ + case '7': /* 307 Temporary Redirect */ + case '8': /* 308 Permanent Redirect */ + break; /*(defined by draft-reschke-http-status-308-07). */ + /* 305 Use Proxy: "The Location field gives the URI of the proxy." + * TODO: This shouldn't actually be that hard to do.*/ + case '5': + return OP_EIMPL; + /* 303 See Other: "The new URI is not a substitute reference for the + * originally requested resource." + * 304 Not Modified: "The 304 response MUST NOT contain a message-body." + * 306 (Unused) + * 309 is not yet defined, so we don't know how to handle it. + */ + default: + return OP_FALSE; + } + _url = NULL; + for (;;) { + char *header; + char *cdr; + ret = op_http_get_next_header(&header, &cdr, &next); + if (ret < 0) + return ret; + if (header == NULL) + break; + if (!strcmp(header, "location") && _url == NULL) + _url = cdr; + } + if (_url == NULL) + return OP_FALSE; + ret = op_parse_url(&next_url, _url); + if (ret < 0) + return ret; + if (_proxy_host == NULL) { + if (strcmp(_stream->url.host, next_url.host) == 0 + && _stream->url.port == next_url.port) { + /*Try to skip re-resolve when connecting to the same host. */ + addrs = &_stream->addr_info; + } + } + if (_proxy_host == NULL) { + OP_ASSERT(_stream->connect_host == _stream->url.host); + _stream->connect_host = next_url.host; + _stream->connect_port = next_url.port; + } + /*Always try to skip re-resolve for proxy connections. */ + else + addrs = &_stream->addr_info; + op_parsed_url_clear(&_stream->url); + _stream->url = next_url; + /*TODO: On servers/proxies that support pipelining, we might be able to + * re-use this connection. */ + op_http_conn_close(_stream, _stream->conns + 0, &_stream->lru_head, + 1); + } + /*Redirection limit reached. */ + return OP_FALSE; +} /*op_http_stream_open func end */ + +static int op_http_conn_send_request(OpusHTTPStream * _stream, + OpusHTTPConn * _conn, opus_int64 _pos, + opus_int32 _chunk_size, + int _try_not_to_block) +{ + opus_int64 next_end; + int ret; + /*We shouldn't have another request outstanding. */ + OP_ASSERT(_conn->next_pos < 0); + /*Build the request to send. */ + OP_ASSERT(_stream->request.nbuf >= _stream->request_tail); + _stream->request.nbuf = _stream->request_tail; + ret = op_sb_append_nonnegative_int64(&_stream->request, _pos); + ret |= op_sb_append(&_stream->request, "-", 1); + if (_chunk_size > 0 + && OP_ADV_OFFSET(_pos, + 2 * _chunk_size) < _stream->content_length) { + /*We shouldn't be pipelining requests with non-HTTP/1.1 servers. */ + OP_ASSERT(_stream->pipeline); + next_end = _pos + _chunk_size; + ret |= + op_sb_append_nonnegative_int64(&_stream->request, + next_end - 1); + /*Use a larger chunk size for our next request. */ + _chunk_size <<= 1; + /*But after a while, just request the rest of the resource. */ + if (_chunk_size > OP_PIPELINE_CHUNK_SIZE_MAX) + _chunk_size = -1; + } else { + /*Either this was a non-pipelined request or we were close enough to the + end to just ask for the rest. */ + next_end = -1; + _chunk_size = -1; + } + ret |= op_sb_append(&_stream->request, "\r\n\r\n", 4); + if (ret < 0) + return ret; + /* If we don't want to block, check to see if there's enough space in the send queue. + * There's still a chance we might block, even if there is enough space, but + * it's a much slimmer one. + * Blocking at all is pretty unlikely, as we won't have any requests queued + * when _try_not_to_block is set, so if FIONSPACE isn't available (e.g., on + * Linux), just skip the test. + */ + if (_try_not_to_block) { +# if defined(FIONSPACE) + int available; + ret = ioctl(_conn->fd, FIONSPACE, &available); + if (ret < 0 || available < _stream->request.nbuf) + return 1; +# endif + } + ret = op_http_conn_write_fully(_conn, + _stream->request.buf, + _stream->request.nbuf); + if (ret < 0) + return ret; + _conn->next_pos = _pos; + _conn->next_end = next_end; + /*Save the chunk size to use for the next request. */ + _conn->chunk_size = _chunk_size; + _conn->nrequests_left--; + return ret; +} + +/*Handles the response to all requests after the first one. + Return: 1 if the connection was closed or timed out, 0 on success, or a + negative value on any other error.*/ +static int op_http_conn_handle_response(OpusHTTPStream * _stream, + OpusHTTPConn * _conn) +{ + char *next; + char *status_code; + opus_int64 range_length; + opus_int64 next_pos; + opus_int64 next_end; + int ret; + ret = op_http_conn_read_response(_conn, &_stream->response); + /*If the server just closed the connection on us, we may have just hit a + * connection re-use limit, so we might want to retry. + */ + if (ret < 0) + return ret == OP_EREAD ? 1 : ret; + next = + op_http_parse_status_line(NULL, &status_code, + _stream->response.buf); + if (next == NULL) + return OP_FALSE; + /*We _need_ a 206 Partial Content response. + Nothing else will do. */ + if (strncmp(status_code, "206", 3) != 0) { + /*But on a 408 Request Timeout, we might want to re-try. */ + return strncmp(status_code, "408", 3) == 0 ? 1 : OP_FALSE; + } + next_pos = _conn->next_pos; + next_end = _conn->next_end; + range_length = -1; + for (;;) { + char *header; + char *cdr; + ret = op_http_get_next_header(&header, &cdr, &next); + if (ret < 0) + return ret; + if (header == NULL) + break; + if (strcmp(header, "content-range") == 0) { + opus_int64 range_first; + opus_int64 range_last; + /*Two Content-Range headers? */ + if (range_length >= 0) + return OP_FALSE; + ret = op_http_parse_content_range(&range_first, &range_last, + &range_length, cdr); + if (ret < 0) + return ret; + /* "A response with satus code 206 (Partial Content) MUST NOT + * include a Content-Range field with a byte-range-resp-spec of '*'." + */ + if (range_first < 0 || range_last < 0) + return OP_FALSE; + /*We also don't want range_last to overflow. */ + if (range_last >= OP_INT64_MAX) + return OP_FALSE; + range_last++; + /*Quit if we didn't get the offset we asked for. */ + if (range_first != next_pos) + return OP_FALSE; + if (next_end < 0) { + /*We asked for the rest of the resource. */ + if (range_length >= 0) { + /*Quit if we didn't get it. */ + if (range_last != range_length) + return OP_FALSE; + } + /*If there was no length, use the end of the range. */ + else + range_length = range_last; + next_end = range_last; + } else { + if (range_last != next_end) + return OP_FALSE; + /*If there was no length, use the larger of the content length or the + end of this chunk. */ + if (range_length < 0) { + range_length = + OP_MAX(range_last, _stream->content_length); + } + } + } else if (strcmp(header, "content-length") == 0) { + opus_int64 content_length; + /*Validate the Content-Length header, if present, against the request we + made. */ + content_length = op_http_parse_content_length(cdr); + if (content_length < 0) + return (int) content_length; + if (next_end < 0) { + /* If we haven't seen the Content-Range header yet and we asked for the + * rest of the resource, set next_end, so we can make sure they match + * when we do find the Content-Range header. + */ + if (next_pos > OP_INT64_MAX - content_length) + return OP_FALSE; + next_end = next_pos + content_length; + } + /*Otherwise, make sure they match now. */ + else if (next_end - next_pos != content_length) + return OP_FALSE; + } else if (strcmp(header, "connection") == 0) { + ret = op_http_parse_connection(cdr); + if (ret < 0) + return ret; + /*If the server told us it was going to close the connection, don't make + any more requests. */ + if (ret > 0) + _conn->nrequests_left = 0; + } + } + /*No Content-Range header. */ + if (range_length < 0) + return OP_FALSE; + /*Update the content_length if necessary. */ + _stream->content_length = range_length; + _conn->pos = next_pos; + _conn->end_pos = next_end; + _conn->next_pos = -1; + return 0; +} + +/* Open a new connection that will start reading at byte offset _pos. + * _pos: The byte offset to start reading from. + * _chunk_size: The number of bytes to ask for in the initial request, or -1 to + * request the rest of the resource. + * This may be more bytes than remain, in which case it will be + * converted into a request for the rest. + */ +static int op_http_conn_open_pos(OpusHTTPStream * _stream, + OpusHTTPConn * _conn, opus_int64 _pos, + opus_int32 _chunk_size) +{ + struct _timeb start_time; + struct _timeb end_time; + opus_int32 connect_rate; + opus_int32 connect_time; + int ret; + ret = + op_http_connect(_stream, _conn, &_stream->addr_info, &start_time); + if (ret < 0) + return ret; + ret = op_http_conn_send_request(_stream, _conn, _pos, _chunk_size, 0); + if (ret < 0) + return ret; + ret = op_http_conn_handle_response(_stream, _conn); + if (ret != 0) + return OP_FALSE; + _ftime(&end_time); + _stream->cur_conni = (int) (_conn - _stream->conns); + OP_ASSERT(_stream->cur_conni >= 0 + && _stream->cur_conni < OP_NCONNS_MAX); + /*The connection has been successfully opened. + Update the connection time estimate. */ + connect_time = op_time_diff_ms(&end_time, &start_time); + connect_rate = _stream->connect_rate; + connect_rate += (OP_MAX(connect_time, 1) - connect_rate + 8) >> 4; + _stream->connect_rate = connect_rate; + return 0; +} + +/*Read data from the current response body. + * If we're pipelining and we get close to the end of this response, queue + * another request. + * If we've reached the end of this response body, parse the next response and + * keep going. + * [out] _buf: Returns the data read. + * _buf_size: The size of the buffer. + * Return: A positive number of bytes read on success. + * 0: The connection was closed. + * OP_EREAD: There was a fatal read error. + */ +static int op_http_conn_read_body(OpusHTTPStream * _stream, + OpusHTTPConn * _conn, + unsigned char *_buf, int _buf_size) +{ + opus_int64 pos; + opus_int64 end_pos; + opus_int64 next_pos; + opus_int64 content_length; + int nread; + int pipeline; + int ret; + /* Currently this function can only be called on the LRU head. + * Otherwise, we'd need a _pnext pointer if we needed to close the connection, + * and re-opening it would re-organize the lists. + */ + OP_ASSERT(_stream->lru_head == _conn); + /*We should have filtered out empty reads by this point. */ + OP_ASSERT(_buf_size > 0); + pos = _conn->pos; + end_pos = _conn->end_pos; + next_pos = _conn->next_pos; + pipeline = _stream->pipeline; + content_length = _stream->content_length; + if (end_pos >= 0) { + /*Have we reached the end of the current response body? */ + if (pos >= end_pos) { + OP_ASSERT(content_length >= 0); + /* If this was the end of the stream, we're done. + * Also return early if a non-blocking read was requested (regardless of + * whether we might be able to parse the next response without + * blocking). + */ + if (content_length <= end_pos) + return 0; + /*Otherwise, start on the next response. */ + if (next_pos < 0) { + /*We haven't issued another request yet. */ + if (!pipeline || _conn->nrequests_left <= 0) { + /*There are two ways to get here: either the server told us it was + going to close the connection after the last request, or we + thought we were reading the whole resource, but it grew while we + were reading it. + The only way the latter could have happened is if content_length + changed while seeking. + Open a new request to read the rest. */ + OP_ASSERT(_stream->seekable); + /*Try to open a new connection to read another chunk. */ + op_http_conn_close(_stream, _conn, &_stream->lru_head, + 1); + /*If we're not pipelining, we should be requesting the rest. */ + OP_ASSERT(pipeline || _conn->chunk_size == -1); + ret = + op_http_conn_open_pos(_stream, _conn, end_pos, + _conn->chunk_size); + if (ret < 0) + return OP_EREAD; + } else { + /*Issue the request now (better late than never). */ + ret = + op_http_conn_send_request(_stream, _conn, pos, + _conn->chunk_size, 0); + if (ret < 0) + return OP_EREAD; + next_pos = _conn->next_pos; + OP_ASSERT(next_pos >= 0); + } + } + if (next_pos >= 0) { + /*We shouldn't be trying to read past the current request body if we're + seeking somewhere else. */ + OP_ASSERT(next_pos == end_pos); + ret = op_http_conn_handle_response(_stream, _conn); + if (ret < 0) + return OP_EREAD; + if (ret > 0 && pipeline) { + opus_int64 next_end; + next_end = _conn->next_end; + /*Our request timed out or the server closed the connection. + Try re-connecting. */ + op_http_conn_close(_stream, _conn, &_stream->lru_head, + 1); + /*Unless there's a bug, we should be able to convert + (next_pos,next_end) into valid (_pos,_chunk_size) parameters. */ + OP_ASSERT(next_end < 0 + || next_end - next_pos >= 0 + && next_end - next_pos <= OP_INT32_MAX); + ret = + op_http_conn_open_pos(_stream, _conn, next_pos, + next_end < + 0 ? -1 + : (opus_int32) (next_end - + next_pos)); + if (ret < 0) + return OP_EREAD; + } else if (ret != 0){ + return OP_EREAD; + } + } + pos = _conn->pos; + end_pos = _conn->end_pos; + content_length = _stream->content_length; + } + OP_ASSERT(end_pos > pos); + _buf_size = (int) OP_MIN(_buf_size, end_pos - pos); + } + nread = op_http_conn_read(_conn, (char *) _buf, _buf_size, 1); + if (nread < 0) + return nread; + pos += nread; + _conn->pos = pos; + OP_ASSERT(end_pos < 0 || content_length >= 0); + /* TODO: If nrequests_left<=0, we can't make a new request, and there will be + * a big pause after we hit the end of the chunk while we open a new connection. + * It would be nice to be able to start that process now, but we have no way + * to do it in the background without blocking (even if we could start it, we + * have no guarantee the application will return control to us in a + * sufficiently timely manner to allow us to complete it, and this is + * uncommon enough that it's not worth using threads just for this). + */ + if (end_pos >= 0 && end_pos < content_length && next_pos < 0 + && pipeline && _conn->nrequests_left > 0 ) { + opus_int64 request_thresh; + opus_int32 chunk_size; + /*Are we getting close to the end of the current response body? + If so, we should request more data. */ + request_thresh = _stream->connect_rate * _conn->read_rate >> 12; + /*But don't commit ourselves too quickly. */ + chunk_size = _conn->chunk_size; + if (chunk_size >= 0) + request_thresh = OP_MIN(chunk_size >> 2, request_thresh); + if (end_pos - pos < request_thresh) { + ret = + op_http_conn_send_request(_stream, _conn, end_pos, + _conn->chunk_size, 1); + if (ret < 0) + return OP_EREAD; + } + } + return nread; +} + +static int op_http_stream_read(void *_stream, + unsigned char *_ptr, int _buf_size) +{ + OpusHTTPStream *stream; + int nread; + opus_int64 size; + opus_int64 pos; + int ci; + stream = (OpusHTTPStream *) _stream; + /*Check for an empty read. */ + if (_buf_size <= 0) + return 0; + ci = stream->cur_conni; + /*No current connection => EOF. */ + if (ci < 0) + return 0; + pos = stream->conns[ci].pos; + size = stream->content_length; + /*Check for EOF. */ + if (size >= 0) { + if (pos >= size) + return 0; + /*Check for a short read. */ + if (_buf_size > size - pos) + _buf_size = (int) (size - pos); + } + nread = + op_http_conn_read_body(stream, stream->conns + ci, _ptr, + _buf_size); + if (nread <= 0) { + /*We hit an error or EOF. + Either way, we're done with this connection. */ + op_http_conn_close(stream, stream->conns + ci, &stream->lru_head, + 1); + stream->cur_conni = -1; + stream->pos = pos; + } + return nread; +} + +/* Discard data until we reach the _target position. + * This destroys the contents of _stream->response.buf, as we need somewhere to + * read this data, and that is a convenient place. + * _just_read_ahead: Whether or not this is a plain fast-forward. + * If 0, we need to issue a new request for a chunk at _target + * and discard all the data from our current request(s). + * Otherwise, we should be able to reach _target without + * issuing any new requests. + * _target: The stream position to which to read ahead. + */ +static int op_http_conn_read_ahead(OpusHTTPStream * _stream, + OpusHTTPConn * _conn, + int _just_read_ahead, + opus_int64 _target) +{ + opus_int64 pos; + opus_int64 end_pos; + opus_int64 next_pos; + opus_int64 next_end; + ptrdiff_t nread; + int ret; + pos = _conn->pos; + end_pos = _conn->end_pos; + next_pos = _conn->next_pos; + next_end = _conn->next_end; + if (!_just_read_ahead) { + /*We need to issue a new pipelined request. + This is the only case where we allow more than one outstanding request + at a time, so we need to reset next_pos (we'll restore it below if we + did have an outstanding request). */ + OP_ASSERT(_stream->pipeline); + _conn->next_pos = -1; + ret = op_http_conn_send_request(_stream, _conn, _target, + OP_PIPELINE_CHUNK_SIZE, 0); + if (ret < 0) + return ret; + } + /*We can reach the target position by reading forward in the current chunk. */ + if (_just_read_ahead && (end_pos < 0 || _target < end_pos)) + end_pos = _target; + else if (next_pos >= 0) { + opus_int64 next_next_pos; + opus_int64 next_next_end; + /*We already have a request outstanding. + Finish off the current chunk. */ + while (pos < end_pos) { + nread = op_http_conn_read(_conn, _stream->response.buf, + (int) OP_MIN(end_pos - pos, + _stream->response.cbuf), + 1); + /*We failed to read ahead. */ + if (nread <= 0) + return OP_FALSE; + pos += nread; + } + OP_ASSERT(pos == end_pos); + if (_just_read_ahead) { + next_next_pos = next_next_end = -1; + end_pos = _target; + } else { + OP_ASSERT(_conn->next_pos == _target); + next_next_pos = _target; + next_next_end = _conn->next_end; + _conn->next_pos = next_pos; + _conn->next_end = next_end; + end_pos = next_end; + } + ret = op_http_conn_handle_response(_stream, _conn); + if (ret != 0) + return OP_FALSE; + _conn->next_pos = next_next_pos; + _conn->next_end = next_next_end; + } + while (pos < end_pos) { + nread = op_http_conn_read(_conn, _stream->response.buf, + (int) OP_MIN(end_pos - pos, + _stream->response.cbuf), 1); + /*We failed to read ahead. */ + if (nread <= 0) + return OP_FALSE; + pos += nread; + } + OP_ASSERT(pos == end_pos); + if (!_just_read_ahead) { + ret = op_http_conn_handle_response(_stream, _conn); + if (ret != 0) + return OP_FALSE; + } else + _conn->pos = end_pos; + OP_ASSERT(_conn->pos == _target); + return 0; +} + +static int op_http_stream_seek(void *_stream, opus_int64 _offset, + int _whence) +{ + struct _timeb seek_time; + OpusHTTPStream *stream; + OpusHTTPConn *conn; + OpusHTTPConn **pnext; + OpusHTTPConn *close_conn; + OpusHTTPConn **close_pnext; + opus_int64 content_length; + opus_int64 pos; + int pipeline; + int ci; + int ret; + stream = (OpusHTTPStream *) _stream; + if (!stream->seekable) + return -1; + content_length = stream->content_length; + /*If we're seekable, we should have gotten a Content-Length. */ + OP_ASSERT(content_length >= 0); + ci = stream->cur_conni; + pos = ci < 0 ? content_length : stream->conns[ci].pos; + switch (_whence) { + case SEEK_SET:{ + /*Check for overflow: */ + if (_offset < 0) + return -1; + pos = _offset; + } + break; + case SEEK_CUR:{ + /*Check for overflow: */ + if (_offset < -pos || _offset > OP_INT64_MAX - pos) + return -1; + pos += _offset; + } + break; + case SEEK_END:{ + /*Check for overflow: */ + if (_offset > content_length + || _offset < content_length - OP_INT64_MAX) + return -1; + pos = content_length - _offset; + } + break; + default: + return -1; + } + /*Mark when we deactivated the active connection. */ + if (ci >= 0) { + op_http_conn_read_rate_update(stream->conns + ci); + seek_time = stream->conns[ci].read_time; + } else + _ftime(&seek_time); + /*If we seeked past the end of the stream, just disable the active + connection. */ + if (pos >= content_length) { + stream->cur_conni = -1; + stream->pos = pos; + return 0; + } + /*First try to find a connection we can use without waiting. */ + pnext = &stream->lru_head; + conn = stream->lru_head; + while (conn != NULL) { + opus_int64 conn_pos; + opus_int64 end_pos; + int available; + /*If this connection has been dormant too long or has made too many + requests, close it. + This is to prevent us from hitting server limits/firewall timeouts. */ + if (op_time_diff_ms(&seek_time, &conn->read_time) > + OP_CONNECTION_IDLE_TIMEOUT_MS + || conn->nrequests_left < OP_PIPELINE_MIN_REQUESTS) { + op_http_conn_close(stream, conn, pnext, 1); + conn = *pnext; + continue; + } + available = op_http_conn_estimate_available(conn); + conn_pos = conn->pos; + end_pos = conn->end_pos; + if (conn->next_pos >= 0) { + OP_ASSERT(end_pos >= 0); + OP_ASSERT(conn->next_pos == end_pos); + end_pos = conn->next_end; + } + OP_ASSERT(end_pos < 0 || conn_pos <= end_pos); + /* Can we quickly read ahead without issuing a new request or waiting for + * any more data? + * If we have an oustanding request, we'll over-estimate the amount of data + * it has available (because we'll count the response headers, too), but + * that probably doesn't matter. + */ + if (conn_pos <= pos && pos - conn_pos <= available + && (end_pos < 0 || pos < end_pos)) { + /*Found a suitable connection to re-use. */ + ret = op_http_conn_read_ahead(stream, conn, 1, pos); + if (ret < 0) { + /*The connection might have become stale, so close it and keep going. */ + op_http_conn_close(stream, conn, pnext, 1); + conn = *pnext; + continue; + } + /*Sucessfully resurrected this connection. */ + *pnext = conn->next; + conn->next = stream->lru_head; + stream->lru_head = conn; + stream->cur_conni = (int) (conn - stream->conns); + OP_ASSERT(stream->cur_conni >= 0 + && stream->cur_conni < OP_NCONNS_MAX); + return 0; + } + pnext = &conn->next; + conn = conn->next; + } + /*Chances are that didn't work, so now try to find one we can use by reading + ahead a reasonable amount and/or by issuing a new request. */ + close_pnext = NULL; + close_conn = NULL; + pnext = &stream->lru_head; + conn = stream->lru_head; + pipeline = stream->pipeline; + while (conn != NULL) { + opus_int64 conn_pos; + opus_int64 end_pos; + opus_int64 read_ahead_thresh; + int available; + int just_read_ahead; + /* Dividing by 2048 instead of 1000 scales this by nearly 1/2, biasing away + * from connection re-use (and roughly compensating for the lag required to + * reopen the TCP window of a connection that's been idle). + * There's no overflow checking here, because it's vanishingly unlikely, and + * all it would do is cause us to make poor decisions. + */ + read_ahead_thresh = OP_MAX(OP_READAHEAD_THRESH_MIN, + stream->connect_rate * + conn->read_rate >> 11); + available = op_http_conn_estimate_available(conn); + conn_pos = conn->pos; + end_pos = conn->end_pos; + if (conn->next_pos >= 0) { + OP_ASSERT(end_pos >= 0); + OP_ASSERT(conn->next_pos == end_pos); + end_pos = conn->next_end; + } + OP_ASSERT(end_pos < 0 || conn_pos <= end_pos); + /*Can we quickly read ahead without issuing a new request? */ + just_read_ahead = conn_pos <= pos + && pos-conn_pos-available <= read_ahead_thresh + && (end_pos < 0 || pos < end_pos); + if (just_read_ahead || (pipeline && end_pos >= 0 + && end_pos-conn_pos-available <= read_ahead_thresh)) { + /*Found a suitable connection to re-use. */ + ret = + op_http_conn_read_ahead(stream, conn, just_read_ahead, + pos); + if (ret < 0) { + /*The connection might have become stale, so close it and keep going. */ + op_http_conn_close(stream, conn, pnext, 1); + conn = *pnext; + continue; + } + /*Sucessfully resurrected this connection. */ + *pnext = conn->next; + conn->next = stream->lru_head; + stream->lru_head = conn; + stream->cur_conni = (int) (conn - stream->conns); + OP_ASSERT(stream->cur_conni >= 0 + && stream->cur_conni < OP_NCONNS_MAX); + return 0; + } + close_pnext = pnext; + close_conn = conn; + pnext = &conn->next; + conn = conn->next; + } + /*No suitable connections. + Open a new one. */ + if (stream->free_head == NULL) { + /* All connections in use. + * Expire one of them (we should have already picked which one when scanning the list). + */ + OP_ASSERT(close_conn != NULL); + OP_ASSERT(close_pnext != NULL); + op_http_conn_close(stream, close_conn, close_pnext, 1); + } + OP_ASSERT(stream->free_head != NULL); + conn = stream->free_head; + /* If we can pipeline, only request a chunk of data. + * If we're seeking now, there's a good chance we will want to seek again + * soon, and this avoids committing this connection to reading the rest of + * the stream. + * Particularly with SSL or proxies, issuing a new request on the same + * connection can be substantially faster than opening a new one. + * This also limits the amount of data the server will blast at us on this + * connection if we later seek elsewhere and start reading from a different + * connection. + */ + ret = op_http_conn_open_pos(stream, conn, pos, + pipeline ? OP_PIPELINE_CHUNK_SIZE : -1); + if (ret < 0) { + op_http_conn_close(stream, conn, &stream->lru_head, 1); + return -1; + } + return 0; +} + +static opus_int64 op_http_stream_tell(void *_stream) +{ + OpusHTTPStream *stream; + int ci; + stream = (OpusHTTPStream *) _stream; + ci = stream->cur_conni; + return ci < 0 ? stream->pos : stream->conns[ci].pos; +} + +static int op_http_stream_close(void *_stream) +{ + OpusHTTPStream *stream; + stream = (OpusHTTPStream *) _stream; + if (stream != NULL) { + op_http_stream_clear(stream); + _ogg_free(stream); + } + return 0; +} + +static const OpusFileCallbacks OP_HTTP_CALLBACKS = { + op_http_stream_read, + op_http_stream_seek, + op_http_stream_tell, + op_http_stream_close +}; +#endif /* OP_ENABLE_HTTP */ + +void opus_server_info_init(OpusServerInfo * _info) +{ + _info->name = NULL; + _info->description = NULL; + _info->genre = NULL; + _info->url = NULL; + _info->server = NULL; + _info->content_type = NULL; + _info->bitrate_kbps = -1; + _info->is_public = -1; + _info->is_ssl = 0; +} + +void opus_server_info_clear(OpusServerInfo * _info) +{ + _ogg_free(_info->content_type); + _ogg_free(_info->server); + _ogg_free(_info->url); + _ogg_free(_info->genre); + _ogg_free(_info->description); + _ogg_free(_info->name); +} + +/* The actual URL stream creation function. + * This one isn't extensible like the application-level interface, but because + * it isn't public, we're free to change it in the future. + */ +static void *op_url_stream_create_impl(OpusFileCallbacks * _cb, + const char *_url, + int _skip_certificate_check, + const char *_proxy_host, + unsigned _proxy_port, + const char *_proxy_user, + const char *_proxy_pass, + OpusServerInfo * _info) +{ + const char *path; + /*Check to see if this is a valid file: URL. */ + path = op_parse_file_url(_url); + if (path != NULL) { + char *unescaped_path; + void *ret; + unescaped_path = op_string_dup(path); + if (unescaped_path == NULL) + return NULL; + ret = + op_fopen(_cb, op_unescape_url_component(unescaped_path), "rb"); + _ogg_free(unescaped_path); + return ret; + } +#if defined(OP_ENABLE_HTTP) + /*If not, try http/https. */ + else { + OpusHTTPStream *stream; + int ret; + stream = _ogg_malloc(sizeof(*stream)); + if (stream == NULL) + return NULL; + op_http_stream_init(stream); + ret = op_http_stream_open(stream, _url, _skip_certificate_check, + _proxy_host, _proxy_port, _proxy_user, + _proxy_pass, _info); + if (ret < 0) { + op_http_stream_clear(stream); + _ogg_free(stream); + return NULL; + } + *_cb = OP_HTTP_CALLBACKS; + return stream; + } +#else + (void) _skip_certificate_check; + (void) _proxy_host; + (void) _proxy_port; + (void) _proxy_user; + (void) _proxy_pass; + (void) _info; + return NULL; +#endif +} + +/*The actual implementation of op_url_stream_vcreate(). + *We have to do a careful dance here to avoid potential memory leaks if + * OpusServerInfo is requested, since this function is also used by + * op_vopen_url() and op_vtest_url(). + *Even if this function succeeds, those functions might ultimately fail. + *If they do, they should return without having touched the OpusServerInfo + * passed by the application. + *Therefore, if this function succeeds and OpusServerInfo is requested, the + * actual info will be stored in *_info and a pointer to the application's + * storage will be placed in *_pinfo. + *If this function fails or if the application did not request OpusServerInfo, + * *_pinfo will be NULL. + *Our caller is responsible for copying *_info to **_pinfo if it ultimately + * succeeds, or for clearing *_info if it ultimately fails. + */ +static void *op_url_stream_vcreate_impl(OpusFileCallbacks * _cb, + const char *_url, + OpusServerInfo * _info, + OpusServerInfo ** _pinfo, + va_list _ap) +{ + int skip_certificate_check; + const char *proxy_host; + opus_int32 proxy_port; + const char *proxy_user; + const char *proxy_pass; + OpusServerInfo *pinfo; + skip_certificate_check = 0; + proxy_host = NULL; + proxy_port = 8080; + proxy_user = NULL; + proxy_pass = NULL; + pinfo = NULL; + *_pinfo = NULL; + for (;;) { + ptrdiff_t request; + request = va_arg(_ap, char *) - (char *) NULL; + /*If we hit NULL, we're done processing options. */ + if (!request) + break; + switch (request) { + case OP_SSL_SKIP_CERTIFICATE_CHECK_REQUEST:{ + skip_certificate_check = !!va_arg(_ap, opus_int32); + } + break; + case OP_HTTP_PROXY_HOST_REQUEST:{ + proxy_host = va_arg(_ap, const char *); + } break; + case OP_HTTP_PROXY_PORT_REQUEST:{ + proxy_port = va_arg(_ap, opus_int32); + if (proxy_port < 0 || proxy_port > (opus_int32) 65535) + return NULL; + } + break; + case OP_HTTP_PROXY_USER_REQUEST:{ + proxy_user = va_arg(_ap, const char *); + } break; + case OP_HTTP_PROXY_PASS_REQUEST:{ + proxy_pass = va_arg(_ap, const char *); + } break; + case OP_GET_SERVER_INFO_REQUEST:{ + pinfo = va_arg(_ap, OpusServerInfo *); + } + break; + /*Some unknown option. */ + default: + return NULL; + } + } + /* If the caller has requested server information, proxy it to a local copy to + * simplify error handling. */ + if (pinfo != NULL) { + void *ret; + opus_server_info_init(_info); + ret = op_url_stream_create_impl(_cb, _url, skip_certificate_check, + proxy_host, proxy_port, proxy_user, + proxy_pass, _info); + if (ret != NULL) + *_pinfo = pinfo; + else + opus_server_info_clear(_info); + return ret; + } + return op_url_stream_create_impl(_cb, _url, skip_certificate_check, + proxy_host, proxy_port, proxy_user, + proxy_pass, NULL); +} + +void *op_url_stream_vcreate(OpusFileCallbacks * _cb, + const char *_url, va_list _ap) +{ + OpusServerInfo info; + OpusServerInfo *pinfo; + void *ret; + ret = op_url_stream_vcreate_impl(_cb, _url, &info, &pinfo, _ap); + if (pinfo != NULL) + *pinfo = info; + return ret; +} + +void *op_url_stream_create(OpusFileCallbacks * _cb, const char *_url, ...) +{ + va_list ap; + void *ret; + va_start(ap, _url); + ret = op_url_stream_vcreate(_cb, _url, ap); + va_end(ap); + return ret; +} + +/*Convenience routines to open/test URLs in a single step.*/ + +OggOpusFile *op_vopen_url(const char *_url, int *_error, va_list _ap) +{ + OpusFileCallbacks cb; + OggOpusFile *of; + OpusServerInfo info; + OpusServerInfo *pinfo; + void *source; + source = op_url_stream_vcreate_impl(&cb, _url, &info, &pinfo, _ap); + if (source == NULL) { + OP_ASSERT(pinfo == NULL); + if (_error != NULL) + *_error = OP_EFAULT; + return NULL; + } + of = op_open_callbacks(source, &cb, NULL, 0, _error); + if (of == NULL) { + if (pinfo != NULL) + opus_server_info_clear(&info); + (*cb.close) (source); + } else if (pinfo != NULL) + *pinfo = info; + return of; +} + +OggOpusFile *op_open_url(const char *_url, int *_error, ...) +{ + OggOpusFile *ret; + va_list ap; + va_start(ap, _error); + ret = op_vopen_url(_url, _error, ap); + va_end(ap); + return ret; +} + +OggOpusFile *op_vtest_url(const char *_url, int *_error, va_list _ap) +{ + OpusFileCallbacks cb; + OggOpusFile *of; + OpusServerInfo info; + OpusServerInfo *pinfo; + void *source; + source = op_url_stream_vcreate_impl(&cb, _url, &info, &pinfo, _ap); + if (source == NULL) { + OP_ASSERT(pinfo == NULL); + if (_error != NULL) + *_error = OP_EFAULT; + return NULL; + } + of = op_test_callbacks(source, &cb, NULL, 0, _error); + if (of == NULL) { + if (pinfo != NULL) + opus_server_info_clear(&info); + (*cb.close) (source); + } else if (pinfo != NULL) + *pinfo = info; + return of; +} + +OggOpusFile *op_test_url(const char *_url, int *_error, ...) +{ + OggOpusFile *ret; + va_list ap; + va_start(ap, _error); + ret = op_vtest_url(_url, _error, ap); + va_end(ap); + return ret; +} diff --git a/http.h b/http.h new file mode 100644 index 0000000..7c94f2c --- /dev/null +++ b/http.h @@ -0,0 +1,59 @@ +#ifndef _OPUSFILE_HTTP_H +#define _OPUSFILE_HTTP_H (1) + +#ifndef _LARGEFILE_SOURCE +# define _LARGEFILE_SOURCE +#endif +#ifndef _LARGEFILE64_SOURCE +# define _LARGEFILE64_SOURCE +#endif +#ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +#endif + +#include +#include + +#define OP_FATAL(_str) abort() +#define OP_ASSERT(_cond) +#define OP_ALWAYS_TRUE(_cond) ((void)(_cond)) + +#define OP_INT64_MAX (2*(((ogg_int64_t)1<<62)-1)|1) +#define OP_INT64_MIN (-OP_INT64_MAX-1) +#define OP_INT32_MAX (2*(((ogg_int32_t)1<<30)-1)|1) +#define OP_INT32_MIN (-OP_INT32_MAX-1) + +#define OP_MIN(_a,_b) ((_a)<(_b)?(_a):(_b)) +#define OP_MAX(_a,_b) ((_a)>(_b)?(_a):(_b)) +#define OP_CLAMP(_lo,_x,_hi) (OP_MAX(_lo,OP_MIN(_x,_hi))) + +/* Advance a file offset by the given amount, clamping against OP_INT64_MAX. + * This is used to advance a known offset by things like OP_CHUNK_SIZE or + * OP_PAGE_SIZE_MAX, while making sure to avoid signed overflow. + * It assumes that both _offset and _amount are non-negative. + */ +#define OP_ADV_OFFSET(_offset,_amount) \ + (OP_MIN(_offset,OP_INT64_MAX-(_amount))+(_amount)) + +/*The maximum channel count for any mapping we'll actually decode.*/ +# define OP_NCHANNELS_MAX (8) + +/*Initial state.*/ +# define OP_NOTOPEN (0) +/*We've found the first Opus stream in the first link.*/ +# define OP_PARTOPEN (1) +# define OP_OPENED (2) +/*We've found the first Opus stream in the current link.*/ +# define OP_STREAMSET (3) +/*We've initialized the decoder for the chosen Opus stream in the current + link.*/ +# define OP_INITSET (4) + +/* Information cached for a single link in a chained Ogg Opus file. + * We choose the first Opus stream encountered in each link to play back (and + * require at least one). + */ + +int op_strncasecmp(const char *_a,const char *_b,int _n); + +#endif diff --git a/in2.h b/in2.h new file mode 100644 index 0000000..9a2bd08 --- /dev/null +++ b/in2.h @@ -0,0 +1,103 @@ +#include "out.h" + +// note: exported symbol is now winampGetInModule2. + +#define IN_VER 0x100 + +typedef struct +{ + int version; // module type (IN_VER) + char *description; // description of module, with version string + + HWND hMainWindow; // winamp's main window (filled in by winamp) + HINSTANCE hDllInstance; // DLL instance handle (Also filled in by winamp) + + char *FileExtensions; // "mp3\0Layer 3 MPEG\0mp2\0Layer 2 MPEG\0mpg\0Layer 1 MPEG\0" + // May be altered from Config, so the user can select what they want + + int is_seekable; // is this stream seekable? + int UsesOutputPlug; // does this plug-in use the output plug-ins? (musn't ever change, ever :) + + void (*Config)(HWND hwndParent); // configuration dialog + void (*About)(HWND hwndParent); // about dialog + + void (*Init)(); // called at program init + void (*Quit)(); // called at program quit + + void (*GetFileInfo)(char *file, char *title, int *length_in_ms); // if file == NULL, current playing is used + int (*InfoBox)(char *file, HWND hwndParent); + + int (*IsOurFile)(char *fn); // called before extension checks, to allow detection of mms://, etc + // playback stuff + int (*Play)(char *fn); // return zero on success, -1 on file-not-found, some other value on other (stopping winamp) error + void (*Pause)(); // pause stream + void (*UnPause)(); // unpause stream + int (*IsPaused)(); // ispaused? return 1 if paused, 0 if not + void (*Stop)(); // stop (unload) stream + + // time stuff + int (*GetLength)(); // get length in ms + int (*GetOutputTime)(); // returns current output time in ms. (usually returns outMod->GetOutputTime() + void (*SetOutputTime)(int time_in_ms); // seeks to point in stream (in ms). Usually you signal yoru thread to seek, which seeks and calls outMod->Flush().. + + // volume stuff + void (*SetVolume)(int volume); // from 0 to 255.. usually just call outMod->SetVolume + void (*SetPan)(int pan); // from -127 to 127.. usually just call outMod->SetPan + + // in-window builtin vis stuff + + void (*SAVSAInit)(int maxlatency_in_ms, int srate); // call once in Play(). maxlatency_in_ms should be the value returned from outMod->Open() + // call after opening audio device with max latency in ms and samplerate + void (*SAVSADeInit)(); // call in Stop() + + + // simple vis supplying mode + void (*SAAddPCMData)(void *PCMData, int nch, int bps, int timestamp); + // sets the spec data directly from PCM data + // quick and easy way to get vis working :) + // needs at least 576 samples :) + + // advanced vis supplying mode, only use if you're cool. Use SAAddPCMData for most stuff. + int (*SAGetMode)(); // gets csa (the current type (4=ws,2=osc,1=spec)) + // use when calling SAAdd() + void (*SAAdd)(void *data, int timestamp, int csa); // sets the spec data, filled in by winamp + + + // vis stuff (plug-in) + // simple vis supplying mode + void (*VSAAddPCMData)(void *PCMData, int nch, int bps, int timestamp); // sets the vis data directly from PCM data + // quick and easy way to get vis working :) + // needs at least 576 samples :) + + // advanced vis supplying mode, only use if you're cool. Use VSAAddPCMData for most stuff. + int (*VSAGetMode)(int *specNch, int *waveNch); // use to figure out what to give to VSAAdd + void (*VSAAdd)(void *data, int timestamp); // filled in by winamp, called by plug-in + + + // call this in Play() to tell the vis plug-ins the current output params. + void (*VSASetInfo)(int nch, int srate); + + + // dsp plug-in processing: + // (filled in by winamp, called by input plug) + + // returns 1 if active (which means that the number of samples returned by dsp_dosamples + // could be greater than went in.. Use it to estimate if you'll have enough room in the + // output buffer + int (*dsp_isactive)(); + + // returns number of samples to output. This can be as much as twice numsamples. + // be sure to allocate enough buffer for samples, then. + int (*dsp_dosamples)(short int *samples, int numsamples, int bps, int nch, int srate); + + + // eq stuff + void (*EQSet)(int on, char data[10], int preamp); // 0-64 each, 31 is +0, 0 is +12, 63 is -12. Do nothing to ignore. + + // info setting (filled in by winamp) + void (*SetInfo)(int bitrate, int srate, int stereo, int synched); // if -1, changes ignored? :) + + Out_Module *outMod; // filled in by winamp, optionally used :) +} In_Module; + + diff --git a/in_opus.c b/in_opus.c new file mode 100644 index 0000000..ac9e558 --- /dev/null +++ b/in_opus.c @@ -0,0 +1,1027 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * in_opus.c by Raymond GILLIBERT, from template in_raw.c. * + * * + * This is a WINAMP 2+ Plugin to play .opus files and radio streams. * + * I think you can do whatever you want with the code if you find it. * + * The code depends on libopus, libogg and libopusfile * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include +#include +#include +#include +#include +#include "resource.h" +#include "infobox.h" +#include "in2.h" +#include "wa_ipc.h" +#include "resample.h" + +// post this to the main window at end of file (after playback as stopped) +#define WM_WA_MPEG_EOF WM_USER+2 +#define IN_UNICODE 0x0F000000 + +// raw configuration. +#define NCH 2 +#define SR 48000 +#define VERBOSE 0 +#define BIG_LENGTH INT_MAX +#define BIG_LENGTH_MULT 60 +#define DECODE_BUFF_SIZE (20*(SR/1000)*NCH)// Smaller one 20ms in // in SAMPLES + +// FUNCTION DECLARATION +static void first_init(char *font_str, int *font_height); +void setvolume(int volume); +void pause(); +char *utf16_to_utf8(const wchar_t *input); +wchar_t *utf8_to_utf16(const char *utfs); +DWORD WINAPI DecodeThread(LPVOID b); // the decode thread procedure + +// GLOBAL VARIABLES +In_Module mod; // the output module (filled in near the bottom of this file) +char lastfn[3*MAX_PATH]; // currently playing file + +opus_int64 decode_pos_ms; // current decoding position, in milliseconds. + // Used for correcting DSP plug-in pitch changes +volatile opus_int64 seek_needed; // if != -1, it is the point that the decode + // thread should seek to, in ms. +int paused; // are we paused? +HANDLE thread_handle=INVALID_HANDLE_VALUE; // the handle to the decode thread + +volatile char killDecodeThread=0; // the kill switch for the decode thread + +OggOpusFile *_of=NULL; +HFONT TAGS_FONT=NULL; +int THREAD_PRIORITY; +opus_int32 PRE_GAIN; +opus_int32 RADIO_GAIN; +int OP_CURRENT_GAIN; +int SAMPLERATE; +char RESAMPLE_Q; +char BPS; +char TAGS_DISPLAY_MODE, isNT; // 0: raw, 1: ANSI, 2: Unicode, 3: AUTO +char UNICODE_FILE; +char INTERNAL_VOLUME; +char VIS_BROKEN; // Winamp version < 5.11 +char HOURS_MODE_ON; // to fix the 24days 20h 31min 24s and 647ms bug +char TARGET_LUFS; +char USE_REPLAY_GAIN=0; // 0 : no, 1: Album, 2: Track. +char USE_DITHERING; +char RADIO; +char OGGEXT_HACK; +char UNIFIED_DIALOG; +char INSTANT_BR; + +///////////////////////////////////////////////////////////////////////////// +// This function is mendatory when linking with MinGW to be able to load dll +// Uner windows 95. You have to built everithing with the following flags +// -mdll -e _DllMain@12 -fno-stack-check -fno-stack-protector +// -mno-stack-arg-probe -nostdlib -lgcc -lkernel32 -lmsvcrt -luser32 -lgdi32 +// I Know that is a lot.... (I am using TDM GCC 5.1, [2015]) +BOOL WINAPI DllMain(HANDLE hInst, ULONG reason_for_call, LPVOID lpReserved) +{ + return TRUE; +} + +///////////////////////////////////////////////////////////////////////////// +// Configuration box we'd want to write it here (using DialogBox, etc) +void config(HWND hwndParent) +{ + int font_height; + char *monstring= calloc(1024, sizeof(char)); if(!monstring) return; + char *font_str = calloc(256, sizeof(char)); if(!font_str) return; + + // To read the config so we show up to date data + // This also makes a way not to restart winamp... + first_init(font_str, &font_height); + + sprintf(monstring, + "Open \"winamp.ini\" with notepad and write the options you want to change\n" + "Current Config:\n\n" + + "[IN_OPUS]\n" + "USE_REPLAY_GAIN=%d\n" + "PRE_GAIN=%.2f\n" + "RADIO_GAIN=%.2f\n" + "TAGS_DISPLAY_MODE=%d\n" + "TAGS_FONT=%d %s\n" + "OUT_SAMPLERATE=%d\n" + "OUT_BITS=%d\n" + "INTERNAL_VOLUME=%d\n" + "TARGET_LUFS=%d\n\n" + + "USE_REPLAY_GAIN values:\n" + "0: Disable (default), 1: Album gain 2: Track gain \n" + "3: Auto track/album gain when shuffle on/off\n" + "4: Raw gain (not even header gain)\n" + "PRE_GAIN is a preamplification factor in dB, applied before RG.\n" + "TAGS_DISPLAY_MODE: 0: Raw, 1: ANSI, 2: Force Unicode 3: Auto\n" + "OUT_SAMPLERATE to set the output samplerate\n" + "INTERNAL_VOLUME set to 1 to enable internal handling of volume.\n" + "TARGET_LUFS, is the Loudness in Units of Full Scale that you want (default -23)" + + , USE_REPLAY_GAIN, ((float)PRE_GAIN)/256.F, ((float)RADIO_GAIN)/256.F + , TAGS_DISPLAY_MODE, font_height, font_str, SAMPLERATE, BPS + , INTERNAL_VOLUME, TARGET_LUFS + ); + MessageBox(hwndParent, monstring, "This is not the configuration you are looking for", MB_OK); + + free(monstring); + free(font_str); +} +///////////////////////////////////////////////////////////////////////////// +// About Dialog box... +void about(HWND hwndParent) +{ + MessageBox(hwndParent, + "OPUS File Player v0.911, by Raymond Gillibert (*.opus, *.opu files).\n" + "Using libopus 1.3.1, libogg 1.3.2 and libopusfile 0.12.\n" + "You can write me at raymond_gillibert@yahoo.fr if necessary." + , "About Winamp OPUS Player",MB_OK); +} + +///////////////////////////////////////////////////////////////////////////// +// To read the internal config +static void readinternalconfig(char *font_str, int *font_height) +{ + float pre_gain_float, radio_gain_float; + int UNICODE_FILE_i, RESAMPLE_Q_i, USE_REPLAY_GAIN_i, INTERNAL_VOLUME_i + , TAGS_DISPLAY_MODE_i, USE_DITHERING_i, TARGET_LUFS_i, bps_i + , OGGEXT_HACK_i, UNIFIED_DIALOG_i, INSTANT_BR_i; + + THREAD_PRIORITY = THREAD_PRIORITY_ABOVE_NORMAL; + + sscanf ("Out=48000, 2, 16 Uni=2, Gain=0, +0.0000, -3.0000, -23, 0, 3, 1, 0, 0, 1, 0 " + "Times New Roman or other font with a super long name", + "Out=%d, %d, %d Uni=%d, Gain=%d, %f, %f, %d, %d, %d, %d, %d, %d, %d, %d %[^\n]s" + , &SAMPLERATE, &RESAMPLE_Q_i, &bps_i + , &UNICODE_FILE_i + , &USE_REPLAY_GAIN_i, &pre_gain_float, &radio_gain_float + , &TARGET_LUFS_i + , &INTERNAL_VOLUME_i + , &TAGS_DISPLAY_MODE_i + , &USE_DITHERING_i + , &OGGEXT_HACK_i + , &UNIFIED_DIALOG_i + , &INSTANT_BR_i + , font_height, font_str); + + RESAMPLE_Q = (char) RESAMPLE_Q_i; + UNICODE_FILE = (char) UNICODE_FILE_i; + BPS = (char) bps_i; + USE_REPLAY_GAIN = (char) USE_REPLAY_GAIN_i; + TAGS_DISPLAY_MODE = (char) TAGS_DISPLAY_MODE_i; + USE_DITHERING = (char) USE_DITHERING_i; + INTERNAL_VOLUME = (char) INTERNAL_VOLUME_i; + TARGET_LUFS = (char) TARGET_LUFS_i; + OGGEXT_HACK = (char) OGGEXT_HACK_i; + UNIFIED_DIALOG = (char) UNIFIED_DIALOG_i; + INSTANT_BR = (char) INSTANT_BR_i; + PRE_GAIN = lrintf(pre_gain_float*256.F); + RADIO_GAIN = lrintf(radio_gain_float*256.F); +} +///////////////////////////////////////////////////////////////////////////// +// To read the config +static void readconfig(char *ini_name, char *rez, char *font_str, int *font_height) +{ + if(!ini_name || *ini_name=='\0' || !rez || !font_str || !font_height) return; + if(INVALID_FILE_ATTRIBUTES == GetFileAttributes(ini_name)) return; + + USE_REPLAY_GAIN =GetPrivateProfileInt("IN_OPUS", "USE_REPLAY_GAIN", USE_REPLAY_GAIN, ini_name); + THREAD_PRIORITY =GetPrivateProfileInt("IN_OPUS", "THREAD_PRIORITY", THREAD_PRIORITY, ini_name); + TAGS_DISPLAY_MODE=GetPrivateProfileInt("IN_OPUS", "TAGS_DISPLAY_MODE", TAGS_DISPLAY_MODE, ini_name); + USE_DITHERING =GetPrivateProfileInt("IN_OPUS", "USE_DITHERING", USE_DITHERING, ini_name); + UNICODE_FILE =GetPrivateProfileInt("IN_OPUS", "UNICODE_FILE", UNICODE_FILE, ini_name); + SAMPLERATE =GetPrivateProfileInt("IN_OPUS", "OUT_SAMPLERATE", SAMPLERATE, ini_name); + RESAMPLE_Q =GetPrivateProfileInt("IN_OPUS", "RESAMPLE_Q", RESAMPLE_Q, ini_name); + INTERNAL_VOLUME =GetPrivateProfileInt("IN_OPUS", "INTERNAL_VOLUME", INTERNAL_VOLUME, ini_name); + TARGET_LUFS =GetPrivateProfileInt("IN_OPUS", "TARGET_LUFS", TARGET_LUFS, ini_name); + BPS =GetPrivateProfileInt("IN_OPUS", "OUT_BITS", BPS, ini_name); + OGGEXT_HACK =GetPrivateProfileInt("IN_OPUS", "OGGEXT_HACK", OGGEXT_HACK, ini_name); + UNIFIED_DIALOG =GetPrivateProfileInt("IN_OPUS", "UNIFIED_DIALOG", UNIFIED_DIALOG, ini_name); + INSTANT_BR =GetPrivateProfileInt("IN_OPUS", "INSTANT_BR", INSTANT_BR, ini_name); + + if(BPS!=8 && BPS!=24 && BPS!=32) BPS=16; + + if(RESAMPLE_Q < 0) RESAMPLE_Q=0; else if (RESAMPLE_Q > 4) RESAMPLE_Q=4; + if(SAMPLERATE < 1) SAMPLERATE = SR; else if (SAMPLERATE > 192000) SAMPLERATE=192000; + + if(GetPrivateProfileString("IN_OPUS", "PRE_GAIN", "666.0", rez, 255, ini_name)){ + float tmpfloatgain; + sscanf(rez, "%f", &tmpfloatgain); + if(tmpfloatgain < 665.0F) PRE_GAIN = lrintf(tmpfloatgain*256.F); + } + if(GetPrivateProfileString("IN_OPUS", "RADIO_GAIN", "666.0", rez, 255, ini_name)){ + float tmpfloatgain; + sscanf(rez, "%f", &tmpfloatgain); + if (tmpfloatgain < 665.0F) RADIO_GAIN = lrintf(tmpfloatgain*256.F); + } + + ///// CUSTOM FONTS ///// + if(GetPrivateProfileString("IN_OPUS", "TAGS_FONT", "", rez, 255, ini_name)){ + sscanf(rez, "%d %[^\n]s", font_height, font_str); + } +} +///////////////////////////////////////////////////////////////////////////// +// +static HFONT applyglobalfont(char *font_str, int font_height) +{ + HDC hdc; + long lfHeight; + HFONT tags_font=NULL; // To set the font + + if(font_str == NULL || font_height == 0 || *font_str == '\0'){ + if(VERBOSE)MessageBox(NULL,"No font substitution","in_opus",MB_OK); + tags_font = NULL; + } else { + hdc = GetDC(NULL); + lfHeight = -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72); + ReleaseDC(NULL, hdc); + tags_font = CreateFont(lfHeight,0,0,0,0,FALSE,0,0,0,0,0,0,0,font_str); + if(VERBOSE)MessageBox(NULL,font_str,"in_opus: using font",MB_OK); + } + + return tags_font; +} +///////////////////////////////////////////////////////////////////////////// +// We need config to be read already. +void applyglobal_unicode_fn(char *ini_name, int ininamelength, char *rez) +{ + if(UNICODE_FILE == 2 && isNT){ // Auto mode and WinNT + FILE *Whatsnew=NULL; + float version=0; + char *p; + + p = ini_name + ininamelength; + while (p >= ini_name && *p != '\\' && *p != '/') p--; + strcpy(++p, "wacup.exe"); + if(INVALID_FILE_ATTRIBUTES != GetFileAttributes(ini_name)){ + if(VERBOSE)MessageBox(mod.hMainWindow,"You are using WACUP dude!","in_opus",MB_OK); + return; + } + strcpy(p, "MediaMonkey.exe"); + if(INVALID_FILE_ATTRIBUTES != GetFileAttributes(ini_name)){ + if(VERBOSE)MessageBox(mod.hMainWindow,"You are using WACUP dude!","in_opus",MB_OK); + UNICODE_FILE=3; // WE ARE USING MEDIA MONKEY + return; + } + strcpy(p, "whatsnew.txt"); + // NOW ini_name contains the path towards "whatsnew.txt" + if(INVALID_FILE_ATTRIBUTES != GetFileAttributes(ini_name)) + Whatsnew=fopen(ini_name, "r"); + if(Whatsnew){ + if(fgets(rez, 255, Whatsnew)){ + sscanf(rez, "Winamp %f", &version); + if(VERBOSE){ + sprintf(rez, "You are using winamp version %f", version); + MessageBox(mod.hMainWindow, rez, "in_opus", MB_OK); + } + } + if(version > 5.295) UNICODE_FILE=2; // if Auto and WinNT and Winamp 5.3+ + else UNICODE_FILE=0; // if Winamp 5.29- + }else{ + if(VERBOSE) MessageBox(mod.hMainWindow,"We could not determine WINAMP's version!","in_opus",MB_OK); + UNICODE_FILE=0; // if we could not find the "whatsnew.txt" + } + if(Whatsnew) fclose(Whatsnew); + } else if(UNICODE_FILE == 2) { // In AUTO mode and not NT. + if(VERBOSE) MessageBox(mod.hMainWindow,"You are using Windows 9x\n" + "No UNICODE support dude!","in_opus",MB_OK); + UNICODE_FILE=0; // if we are not under NT based OS + } + // Here if UNICODE_FILE=1 it means it was before. + // if UNICODE_FILE=0 it means that it was set to 0 befort or that the system does + // not meet the requirements. + // Finally if UNICODE_FILE=2 it means that Auto mode was enabled requirements are met. +} +///////////////////////////////////////////////////////////////////////////// +// Initialisation called at startup... +void init() +{ + HOURS_MODE_ON=0; + char ini_name[MAX_PATH] = {0}, rez[256], font_str[256]; + int font_height=0; + int winamp_version; + + VIS_BROKEN=1; + winamp_version = SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GETVERSION); + + if(winamp_version >= 0x5011){ + if(winamp_version >= 0x5012) VIS_BROKEN=0; // Winamp 5.12 allows 24/32b visualisation. + // this gets the string of the full ini file path + char *newini = (char *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GETINIFILE); + if(!newini) return; + strncpy(ini_name, newini, sizeof(ini_name)); + + // Lastfn here contains the winamp.ini path, we read the file only if + // we have not read it already. + if(_stricmp(lastfn, ini_name)){ + if(VERBOSE)MessageBox(NULL, ini_name,"in_opus: Winamp ini_name bis",MB_OK); + readconfig(ini_name, rez, font_str, &font_height); //winamp.ini + applyglobalfont(font_str, font_height); + } + } +} +///////////////////////////////////////////////////////////////////////////// +// Initialisation called before winamp loads the module. +static void first_init(char *Fstr, int *Fnth) +{ + if(VERBOSE)MessageBox(NULL,"first_init start","in_opus",MB_OK); + + int font_height=0; + + char ini_name[MAX_PATH]={0}, rez[256], font_str[256]; + int ininamelength; + char *p; + + isNT = !(GetVersion() & 0x80000000); /* To Use Unicode stuff on NT */ + + readinternalconfig(font_str, &font_height); + + //// Find in_opus.ini in plugin folder //// + GetModuleFileName(NULL, ini_name, sizeof(ini_name)); + ininamelength = strlen(ini_name); + p = strrchr(ini_name, '\\'); + strcpy(p, "\\Plugins\\in_opus.ini"); + if(VERBOSE)MessageBox(NULL, ini_name,"in_opus: in_opus.ini path",MB_OK); + + readconfig(ini_name, rez, font_str, &font_height); //in_opus.ini + + //// Find winamp.ini IN Winamp's folder //// + GetModuleFileName(NULL, ini_name, sizeof(ini_name)); + ininamelength = strlen(ini_name); + p = strrchr(ini_name, '.'); + if (!p) p = ini_name + ininamelength; + strncpy(p, ".ini\0",5); + if(VERBOSE)MessageBox(NULL, ini_name,"in_opus: Winamp.ini path",MB_OK); + + readconfig(ini_name, rez, font_str, &font_height); //winamp.ini + + TAGS_FONT = applyglobalfont(font_str, font_height); + + applyglobal_unicode_fn(ini_name, ininamelength, rez); + if(Fstr && Fnth){ + if(font_height!=0)strcpy(Fstr, font_str); + else strcpy(Fstr, "(Not user set)"); + *Fnth=font_height; + }else{ + strcpy(lastfn, ini_name); + } +} // END OF INIT +///////////////////////////////////////////////////////////////////////////// +// Called when winanmp quits... +void quit() +{ + if(_of) op_free(_of); // Just in case +} + +///////////////////////////////////////////////////////////////////////////// +// Used for detecting URL streams... +int isourfile(char *fn) +{ + char *fnUTF8, *p; + int err = -132; + + if(UNICODE_FILE) fnUTF8 = utf16_to_utf8((wchar_t *)fn); else fnUTF8 = NULL; + fn = p = (UNICODE_FILE && fnUTF8) ? fnUTF8 : fn; + p+= strlen(p) - 4 ; + if (isURL(fn)) { + + if( !_strnicmp(p,".opu", 4) + || !_strnicmp(--p,".opus", 5) ){ + return 1; + } + } else if (OGGEXT_HACK && !_strnicmp(p, ".ogg", 4)){ + // Maybe an opus file with .ogg ext + OggOpusFile *_tmp = op_test_file(fn, &err); + + if (err == 0 && _tmp) { + op_free(_tmp); +// MessageBox(NULL, ".OGG OPUS file", "isourfile", MB_OK); + return 1; // This is actually an opus file. + } else { +// MessageBox(NULL, ".OGG NOT OPUS file", "isourfile", MB_OK); + return 0; + } + } + + return 0; +} +///////////////////////////////////////////////////////////////////////////// +// This is for the current file only. We could add more features like +// handelig of peak tag info, +void apply_replaygain(void) +{ + int lufs_offset; + + // We apply the gain to reach desired LUFs only if RG is enabled + // AND RG info is availabes in file + if(USE_REPLAY_GAIN) lufs_offset = (isRGavailable(_of))? (TARGET_LUFS+23)*256 : 0; + else lufs_offset = 0; + + switch(USE_REPLAY_GAIN){ + case 1: OP_CURRENT_GAIN = OP_ALBUM_GAIN; break; + case 2: OP_CURRENT_GAIN = OP_TRACK_GAIN; break; + case 3: OP_CURRENT_GAIN = SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GET_SHUFFLE) + ? OP_TRACK_GAIN + : OP_ALBUM_GAIN; + break; + // NO LUFS in case of absolute gain + case 4: OP_CURRENT_GAIN = OP_ABSOLUTE_GAIN; lufs_offset = 0; break; + + // By default we use only header gain + default: OP_CURRENT_GAIN = OP_HEADER_GAIN; + } + op_set_gain_offset(_of, OP_CURRENT_GAIN, lufs_offset+(RADIO? RADIO_GAIN: PRE_GAIN)); +} + +///////////////////////////////////////////////////////////////////////////// +// Called when winamp wants to play a file +int play(char *fn) +{ + int maxlatency, err; + DWORD thread_id; + size_t dwStackSize; + char *fnUTF8=NULL, *p; + paused=0; + decode_pos_ms=0; + seek_needed=-1; + + // File opening here + if(UNICODE_FILE) fnUTF8 = utf16_to_utf8((wchar_t *)fn); + if(VERBOSE) MessageBox(mod.hMainWindow, UNICODE_FILE && fnUTF8? fnUTF8: fn + , "in_opus: play function, going to OPEN FILE",MB_OK); + if(isURL(UNICODE_FILE ? fnUTF8 : fn)){ + RADIO = 1; + mod.is_seekable = 0; + + if(VERBOSE) MessageBox(mod.hMainWindow, fn , "in_opus: This is an URL",MB_OK); + p = UNICODE_FILE ? fnUTF8 : fn; + p += strlen(p) - 6; + if(!_strnicmp(p,">.opus", 6)) *p ='\0'; + _of = op_open_url(UNICODE_FILE && fnUTF8 ? fnUTF8 : fn, &err, NULL); + + } else { + _of = op_open_file(UNICODE_FILE && fnUTF8 ? fnUTF8 : fn, &err); + RADIO = 0; + mod.is_seekable = 1; + } + if (_of == NULL || err){ // error opening file + if(VERBOSE) MessageBox(mod.hMainWindow, UNICODE_FILE ? fnUTF8: fn , "in_opus: unable to open file",MB_OK); + // we return error. 1 means to keep going in the playlist, -1 + // means to stop the playlist. + if(RADIO) strcpy(lastfn, UNICODE_FILE ? fnUTF8 : fn); + + if(fnUTF8) free(fnUTF8); + return RADIO? 0 : 1; + } + + strcpy(lastfn, UNICODE_FILE ? fnUTF8 : fn); + free(fnUTF8); + + // -1 and -1 are to specify buffer and prebuffer lengths. -1 means + // to use the default, which all input plug-ins should really do. + maxlatency = mod.outMod->Open(SAMPLERATE,NCH,BPS, -1,-1); + + // maxlatency is the maxium latency between a outMod->Write() call and + // when you hear those samples. In ms. Used primarily by the visualization + // system if < 0 means error opening device. + if (maxlatency < 0) { + if(VERBOSE) MessageBox(mod.hMainWindow,"Unable to open devide (maxlatency < 0)" + , "in_opus error:",MB_OK); + op_free(_of); _of=NULL; + return 1; + } + // initialize visualization stuff + mod.SAVSAInit(maxlatency,SAMPLERATE); + mod.VSASetInfo(SAMPLERATE,NCH); + + // Set Dithering and RG + op_set_dither_enabled(_of, USE_DITHERING); + apply_replaygain(); + + // set the output plug-ins default volume. + // volume is 0-255, -666 is a token for current volume. + setvolume(-666); // mod.outMod->SetVolume(-666); + + // LAUNCH DECODE THREAD + if(VERBOSE >= 2) MessageBox(NULL,"Going to Lunch DecodeThread", "in_opus", MB_OK); + dwStackSize = 0; //1048576 = 1 MB + killDecodeThread=0; + thread_handle = (HANDLE) CreateThread(NULL,dwStackSize,(LPTHREAD_START_ROUTINE) DecodeThread,NULL,0,&thread_id); + SetThreadPriority (thread_handle, THREAD_PRIORITY); + + return 0; +} // END OF play() + +///////////////////////////////////////////////////////////////////////////// +// Standard pause implementation +void pause() { paused=1; mod.outMod->Pause(1); } +void unpause() { paused=0; mod.outMod->Pause(0); } +int ispaused() { return paused; } + +///////////////////////////////////////////////////////////////////////////// +// Stop playing. +void stop() +{ + if (thread_handle != INVALID_HANDLE_VALUE) { + killDecodeThread=1; + if (WaitForSingleObject(thread_handle, 5000) == WAIT_TIMEOUT){ // 5 sec + MessageBox(mod.hMainWindow + ,"Error, DecodeThread not responding...\nKilling decode thread!" + ,"in_opus: error", 0); + TerminateThread(thread_handle, 0); + } + CloseHandle(thread_handle); + thread_handle = INVALID_HANDLE_VALUE; + } + + // close output system + mod.outMod->Close(); + + // deinitialize visualization + mod.SAVSADeInit(); + + // Write your own file closing code here + HOURS_MODE_ON=0; + if (_of != NULL) { + op_free(_of); + _of=NULL; + } +} // END OF stop() + +///////////////////////////////////////////////////////////////////////////// +// Returns length of playing track in ms +int getlength() +{ + opus_int64 length_in_ms; // -1000 means unknown + + length_in_ms = -1000; + if(HOURS_MODE_ON!=2) HOURS_MODE_ON=0; + + if(!RADIO && _of) length_in_ms = op_pcm_total(_of, -1)/(opus_int64)48; + + if(length_in_ms > BIG_LENGTH){ // Could be INT_MAX here or 1 000 min + length_in_ms = length_in_ms/BIG_LENGTH_MULT; + HOURS_MODE_ON=1; + } + + return length_in_ms; // Will be 60x smaller in hour mode +} + +///////////////////////////////////////////////////////////////////////////// +// Returns current output position, in ms. you could just use +// return mod.outMod->GetOutputTime(), but the dsp plug-ins that do tempo +// changing tend to make that wrong. +int getoutputtime() +{ + if(!HOURS_MODE_ON){ + return decode_pos_ms + ( mod.outMod->GetOutputTime()-mod.outMod->GetWrittenTime() ); + } else { + return decode_pos_ms/BIG_LENGTH_MULT; + } +} + +///////////////////////////////////////////////////////////////////////////// +// Called when the user releases the seek scroll bar. +// Usually we use it to set seek_needed to the seek +// point (seek_needed is -1 when no seek is needed) +// and the decode thread checks seek_needed. +void setoutputtime(int time_in_ms) // Or mili-minutes when in HOURS_MODE +{ + seek_needed = HOURS_MODE_ON? ((opus_int64)time_in_ms*(opus_int64)BIG_LENGTH_MULT): time_in_ms; +} + +///////////////////////////////////////////////////////////////////////////// +// Standard volume/pan functions +void setvolume(int volume) +{ + mod.outMod->SetVolume(volume); + static int cvol; + int current_pregain =RADIO? RADIO_GAIN: PRE_GAIN; + if(volume != -666)cvol=volume; + if(INTERNAL_VOLUME==1 || (INTERNAL_VOLUME==2 && RADIO)){ + if(_of)op_set_gain_offset(_of, OP_CURRENT_GAIN, (cvol>0)? current_pregain - 20*(255-cvol): -32768); + } else { + mod.outMod->SetVolume(volume); + } +} +void setpan(int pan) +{ + mod.outMod->SetPan(pan); +} + +///////////////////////////////////////////////////////////////////////////// +// This gets called when the use hits Alt+3 to get the file info. +int infoDlg(char *fn, HWND hwnd) +{ + DoInfoBox(mod.hDllInstance, hwnd, fn); + return 0; +} + +///////////////////////////////////////////////////////////////////////////// +// Returns length of opus file track in ms or mili-minutes +static int get_opus_length_auto(OggOpusFile *_tmp, char *hours_mode_on) +{ + opus_int64 length_in_ms; // -1000 means unknown + + if(hours_mode_on) *hours_mode_on = 0; + + length_in_ms = _tmp ? op_pcm_total(_tmp, -1)/48 : -1000; + + if(length_in_ms > BIG_LENGTH){ // Could be INT_MAX here it is 1 000 min + length_in_ms = length_in_ms/BIG_LENGTH_MULT; + if(hours_mode_on) *hours_mode_on = 1; + } + + return (int)length_in_ms; // Will be 60x smaller in hour mode +} + +///////////////////////////////////////////////////////////////////////////// +// This is an odd function. it is used to get the title and/or +// length of a track. +// If filename is either NULL or of length 0, it means you should +// return the info of lastfn. Otherwise, return the information +// for the file in filename. +// If title is NULL, no title is copied into it. +// If length_in_ms is NULL, no length is copied into it. +void getfileinfo(char *filename, char *title, int *length_in_ms) +{ + int err=0; + OggOpusFile *_tmp=NULL; + char hours_mode_on=0; + + if (!filename || !*filename){ // currently playing file => lastfn + char is_lfn_url = isURL(lastfn); + if(!is_lfn_url)_tmp = op_open_file (lastfn, &err); + if (length_in_ms) { + *length_in_ms = get_opus_length_auto(_tmp, &hours_mode_on); + } + if (title){ // get non-path portion of lastfn + char *p=lastfn+strlen(lastfn); + while (p >= lastfn && *p != '\\') p--; p++; + + if(err && !is_lfn_url && !UNICODE_FILE) + sprintf(title,"[in_opus err %d %s] %s",err,TranslateOpusErr(err), p); + else if(hours_mode_on) + sprintf(title,"%s [h:min]", p); + else + strcpy(title, p); + } + if(_tmp) op_free(_tmp); + } else { // some other file => filename + char is_fn_url = isURL(filename); + if(!is_fn_url) _tmp = op_open_file (filename, &err); + if (length_in_ms){ // calculate length + *length_in_ms = get_opus_length_auto(_tmp, &hours_mode_on); + } + if (title){ // get non path portion of filename + const char *p=filename+strlen(filename); + while (p >= filename && *p != '\\') p--; p++; + + if(err && !is_fn_url && !UNICODE_FILE) + sprintf(title,"[in_opus err %d %s] %s",err,TranslateOpusErr(err), p); + else if(hours_mode_on) + sprintf(title,"%s [h:min]", p); + else + strcpy(title, p); + } + if(_tmp) op_free(_tmp); + } +} + +///////////////////////////////////////////////////////////////////////////// +void getfileinfoW(char *filenamew, char *titlew, int *length_in_ms) +{ + int err=0; + OggOpusFile *_tmp=NULL; + char *fnUTF8=NULL, is_lfn_url, is_fn_url; + wchar_t *tmpW=NULL, *filename, *title; + title = (wchar_t *)titlew; + filename = (wchar_t *)filenamew; + + char hours_mode_on=0; + + if (!filename || !*filename){ // currently playing file + is_lfn_url = isURL(lastfn); + if(!is_lfn_url)_tmp = op_open_file(lastfn, &err); + if (length_in_ms){ + *length_in_ms = get_opus_length_auto(_tmp, &hours_mode_on); + } + if (title){ // get non-path portion of lastfn which is UTF8 already + char *p=lastfn+strlen(lastfn); + while (p >= lastfn && *p != '\\') p--; p++; + tmpW = utf8_to_utf16(p); + + if(err && !is_lfn_url) swprintf(title, L"[in_opus err %d %s] %s", err, TranslateOpusErrW(err), tmpW); + else if (hours_mode_on) swprintf(title, L"%s [h:min]", tmpW); + else wcscpy(title, tmpW); + + free(tmpW); + } + if(_tmp) op_free(_tmp); + } else {// some other file => filename + fnUTF8 = utf16_to_utf8(filename); + if(fnUTF8)is_fn_url = isURL(fnUTF8); else return; + if(!is_fn_url) _tmp = op_open_file(fnUTF8, &err); + + if (length_in_ms){ // calculate length + *length_in_ms = get_opus_length_auto(_tmp, &hours_mode_on); + } + if (title) // get non path portion of filename + { + const char *p=fnUTF8+strlen(fnUTF8); + while (p >= fnUTF8 && *p != '\\') p--; p++; + tmpW = utf8_to_utf16(p); + + if(err && !is_fn_url) swprintf(title, L"[in_opus err %d %s] %s", err, TranslateOpusErrW(err), tmpW); + else if (hours_mode_on) swprintf(title, L"%s [h:min]", tmpW); + else wcscpy(title, tmpW); + + free(tmpW); + } + if(_tmp) op_free(_tmp); + if(fnUTF8) free(fnUTF8); + } +} + +///////////////////////////////////////////////////////////////////////////// +// Winamp2 visuals have problems accepting sample sizes larger than +// 16 bits, so we reduce it to 8 bits if in 24 or 32 bit mode. +// Function cp/pasted and modified from in_flac.c (libflac-1.2.1) +static inline void do_vis(char *__restrict data, char *__restrict vis_buffer + , long long pos, unsigned samples, char resolution) +{ + char *ptr; + unsigned size, count; + int position; + + position = HOURS_MODE_ON? pos/BIG_LENGTH_MULT: pos; + + if(!vis_buffer) goto DEFAULT; // If we did not allocate buffer it means + // that ther is no deed to reduce BPS. + switch(resolution) { + case 32: + case 24: + size = resolution / 8; + count = samples; + data += size - 1; + ptr = vis_buffer; + + while(count--) { + *ptr++ = data[0] ^ 0x80; + data += size; + } + data = vis_buffer; + resolution = 8; + /* fall through */ + case 16: + case 8: + default: + DEFAULT: + mod.SAAddPCMData (data, NCH, resolution, position); + mod.VSAAddPCMData(data, NCH, resolution, position); + } +} + +///////////////////////////////////////////////////////////////////////////// +// if you CAN do EQ with your format, each data byte is 0-63 (+20db to -20db) +// and preamp is the same. +void eq_set(int on, char data[10], int preamp) +{ + +} + +///////////////////////////////////////////////////////////////////////////// +// Render 576 samples into buf. This function is only used by DecodeThread. +// Note that if you adjust the size of sample_buffer, for say, 1024 +// sample blocks, it will still work, but some of the visualization +// might not look as good as it could. Stick with 576 sample blocks +// if you can, and have an additional auxiliary (overflow) buffer if +// necessary... Buff size should be 576*NCH*(BPS/8) in bytes +static inline int get_576_samples(char *__restrict buf, float *__restrict Ibuff) +{ + int l; + + // op_read_stereo always output 16b 48kHz stereo output, the return + // value is the number of samples per channel. + if(BPS!=16){ + l = op_read_float_stereo(_of, Ibuff, DECODE_BUFF_SIZE); + float2int_dither(buf, Ibuff, l*NCH, BPS, USE_DITHERING); + }else{ + l = op_read_stereo(_of, (opus_int16*) buf, DECODE_BUFF_SIZE); // (2ch * 60ms at 48KHz = 11520) + } + + return NCH*(BPS/8)*l; // = Number of bytes +} +///////////////////////////////////////////////////////////////////////////// +// Alternate version that down sample to SAMPLERATE. +static inline int get_samples44( + char *__restrict buf, + float *__restrict Ibuff, float *__restrict Obuff, + SpeexResamplerState *StResampler, + spx_uint32_t max_out_samples) +{ + spx_uint32_t l, lout; + lout = max_out_samples; + + l = op_read_float_stereo(_of, Ibuff, DECODE_BUFF_SIZE); // (2ch * 60ms at 48KHz = 11520) + + int lbck=l; + int err = speex_resampler_process_interleaved_float(StResampler, + Ibuff, &l, + Obuff, &lout); + float2int_dither(buf, Obuff, lout*NCH, BPS, USE_DITHERING); + + if (VERBOSE >= 3){ + char tmp[128]; + sprintf(tmp, "We are after resampling, in=%d/%d, out=%d/%d\nERROR: %d" + , l, lbck, lout, max_out_samples, err); + MessageBoxA(NULL,tmp, "in_opus", MB_OK); + } + + return NCH*(BPS/8)*lout; // number of bytes +} + +///////////////////////////////////////////////////////////////////////////// +DWORD WINAPI DecodeThread(LPVOID b) +{ + float *Ibuff=NULL, *Obuff=NULL; + char *sample_buffer=NULL; + char *vis_buffer=NULL; + SpeexResamplerState *StResampler=NULL; + int err, l, max_out_length; + int done=0, avbitrate=0; // set to TRUE if decoding has finished + + if(VERBOSE >= 3) MessageBox(NULL,"Decode Thread Start", "in_opus", MB_OK); + + max_out_length = (DECODE_BUFF_SIZE*(BPS/8)*SAMPLERATE)/SR; // in byte! not samples + sample_buffer = malloc(max_out_length); + if(!sample_buffer) goto QUIT; + + if(SAMPLERATE != SR){ + StResampler = speex_resampler_init(NCH, SR, SAMPLERATE, RESAMPLE_Q, &err); + if(VERBOSE >= 3) MessageBox(NULL,"Speex Resampler started", "in_opus", MB_OK); + Ibuff= malloc( (DECODE_BUFF_SIZE)*sizeof(float)); + Obuff= malloc( ( (DECODE_BUFF_SIZE)*sizeof(float)*SAMPLERATE )/SR); + if(!StResampler || !Ibuff || !Obuff) goto QUIT; + } else if(BPS!=16) { + Ibuff= malloc( (DECODE_BUFF_SIZE)*sizeof(float)); + } + if(VIS_BROKEN && (BPS==24 || BPS==32)) vis_buffer=malloc( (DECODE_BUFF_SIZE*SAMPLERATE)/SR ); //always 8b + + if(!INSTANT_BR) avbitrate = op_bitrate(_of, -1)/1000; + + mod.SetInfo(avbitrate, SAMPLERATE/1000, NCH, 1); + + while (!killDecodeThread) { + if(VERBOSE >= 3) MessageBox(NULL,"Decode Thread not dead", "in_opus", MB_OK); + + if (seek_needed != -1){ // seek is needed. + if(VERBOSE >= 2) MessageBox(NULL,"Going to seek in file", "in_opus", MB_OK); + decode_pos_ms = seek_needed; + seek_needed=-1; + done=0; + mod.outMod->Flush(HOURS_MODE_ON?decode_pos_ms/BIG_LENGTH_MULT:decode_pos_ms); + // flush output device and set + // output position to the seek position + op_pcm_seek (_of, decode_pos_ms*48); //sample to seek to. + } + + if (done){ // done was set to TRUE during decoding, signaling eof + mod.outMod->CanWrite(); // some output drivers need CanWrite + // to be called on a regular basis. + if (!mod.outMod->IsPlaying()) { + // we're done playing, so tell Winamp and quit the thread. + PostMessage(mod.hMainWindow,WM_WA_MPEG_EOF,0,0); + goto QUIT; // quit thread + } + Sleep(10); // give a little CPU time back to the system. + + } else if (mod.outMod->CanWrite() >= ((max_out_length)*(mod.dsp_isactive()?2:1))) + // CanWrite() returns the number of bytes you can write, so we check that + // to the block size. the reason we multiply the block size by two if + // mod.dsp_isactive() is that DSP plug-ins can change it by up to a + // factor of two (for tempo adjustment). + { + if(VERBOSE >= 3) MessageBox(NULL,"Going to get samples", "in_opus", MB_OK); + if(StResampler){ + l=get_samples44(sample_buffer, Ibuff, Obuff, StResampler, max_out_length/(NCH*(BPS/8))); + } else { + l=get_576_samples(sample_buffer, Ibuff); + } + + if (!l){ // no samples means we're at eof + if(VERBOSE >= 2) MessageBox(NULL,"No samples found, we are at OEF", "in_opus", MB_OK); + done=1; + } else { // we got samples! + if(VERBOSE >= 3) MessageBox(NULL,"We got samples", "in_opus", MB_OK); + + do_vis(sample_buffer, vis_buffer, decode_pos_ms, max_out_length/(BPS/8), BPS); + + // adjust decode position variable + decode_pos_ms+=(l*500)/(SAMPLERATE*(BPS/8)); + + // if we have a DSP plug-in, then call it on our samples + if (mod.dsp_isactive()){ + + l=mod.dsp_dosamples( (short *)sample_buffer // dsp_dosamples + , l/(NCH*(BPS/8)), BPS, NCH + , SAMPLERATE) *(NCH*(BPS/8)); + } + if(VERBOSE >= 3) MessageBox(NULL,"going to write pcm data to the output system" + , "in_opus", MB_OK); + + // write the pcm data to the output system + mod.outMod->Write(sample_buffer, l); + + // Write informations in the winamp windows + // dividing by 1000 for the first parameter of setinfo makes it + // display 'H'... for hundred.. i.e. 14H Kbps. + if(INSTANT_BR) { + int br=op_bitrate_instant(_of)/1000; + mod.SetInfo((br> 0)? br:-1,-1,-1,-1); + } + } + } else { + // if we can't write data, wait a little bit. Otherwise, continue + // through the loop writing more data (without sleeping) + Sleep(20); + if(VERBOSE >= 3) MessageBox(NULL,"Unable to write samples,\n Sleeping 20 ms" + , "in_opus", MB_OK); + } + if(decode_pos_ms > BIG_LENGTH) HOURS_MODE_ON=2; + } // END of While !Kill decode thread + + QUIT: + if (StResampler){speex_resampler_destroy(StResampler); StResampler=NULL;} + if(Obuff) free(Obuff); + if(Ibuff) free(Ibuff); + if(sample_buffer) free(sample_buffer); + if(vis_buffer) free(vis_buffer); + + return 0; +} + +///////////////////////////////////////////////////////////////////////////// +// module definition. +In_Module mod = +{ + IN_VER, // defined in IN2.H + "OPUS Player v0.911 by Raymond", + 0, // hMainWindow (filled in by winamp) + 0, // hDllInstance (filled in by winamp) + "OPUS\0OPUS Audio File (*.OPUS)\0OPU\0Opus Audio File (*.OPU)\0", + // this is a double-null limited list. "EXT\0Description\0EXT\0Description\0" etc. + 1, // is_seekable + 1, // uses output plug-in system + config, + about, + init, + quit, + getfileinfo, + infoDlg, + isourfile, + play, + pause, + unpause, + ispaused, + stop, + + getlength, + getoutputtime, + setoutputtime, + + setvolume, + setpan, + + 0,0,0,0,0,0,0,0,0, // visualization calls filled in by winamp + + 0,0, // dsp calls filled in by winamp + + eq_set, //eq_set + + NULL, // setinfo call filled in by winamp + + 0 // out_mod filled in by winamp +}; +///////////////////////////////////////////////////////////////////////////// +// If we want to support unicode filenmes we need to modify these functions: +// void GetFileInfo(char *filename, char *title, int *length_in_ms)* DONE +// int infoDlg(char *fn, HWND hwnd) * DONE +// IsOurFile()* DONE +// Play() * DONE +///////////////////////////////////////////////////////////////////////////// +// exported symbol. Returns a pointer to the output module. +__declspec( dllexport ) In_Module * winampGetInModule2() +{ + first_init(NULL, NULL); + + if(isNT && UNICODE_FILE){ + if(VERBOSE) MessageBox(NULL ,"Unicode filename support enabled" ,"in_opus",MB_OK); + mod.version=(IN_VER | IN_UNICODE); + if(UNICODE_FILE != 3) mod.GetFileInfo=getfileinfoW; // Except For MediaMonkey + } + return &mod; +} diff --git a/infobox.c b/infobox.c new file mode 100644 index 0000000..540325f --- /dev/null +++ b/infobox.c @@ -0,0 +1,627 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * in_opus info box by Gillibert Raymond, * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include +#include +#include +#include "resource.h" +#include "infobox.h" + +extern HFONT TAGS_FONT; // To set the font +extern char USE_REPLAY_GAIN; // 0 : no, 1: Album, 2: Track. +extern char TAGS_DISPLAY_MODE, isNT; +extern char UNICODE_FILE; +extern char TARGET_LUFS; +extern char UNIFIED_DIALOG; + +int isourfile(char *fn); + +#include "utf_ansi.c" + +/*************************************** + * SHARED HELPERS * + ***************************************/ +int has_opus_ext(const char *p) +{ + p+=strlen(p); p-=4; + if(!_strnicmp(p, ".opu", 4) || !_strnicmp(--p, ".opus", 5)) { + return 1; + } else { + return 0; + } +} +int has_opus_extW(const wchar_t *p) +{ + p+=wcslen(p); p = &p[-4]; + if(!_wcsnicmp(p, L".opu", 4) || !_wcsnicmp(--p, L".opus", 5)) { + return 1; + } else { + return 0; + } +} + +char isURL(char *fn) +{ + if(fn && fn[0] == 'h' && fn[1] == 't' && fn[2] == 't' + && fn[3] == 'p' && fn[4] == ':' && fn[5] == '/' && fn[6] == '/') + return 1; + else + return 0; +} + +char isRGavailable(OggOpusFile *_of) +{ + if(_of == NULL) return 0; + if(op_get_header_gain(_of)) return 1; // Il y as un gain... + + const OpusTags *_tags=NULL; + _tags = op_tags(_of, -1); + if(_tags){ + int gain; + if(opus_tags_get_track_gain(_tags, &gain) == 0) { return 1;} + if(opus_tags_get_album_gain(_tags, &gain) == 0) { return 1;} + } + return 0; +} + +const char *TranslateOpusErr(int err) +{ + switch(err){ + case OP_FALSE : return "Fail"; + case OP_EOF : return "End of file"; + case OP_HOLE : return "Corupt page"; + case OP_EREAD : return "Read failed"; + case OP_EFAULT : return "Offline"; + case OP_EIMPL : return "Unsupported"; + case OP_EINVAL : return "Invalid param"; + case OP_ENOTFORMAT : return "Not an opus stream"; + case OP_EBADHEADER : return "Bad header"; + case OP_EVERSION : return "Bad version"; + case OP_ENOTAUDIO : return "Not audio?"; + case OP_EBADPACKET : return "Packet failed to decode"; + case OP_EBADLINK : return "Bad link"; + case OP_ENOSEEK : return "Unseekable stream"; + case OP_EBADTIMESTAMP: return "Bad timestamp"; + default: return "?"; + } +} +const wchar_t *TranslateOpusErrW(int err) +{ + switch(err){ + case OP_FALSE : return L"Fail"; + case OP_EOF : return L"End of file"; + case OP_HOLE : return L"Corupt page"; + case OP_EREAD : return L"Read failed"; + case OP_EFAULT : return L"Offline"; + case OP_EIMPL : return L"Unsupported"; + case OP_EINVAL : return L"Invalid param"; + case OP_ENOTFORMAT : return L"Not an opus stream"; + case OP_EBADHEADER : return L"Bad header"; + case OP_EVERSION : return L"Bad version"; + case OP_ENOTAUDIO : return L"Not audio?"; + case OP_EBADPACKET : return L"Packet failed to decode"; + case OP_EBADLINK : return L"Bad link"; + case OP_ENOSEEK : return L"Unseekable stream"; + case OP_EBADTIMESTAMP: return L"Bad timestamp"; + default: return L"?"; + } +} + +/*************************************** + * Infobox helpers * + ***************************************/ +static void SetTextStr(HWND hwnd, int IDC, const char *z) +{ + char *tmpstr; + wchar_t *tmpW; + if(!z || *z == '\0') return; + + if (TAGS_DISPLAY_MODE == 0){ //RAW UTF-8 + SetDlgItemText(hwnd, IDC, z); + } else if (TAGS_DISPLAY_MODE == 2 || (TAGS_DISPLAY_MODE == 3 && isNT)){ // Full Unicode + tmpW = utf8_to_utf16((char *)z); + if (tmpW && *tmpW != '\0')SetDlgItemTextW(hwnd, IDC, tmpW); + free(tmpW); + } else { // Convert to ANSI... + tmpstr = utf8_to_ansi((char *)z); + if (tmpstr && *tmpstr != '\0') SetDlgItemText(hwnd, IDC, tmpstr); + free(tmpstr); + } +} + +static void SetText(HWND hwnd, int IDC, const char *TagN, const OpusTags *_tags) +{ + const char *z; + z = opus_tags_query(_tags, TagN, 0); + + SetTextStr(hwnd, IDC, z); + +} + +static void SetTextRaw(HWND hwnd, int IDC, const char *TagN, const OpusTags *_tags) +{ + const char *z; + z = opus_tags_query(_tags, TagN, 0); + if(z && *z != '\0') SetDlgItemText(hwnd, IDC, z); +} + +static void SetTextML(HWND hwnd, int IDC, const char *TagN, const OpusTags *_tags) +{ + char *tmpstr; + const char *z; + + z = opus_tags_query(_tags, TagN, 0); + + if(z && *z != '\0'){ + tmpstr = unix2dos(z); + SetTextStr(hwnd, IDC, tmpstr); + free(tmpstr); // this one we have to free, it aint const + } +} + +static void SetFonts(HWND hwnd,HFONT fonte) +{ + if(fonte != NULL && hwnd != NULL){ + WPARAM newfont = (WPARAM)fonte; + SendDlgItemMessage(hwnd, IDC_NAME, WM_SETFONT, newfont, TRUE); + SendDlgItemMessage(hwnd, IDC_TITLE, WM_SETFONT, newfont, TRUE); + SendDlgItemMessage(hwnd, IDC_ARTIST, WM_SETFONT, newfont, TRUE); + SendDlgItemMessage(hwnd, IDC_ALBUM, WM_SETFONT, newfont, TRUE); + SendDlgItemMessage(hwnd, IDC_COMMENT, WM_SETFONT, newfont, TRUE); + SendDlgItemMessage(hwnd, IDC_GENRE, WM_SETFONT, newfont, TRUE); + SendDlgItemMessage(hwnd, IDC_COMPOSER, WM_SETFONT, newfont, TRUE); + SendDlgItemMessage(hwnd, IDC_YEAR, WM_SETFONT, newfont, TRUE); + SendDlgItemMessage(hwnd, IDC_TRACK, WM_SETFONT, newfont, TRUE); + SendDlgItemMessage(hwnd, IDC_URL, WM_SETFONT, newfont, TRUE); + SendDlgItemMessage(hwnd, IDC_PERFORMER, WM_SETFONT, newfont, TRUE); + SendDlgItemMessage(hwnd, IDC_DESCRIPTION,WM_SETFONT, newfont, TRUE); + SendDlgItemMessage(hwnd, IDC_COPYRIGHT, WM_SETFONT, newfont, TRUE); + SendDlgItemMessage(hwnd, IDC_TRACKTOTAL, WM_SETFONT, newfont, TRUE); + } +} +static const char *GetRGmode(char use_replay_gain) +{ + const char *rg_mode; + + switch(use_replay_gain){ + case 1: rg_mode="Album"; break; + case 2: rg_mode="Track"; break; + case 3: rg_mode="Auto"; break; + case 4: rg_mode="Null"; break; + default: rg_mode="Off"; + } + return rg_mode; +} +/************************************* + * Infobox Initialisation for files * + *************************************/ +static BOOL InitInfoboxInfo(HWND hwnd, const char *file) +{ + OpusServerInfo info_real, *_info=NULL; + LONGLONG filesize=0; + OggOpusFile *_tmp=NULL; + const OpusTags *_tags=NULL; + char *tmpstr, *p, *fnUTF8=NULL; + int err, length=0, tgain=0, again=0, hgain=0; + float bps=0; + int retA=-1, retT=-1, lufs_offset=0; + char *buffer =NULL; + char file_is_url; + char ret; + + buffer = malloc(258*sizeof(char)); if(!buffer) return FALSE; + + /* write file name as given by Winamp */ + if(UNICODE_FILE){ + fnUTF8 = utf16_to_utf8((wchar_t *)file); + + if(TAGS_DISPLAY_MODE == 2 || (TAGS_DISPLAY_MODE == 3 && isNT)){ + SetDlgItemTextW(hwnd, IDC_NAME, (wchar_t *)file); // UNICODE + } else if(TAGS_DISPLAY_MODE == 1){ + tmpstr=utf8_to_ansi(fnUTF8); + if(tmpstr) SetDlgItemText(hwnd, IDC_NAME, tmpstr); // ANSI + free(tmpstr); + } else if(TAGS_DISPLAY_MODE == 0){ + SetDlgItemText(hwnd, IDC_NAME, fnUTF8); // raw utf-8 dump + } + } else { // no UNICODE_FILE + SetDlgItemText(hwnd, IDC_NAME, file); // file is ANSI + } + file_is_url = isURL(UNICODE_FILE ? fnUTF8 : (char *)file); + + /* stream data and vorbis comment */ + if(file_is_url){ + int L; + _info = &info_real; + if(_info == NULL) goto FAIL; + opus_server_info_init(_info); + + p = UNICODE_FILE ? fnUTF8 : (char *)file; + L = strlen(p) - 6; + if(!_strnicmp(p+L, ">.opus", 6)){ + strcpy(buffer, p); + buffer[L]='\0'; + L=0; + } + _tmp = op_open_url(!L? buffer: p, &err,OP_GET_SERVER_INFO(_info),NULL); + + } else { // NOT an URL + _tmp = op_open_file(UNICODE_FILE? fnUTF8: file, &err); + } + + if (!_tmp){ + if(file_is_url && err == OP_ENOTFORMAT) + sprintf(buffer,"Cannot open file: \n\n%s\n\n" + "Error No. -132: Not an opus stream\n" + "Try renaming the stream adding ?.mp3 or ?.ogg at the end." + , UNICODE_FILE ? fnUTF8: file); + + else + sprintf(buffer, "Cannot open file: \n\n%s\n\n Error No. %d: %s" + , UNICODE_FILE? fnUTF8: file, err, TranslateOpusErr(err)); + + MessageBox(hwnd, buffer, "OPUS Stream Error", MB_OK); + + goto FAIL; + } + if(fnUTF8){ free(fnUTF8); fnUTF8=NULL; } + + + if(!file_is_url){ + _tags = op_tags(_tmp, -1); + if (!_tags) goto FAIL; + + filesize = op_raw_total (_tmp, -1); // In Bites + length = (int)(op_pcm_total(_tmp, -1)/48000); // File length in seconds + bps = (float)op_bitrate(_tmp, -1); // In bits per seconds + retT = opus_tags_get_track_gain (_tags, &tgain); // Track Gain + retA = opus_tags_get_album_gain (_tags, &again); // Album Gain + hgain = op_get_header_gain(_tmp); // Header gain, all in 256th dB + + sprintf(buffer, "%I64d bytes (%d ch) for %d h %d min %d s at %.1f Kbps" + , filesize, op_channel_count(_tmp, -1) ,(length/3600) + , (length/60)%60, length%60, bps/1000.F); + } else { + sprintf(buffer, "Radio Stream: %s (%d ch) at %d kbps" + , _info->server, op_channel_count(_tmp, -1) + , _info->bitrate_kbps); + } + + SetDlgItemText(hwnd, IDC_INFO, buffer); + + /* TAGS */ + + SetFonts(hwnd, TAGS_FONT); // Setting spetial font if needed + + if(!file_is_url){ // Normal TAGS + SetText (hwnd, IDC_TITLE, "TITLE", _tags); + SetText (hwnd, IDC_ARTIST, "ARTIST", _tags); + SetText (hwnd, IDC_ALBUM, "ALBUM", _tags); + SetText (hwnd, IDC_COMMENT, "COMMENT", _tags); + SetText (hwnd, IDC_GENRE, "GENRE", _tags); + SetText (hwnd, IDC_COMPOSER, "COMPOSER", _tags); + SetText (hwnd, IDC_PERFORMER, "PERFORMER", _tags); + SetText (hwnd, IDC_COPYRIGHT, "COPYRIGHT", _tags); + SetTextRaw (hwnd, IDC_YEAR, "DATE", _tags); + SetTextRaw (hwnd, IDC_TRACK, "TRACKNUMBER", _tags); + SetTextRaw (hwnd, IDC_TRACKTOTAL, "TRACKTOTAL", _tags); + SetTextRaw (hwnd, IDC_URL, "PURL", _tags); + SetTextML (hwnd, IDC_DESCRIPTION,"DESCRIPTION", _tags); //MultiLine + } else if (_info){ // RADIO mode + SetTextStr (hwnd, IDC_TITLE, _info->name); + SetTextStr (hwnd, IDC_DESCRIPTION, _info->description); + SetTextStr (hwnd, IDC_GENRE, _info->genre); + SetTextStr (hwnd, IDC_URL, _info->url); + SetTextStr (hwnd, IDC_COMMENT, _info->content_type); + SetTextStr (hwnd, IDC_COPYRIGHT, (_info->is_public==1)?"Public server" + : (_info->is_public==-1)?"": "Private server"); + opus_server_info_clear(_info); + } + + /* Small TAG at the bottom */ + if(hgain != 0 || retT == 0 || retA == 0) + lufs_offset = (23 + TARGET_LUFS)*256; + + sprintf(buffer + ,"Encoder: %s\nTrk %+.1f dB, Alb %+.1f dB, Hed %+.1f dB, (%s)" + ,file_is_url? "Not ckecked": opus_tags_query(_tags, "ENCODER", 0) + ,((float)(tgain))*(1.F/256.F) + ,((float)(again))*(1.F/256.F) + ,((float)(hgain+lufs_offset))*(1.F/256.F) + ,GetRGmode(USE_REPLAY_GAIN) ); + + SetDlgItemText(hwnd, IDC_ENCODER, buffer); + + /* END OF TAGS */ + + ret=TRUE; goto FREALL; // if we are here everything is good. + + FAIL: ret=FALSE; + + FREALL: /* Free all mem and exit */ + if(fnUTF8) free(fnUTF8); + free(buffer); + if(_tmp) op_free(_tmp); + + return ret; +} + +static INT_PTR CALLBACK InfoProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { // Big Switch de msg + + /* Initialisation */ + case WM_INITDIALOG: + SetWindowText(hwnd, isURL((char *)lParam)? "OPUS Radio Info": "OPUS File Info"); + /* init fields using our funcion */ + if (!InitInfoboxInfo(hwnd, (const char*)lParam)) + PostMessage(hwnd, WM_CLOSE, 0, 0); + return TRUE; + + /* If Destroy message is sent */ + case WM_DESTROY: break; + + /* A Command is sent */ + case WM_COMMAND: + switch (LOWORD(wParam)) { /* OK / Cancel */ + case IDOK: + /* int SUCESS = write_tags_in_file(hwnd, (const char*)lParam)); + * EndDialog(hwnd, LOWORD(wParam)); + * return SUCESS; */ + case IDCANCEL: + EndDialog(hwnd, LOWORD(wParam)); + return TRUE; + } break; + + } // END Big Switch de msg + + return 0; +} + +void DoInfoBox(HINSTANCE inst, HWND hwnd, const char *filename) +{ + int error; + + if(TAGS_DISPLAY_MODE == 2 || (TAGS_DISPLAY_MODE == 3 && isNT)){ + error = DialogBoxParamW(inst, MAKEINTRESOURCEW(IDD_INFOBOX), hwnd, InfoProc, (LONG)filename); + + if(error == 0){ + MessageBox(hwnd, "Error: cannot create Unicode Dialog Box,\n" + "Your system does not handle 'DialogBoxParamW' function\n" + "Go in wianmp.ini and set TAGS_DISPLAY_MODE=0, 1 or 3\n" + "in the [IN_OPUS] section\n\n" + "Disabling Unicode support for this session" + , "in_opus error", MB_OK); + + TAGS_DISPLAY_MODE=1; + } + } + if(TAGS_DISPLAY_MODE <= 1 || (TAGS_DISPLAY_MODE == 3 && !isNT)){ + error = DialogBoxParam(inst, MAKEINTRESOURCE(IDD_INFOBOX), hwnd, InfoProc, (LONG)filename); + } +} + + +///////////////////////////////////////////////////////////////////////////// +// This is called for every metadata item to be saved to a file and has a unicode and ansi +// version. The unicode version is the prefered version on unicode compatible Winamp clients +// though the ansi function will be called if the unicode is not present or being used on an +// older style client without internal unicode support for metadata. +// data is the metadata tag being queried which typically consists of the id3v1 fields but also includes some +// additional ones which Winamp uses for determining how to handle files or display different things to the user. +// They can consist of, but are not limited to the following (and plug-in specific tags can be handled): +// "track", "title", "artist", "album", "year", "comment", "genre", "length", +// "type", "family", "formatinformation", "gain", "bitrate", "vbr", "stereo" and more + +int winampGetExtendedFileInfo_utf8(const char *fn, const char *data, char *dest, size_t destlen) +{ + const char *z = NULL; + int err; + const OpusTags *_tags=NULL; + OggOpusFile *_of; + +// MessageBox(NULL, fn, data, MB_OK); + + if (!_stricmp(data, "type")) { + dest[0] = '0'; // audio format + return 1; + + } else if (!_stricmp(data, "family")) { +// strcpy(dest, "Ogg/Opus Audio File"); + return 0; + + } else if (!_stricmp(data, "length")) { + return 0; + + } + // opening file + _of = op_open_file(fn, &err); + if(_of && !err){ // if file opened correctly, open tags. + _tags = op_tags(_of, -1); + } else if (err != -132 || has_opus_ext(fn)) { // if the file could not be opened and is opus. + if(_of) op_free(_of); // free it in case. + if(!_stricmp(data, "title")) { + // send error message... + char *p; + if((p = strrchr(fn, '\\'))) p++; else p = (char *)fn; + + sprintf(dest, "[in_opus err %d %s] %s" + , err, TranslateOpusErr(err), p); + return 1; + } else { + return 0; + } + } + // if file opened correctly + if(!_tags) goto fail; + + // FROM HERE TAGS HAS TO BE OK! + + if (!_stricmp(data, "bitrate")) { + sprintf(dest, "%1.f", (float)op_bitrate(_of, -1)); + goto finish; + + } else if (!_stricmp(data, "formatinformation")) { + sprintf(dest, + "Size: %I64d bites\n" + "Length: %I64d s\n" + "Bitrate: %.2f kbps\n" + "Channels: %d\n" + "Header gain: %.2f dB\n" + "Input samplerate: %d Hz\n" + "Encoder: %s\n" + , op_raw_total (_of, -1) + , op_pcm_total(_of, -1)/48000 + , (float)op_bitrate(_of, -1)/1000.F + , op_channel_count(_of, -1) + , (float)(op_get_header_gain(_of)/256.F) + , op_get_input_sample_rate(_of) + , opus_tags_query(_tags, "ENCODER", 0) + ); + + goto finish; + + } else if (!_stricmp(data, "replaygain_track_gain")) { + int tgain=0; + opus_tags_get_track_gain (_tags, &tgain); + sprintf(dest, "%.2f", (float)tgain/256.f); + goto finish; + + } else if (!_stricmp(data, "replaygain_album_gain")) { + int again=0; + opus_tags_get_album_gain (_tags, &again); + sprintf(dest, "%.2f", (float)again/256.f); + goto finish; + + } else if (!_stricmp(data, "comment")) { // convert to DOS + char *dos; + z = opus_tags_query(_tags, "DESCRIPTION", 0); + dos = unix2dos(z); if(!dos) return 0; + dest[0] = '\0'; + strncat(dest, dos, destlen-1); + free(dos); + goto finish; + + } else if (!_stricmp(data, "year")) { + z = opus_tags_query(_tags, "DATE", 0); + } else if (!_stricmp(data, "track")) { + z = opus_tags_query(_tags, "TRACKNUMBER", 0); + } else if (!_stricmp(data, "disc")) { + z = opus_tags_query(_tags, "DISCNUMBER", 0); + } else { + z = opus_tags_query(_tags, data, 0); + } + if(z){ + dest[0] = '\0'; + strncat(dest, z, destlen-1); + goto finish; + } + fail: + op_free(_of); + return 0; + + finish: // sucess! + + op_free(_of); + return 1; +} + +__declspec(dllexport) int winampGetExtendedFileInfo(const char *fn, const char *data, char *dest, size_t destlen) +{ + char *dest_utf8, *dest_ansi; + int ret; + + dest_utf8=calloc(destlen, sizeof(char)); if(!dest_utf8) return 0; + + if(winampGetExtendedFileInfo_utf8(fn, data, dest_utf8, destlen)) { + dest_ansi=utf8_to_ansi(dest_utf8); if(!dest_ansi) return 0; + dest[0] = '\0'; + strncat(dest, dest_ansi, destlen-1); + free(dest_ansi); + ret = 1; + } else { + ret = 0; + } + free(dest_utf8); + return ret; +} + +__declspec(dllexport) int winampGetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *dest, size_t destlen) +{ + char *dest_utf8, *fnUTF8; + wchar_t *dest_w; + int ret; + fnUTF8 = utf16_to_utf8(fn); if(!fnUTF8) return 0; + dest_utf8 = calloc(destlen, sizeof(char)); if(!dest_utf8) return 0; + + if(winampGetExtendedFileInfo_utf8(fnUTF8, data, dest_utf8, destlen)) { + dest_w = utf8_to_utf16(dest_utf8); if(!dest_w) return 0; + dest[0] = '\0'; + wcsncat(dest, dest_w, destlen-1); + free(dest_w); + ret = 1; + } else { + ret = 0; + } + free(dest_utf8); + free(fnUTF8); + + return ret; +} +__declspec(dllexport) int winampUseUnifiedFileInfoDlg(const wchar_t *fn) +{ + int err=-132; + wchar_t *p=(wchar_t *)fn; +// MessageBox(NULL,"START","winampUseUnifiedFileInfoDlg",MB_OK); + + if(UNIFIED_DIALOG){ + p += wcslen(p); + + if (!_wcsnicmp(fn,L"http://", 7)){ + // if URL; +// MessageBox(NULL,"URL return 0","winampUseUnifiedFileInfoDlg",MB_OK); + return 0; + + } else if(has_opus_extW(fn)) { + // extension is .opus or .opu +// MessageBox(NULL,"OPUS file and return 1","winampUseUnifiedFileInfoDlg",MB_OK); + return 1; + + } else if((p[-1]=='g'&&p[-2]=='g'&&p[-3]=='o'&&p[-4]=='.')) { + // extenson is .ogg + char *fnUTF8; + OggOpusFile *_tmp; + + fnUTF8 = utf16_to_utf8(fn); if(!fnUTF8) return 0; + _tmp = op_test_file(fnUTF8, &err); + free(fnUTF8); + if(_tmp) op_free(_tmp); + + if(err == 0){ +// MessageBox(NULL,"OGG file and return 1","winampUseUnifiedFileInfoDlg",MB_OK); + return 1; + } else { +// MessageBox(NULL,"OGG file and return 0","winampUseUnifiedFileInfoDlg",MB_OK); + return 0; + } + } + + } else { + return 0; + } + return 0; +} +__declspec(dllexport) int winampSetExtendedFileInfoW(const wchar_t *fn, const char *data, const wchar_t *val) +{ + return 0; +} +__declspec(dllexport) int winampSetExtendedFileInfo(const char *fn, const char *data, const char *val) +{ + return 0; +} +__declspec(dllexport) int winampWriteExtendedFileInfo() +{ + return 0; +} diff --git a/infobox.h b/infobox.h new file mode 100644 index 0000000..ae20f25 --- /dev/null +++ b/infobox.h @@ -0,0 +1,14 @@ +// Just for this man.. + +void DoInfoBox(HINSTANCE inst, HWND hwnd, const char *filename); + +opus_int32 op_get_header_gain(OggOpusFile *_of); +opus_int32 op_get_input_sample_rate(OggOpusFile *_of); +const char *TranslateOpusErr(int err); +const wchar_t *TranslateOpusErrW(int err); + +char isURL(char *fn); +char isRGavailable(OggOpusFile *_of); + +//int has_opus_ext(const char *p); +//int has_opus_extW(const wchar_t *p); diff --git a/resample.c b/resample.c new file mode 100644 index 0000000..9c05587 --- /dev/null +++ b/resample.c @@ -0,0 +1,887 @@ +/**************************************************************************** + * Copyright (C) 2007-2008 Jean-Marc Valin * + * Copyright (C) 2008 Thorvald Natvig * + * * + * File: resample.c * + * Arbitrary resampling code * + **************************************************************************** + * Redistribution and use in source and binary forms, with or without * + * modification, are permitted provided that the following conditions are * + * met: * + * * + * 1. Redistributions of source code must retain the above copyright notice,* + * this list of conditions and the following disclaimer. * + * * + * 2. Redistributions in binary form must reproduce the above copyright * + * notice, this list of conditions and the following disclaimer in the * + * documentation and/or other materials provided with the distribution. * + * * + * 3. The name of the author may not be used to endorse or promote products * + * derived from this software without specific prior written permission. * + **************************************************************************** + * The design goals of this code are: * + * - Very fast algorithm * + * - SIMD-friendly algorithm * + * - Low memory requirement * + * - Good *perceptual* quality (and not best SNR) * + * Warning: This resampler is relatively new. Although I think I got rid of * + * all the major bugs and I don't expect the API to change anymore, there * + * may be something I've missed. So use with caution. * + * * + * This algorithm is based on this original resampling algorithm: * + * Smith, Julius O. Digital Audio Resampling Home Page * + * Center for Computer Research in Music and Acoustics (CCRMA), * + * Stanford University, 2007. * + * Web published at https://ccrma.stanford.edu/~jos/resample/. * + * * + * There is one main difference, though. This resampler uses cubic * + * interpolation instead of linear interpolation in the above paper. This * + * makes the table much smaller and makes it possible to compute that table * + * on a per-stream basis. In turn, being able to tweak the table for each * + * stream makes it possible to both reduce complexity on simple ratios * + * (e.g. 2/3), and get rid of the rounding operations in the inner loop. * + * The latter both reduces CPU time and makes the algorithm more * + * SIMD-friendly. * + ****************************************************************************/ + +#define MULT16_32_Q15(a,b) ((a)*(b)) +#define MULT16_16(a,b) ((spx_word32_t)(a)*(spx_word32_t)(b)) +#define SHR32(a,shift) (a) +#define PSHR32(a,shift) (a) +#define SATURATE32PSHR(x,shift,a) (x) +typedef float spx_word16_t; +typedef float spx_word32_t; + +#include +static void *speex_alloc(size_t size) {return calloc(size,1);} +static void *speex_realloc(void *ptr, size_t size) {return realloc(ptr, size);} +static void speex_free(void *ptr) {free(ptr);} + +#include "resample.h" + +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +/* #define IMAX(a,b) ((a) > (b) ? (a) : (b)) + * #define IMIN(a,b) ((a) < (b) ? (a) : (b)) */ + +#ifndef UINT32_MAX +#define UINT32_MAX 4294967295U +#endif + +typedef int (*resampler_basic_func)(SpeexResamplerState *, spx_uint32_t , const spx_word16_t *, spx_uint32_t *, spx_word16_t *, spx_uint32_t *); + +struct SpeexResamplerState_ { + spx_uint32_t in_rate; + spx_uint32_t out_rate; + spx_uint32_t num_rate; + spx_uint32_t den_rate; + + int quality; + spx_uint32_t nb_channels; + spx_uint32_t filt_len; + spx_uint32_t mem_alloc_size; + spx_uint32_t buffer_size; + int int_advance; + int frac_advance; + float cutoff; + spx_uint32_t oversample; + int initialised; + int started; + + /* These are per-channel */ + spx_int32_t *last_sample; + spx_uint32_t *samp_frac_num; + spx_uint32_t *magic_samples; + + spx_word16_t *mem; + spx_word16_t *sinc_table; + spx_uint32_t sinc_table_length; + resampler_basic_func resampler_ptr; + + int in_stride; + int out_stride; +} ; + +static const float kaiser8_table[36] = { + 0.99635258, 1.00000000, 0.99635258, 0.98548012, 0.96759014, 0.94302200, + 0.91223751, 0.87580811, 0.83439927, 0.78875245, 0.73966538, 0.68797126, + 0.63451750, 0.58014482, 0.52566725, 0.47185369, 0.41941150, 0.36897272, + 0.32108304, 0.27619388, 0.23465776, 0.19672670, 0.16255380, 0.13219758, + 0.10562887, 0.08273982, 0.06335451, 0.04724088, 0.03412321, 0.02369490, + 0.01563093, 0.00959968, 0.00527363, 0.00233883, 0.00050000, 0.00000000 +}; + +static const float kaiser6_table[36] = { + 0.99733006, 1.00000000, 0.99733006, 0.98935595, 0.97618418, 0.95799003, + 0.93501423, 0.90755855, 0.87598009, 0.84068475, 0.80211977, 0.76076565, + 0.71712752, 0.67172623, 0.62508937, 0.57774224, 0.53019925, 0.48295561, + 0.43647969, 0.39120616, 0.34752997, 0.30580127, 0.26632152, 0.22934058, + 0.19505503, 0.16360756, 0.13508755, 0.10953262, 0.08693120, 0.06722600, + 0.05031820, 0.03607231, 0.02432151, 0.01487334, 0.00752000, 0.00000000 +}; + +struct FuncDef { + const float *table; + int oversample; +}; + +static const struct FuncDef kaiser8_funcdef = {kaiser8_table, 32}; +#define KAISER8 (&kaiser8_funcdef) +static const struct FuncDef kaiser6_funcdef = {kaiser6_table, 32}; +#define KAISER6 (&kaiser6_funcdef) + +struct QualityMapping { + int base_length; + int oversample; + float downsample_bandwidth; + float upsample_bandwidth; + const struct FuncDef *window_func; +}; + +/* This table maps conversion quality to internal parameters. There are two + * reasons that explain why the up-sampling bandwidth is larger than the + * down-sampling bandwidth: + * 1) When up-sampling, we can assume that the spectrum is already attenuated + * close to the Nyquist rate (from an A/D or a previous resampling filter) + * 2) Any aliasing that occurs very close to the Nyquist rate will be masked + * by the sinusoids/noise just below the Nyquist rate (guaranteed only for + * up-sampling). + */ +static const struct QualityMapping quality_map[5] = { + { 8, 4, 0.830f, 0.860f, KAISER6 }, /* Q0 */ + { 16, 4, 0.850f, 0.880f, KAISER6 }, /* Q1 */ + { 32, 4, 0.882f, 0.910f, KAISER6 }, /* Q2 */ /* 82.3% cutoff ( ~60 dB stop) 6 */ + { 48, 8, 0.895f, 0.917f, KAISER8 }, /* Q3 */ /* 84.9% cutoff ( ~80 dB stop) 8 */ + { 64, 8, 0.921f, 0.940f, KAISER8 }, /* Q4 */ /* 88.7% cutoff ( ~80 dB stop) 8 */ +}; +/* 8, 24, 40, 56, 80, 104, 128, 160, 200, 256, 320 */ +static inline float compute_func(float x, const struct FuncDef *func) +{ + float y, frac; + float interp[4]; + int ind; + y = x*func->oversample; +// ind = (y>0)? (int)y: (int)(y-1.f); + ind= (int)(y + 32768.F) - 32768; + frac = (y-ind); + /* CSE with handle the repeated powers */ + interp[3] = -0.1666666667F*frac + 0.1666666667F*frac*(frac*frac); + interp[2] = frac + 0.5F*(frac*frac) - 0.5F*(frac*frac)*frac; + interp[0] = -0.3333333333F*frac + 0.5F*(frac*frac) - 0.1666666667F*frac*(frac*frac); + /* Just to make sure we don't have rounding problems */ + interp[1] = 1.f-interp[3]-interp[2]-interp[0]; + + return interp[0]*func->table[ind] + interp[1]*func->table[ind+1] + + interp[2]*func->table[ind+2] + interp[3]*func->table[ind+3]; +} + +/* The slow way of computing a sinc for the table. Should improve that some day */ +static spx_word16_t sinc(float cutoff, float x, int N, const struct FuncDef *window_func) +{ + /*fprintf (stderr, "%f ", x);*/ + float xx, xabs; + xabs=fabsf(x); + if (xabs < 1e-6) + return cutoff; + else if (xabs > .5*N) + return 0; + + /*FIXME: Can it really be any slower than this? */ + xx = x * cutoff; + return cutoff*sinf(M_PI*xx)/(M_PI*xx) * compute_func(2.F*xabs/N, window_func); +} + +/* SSE ASEMBLY FUNCTION */ +#ifdef __SSE1 +float inner_product_single(const float *a, const float *b, unsigned int len); +char HAVE_SSE; +#endif + +static int resampler_basic_direct_single(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + int N, out_sample, last_sample, j; + + spx_uint32_t samp_frac_num; + spx_word16_t *sinc_table; + int out_stride, int_advance, frac_advance; + spx_uint32_t den_rate; + spx_word32_t sum; + const spx_word16_t *sinct; + const spx_word16_t *iptr; + + N = st->filt_len; + out_sample = 0; + last_sample = st->last_sample[channel_index]; + samp_frac_num = st->samp_frac_num[channel_index]; + sinc_table = st->sinc_table; + out_stride = st->out_stride; + int_advance = st->int_advance; + frac_advance = st->frac_advance; + den_rate = st->den_rate; + + + while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len)) + { + sinct = &sinc_table[samp_frac_num*N]; + iptr = &in[last_sample]; + + #ifdef __SSE1 + if(HAVE_SSE){ + sum = inner_product_single(sinct, iptr, N); + } else + #endif + { + sum = 0; + for(j=0; j= den_rate) + { + samp_frac_num -= den_rate; + last_sample++; + } + } + + st->last_sample[channel_index] = last_sample; + st->samp_frac_num[channel_index] = samp_frac_num; + + return out_sample; +} + +static int multiply_frac(spx_uint32_t *result, spx_uint32_t value, spx_uint32_t num, spx_uint32_t den) +{ + spx_uint32_t major = value / den; + spx_uint32_t remain = value % den; + /* TODO: Could use 64 bits operation to check for overflow. But only guaranteed in C99+ */ + if (remain > UINT32_MAX / num || major > UINT32_MAX / num + || major * num > UINT32_MAX - remain * num / den) + return RESAMPLER_ERR_OVERFLOW; + *result = remain * num / den + major * num; + return RESAMPLER_ERR_SUCCESS; +} + +static int update_filter(SpeexResamplerState *st) +{ + spx_uint32_t old_length = st->filt_len; + spx_uint32_t old_alloc_size = st->mem_alloc_size; + spx_uint32_t min_sinc_table_length; + spx_uint32_t min_alloc_size; + + spx_int32_t i, j; + + st->int_advance = st->num_rate/st->den_rate; + st->frac_advance = st->num_rate%st->den_rate; + st->oversample = quality_map[st->quality].oversample; + st->filt_len = quality_map[st->quality].base_length; + + if (st->num_rate > st->den_rate) + { + /* down-sampling */ + st->cutoff = quality_map[st->quality].downsample_bandwidth * st->den_rate / st->num_rate; + if (multiply_frac(&st->filt_len,st->filt_len,st->num_rate,st->den_rate) != RESAMPLER_ERR_SUCCESS) + goto fail; + /* Round up to make sure we have a multiple of 8 for SSE */ + st->filt_len = ((st->filt_len-1)&(~0x7))+8; + if (2*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (4*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (8*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (16*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (st->oversample < 1) + st->oversample = 1; + } else { + /* up-sampling */ + st->cutoff = quality_map[st->quality].upsample_bandwidth; + } + + if (INT_MAX/sizeof(spx_word16_t)/st->den_rate < st->filt_len) + goto fail; + + min_sinc_table_length = st->filt_len*st->den_rate; + + if (st->sinc_table_length < min_sinc_table_length) + { + spx_word16_t *sinc_table = speex_realloc(st->sinc_table,min_sinc_table_length*sizeof(spx_word16_t)); + if (!sinc_table) + goto fail; + + st->sinc_table = sinc_table; + st->sinc_table_length = min_sinc_table_length; + } + for (i=0;i<(spx_int32_t)st->den_rate;i++) + for (j=0;j<(spx_int32_t)st->filt_len;j++) + st->sinc_table[i*st->filt_len+j] = sinc(st->cutoff,((j-(spx_int32_t)st->filt_len/2+1) + - ((float)i)/st->den_rate), st->filt_len + , quality_map[st->quality].window_func); + + st->resampler_ptr = resampler_basic_direct_single; + + /*fprintf (stderr, "resampler uses direct sinc table and normalised cutoff %f\n", cutoff);*/ + + /* Here's the place where we update the filter memory to take into account + the change in filter length. It's probably the messiest part of the code + due to handling of lots of corner cases. */ + + /* Adding buffer_size to filt_len won't overflow here because filt_len + could be multiplied by sizeof(spx_word16_t) above. */ + min_alloc_size = st->filt_len-1 + st->buffer_size; + if (min_alloc_size > st->mem_alloc_size) + { + spx_word16_t *mem; + if (INT_MAX/sizeof(spx_word16_t)/st->nb_channels < min_alloc_size) + goto fail; + else if (!(mem = (spx_word16_t*)speex_realloc(st->mem, st->nb_channels*min_alloc_size * sizeof(*mem)))) + goto fail; + + st->mem = mem; + st->mem_alloc_size = min_alloc_size; + } + if (!st->started) + { + spx_uint32_t i; + for (i=0;inb_channels*st->mem_alloc_size;i++) + st->mem[i] = 0; + /*speex_warning("reinit filter");*/ + } else if (st->filt_len > old_length) + { + spx_uint32_t i; + /* Increase the filter length */ + /*speex_warning("increase filter size");*/ + for (i=st->nb_channels;i--;) + { + spx_uint32_t j; + spx_uint32_t olen = old_length; + /*if (st->magic_samples[i])*/ + { + /* Try and remove the magic samples as if nothing had happened */ + + /* FIXME: This is wrong but for now we need it to avoid going over the array bounds */ + olen = old_length + 2*st->magic_samples[i]; + for (j=old_length-1+st->magic_samples[i];j--;) + st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]] = st->mem[i*old_alloc_size+j]; + for (j=0;jmagic_samples[i];j++) + st->mem[i*st->mem_alloc_size+j] = 0; + st->magic_samples[i] = 0; + } + if (st->filt_len > olen) + { + /* If the new filter length is still bigger than the "augmented" length */ + /* Copy data going backward */ + for (j=0;jmem[i*st->mem_alloc_size+(st->filt_len-2-j)] = st->mem[i*st->mem_alloc_size+(olen-2-j)]; + /* Then put zeros for lack of anything better */ + for (;jfilt_len-1;j++) + st->mem[i*st->mem_alloc_size+(st->filt_len-2-j)] = 0; + /* Adjust last_sample */ + st->last_sample[i] += (st->filt_len - olen)/2; + } else { + /* Put back some of the magic! */ + st->magic_samples[i] = (olen - st->filt_len)/2; + for (j=0;jfilt_len-1+st->magic_samples[i];j++) + st->mem[i*st->mem_alloc_size+j] = st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]]; + } + } + } else if (st->filt_len < old_length) + { + spx_uint32_t i; + /* Reduce filter length, this a bit tricky. We need to store some of the memory as "magic" + samples so they can be used directly as input the next time(s) */ + for (i=0;inb_channels;i++) + { + spx_uint32_t j; + spx_uint32_t old_magic = st->magic_samples[i]; + st->magic_samples[i] = (old_length - st->filt_len)/2; + /* We must copy some of the memory that's no longer used */ + /* Copy data going backward */ + for (j=0;jfilt_len-1+st->magic_samples[i]+old_magic;j++) + st->mem[i*st->mem_alloc_size+j] = st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]]; + st->magic_samples[i] += old_magic; + } + } + return RESAMPLER_ERR_SUCCESS; + +fail: + st->resampler_ptr = NULL; + /* st->mem may still contain consumed input samples for the filter. + Restore filt_len so that filt_len - 1 still points to the position after + the last of these samples. */ + st->filt_len = old_length; +// MessageBoxA(NULL, "RESAMPLER_ERR_ALLOC_FAILED", "in_OPUS_resampler", 0); + + return RESAMPLER_ERR_ALLOC_FAILED; + +} // END update_filter(SpeexResamplerState *st) + + +SpeexResamplerState *speex_resampler_init(spx_uint32_t nb_channels, spx_uint32_t in_rate, spx_uint32_t out_rate, int quality, int *err) +{ + return speex_resampler_init_frac(nb_channels, in_rate, out_rate, in_rate, out_rate, quality, err); +} + +SpeexResamplerState *speex_resampler_init_frac(spx_uint32_t nb_channels + , spx_uint32_t ratio_num + , spx_uint32_t ratio_den + , spx_uint32_t in_rate + , spx_uint32_t out_rate + , int quality + , int *err) +{ + SpeexResamplerState *st; + int filter_err; + #ifdef __SSE1 + __builtin_cpu_init(); + HAVE_SSE=__builtin_cpu_supports("sse"); + #endif +// if(HAVE_SSE)MessageBoxA(NULL, "SSE", "We have SSE", 0); + + if (nb_channels == 0 + || ratio_num == 0 + || ratio_den == 0 + || quality > SPEEX_RESAMPLER_QUALITY_MAX + || quality < SPEEX_RESAMPLER_QUALITY_MIN) + { + if (err) + *err = RESAMPLER_ERR_INVALID_ARG; + return NULL; + } + st = speex_alloc(sizeof(SpeexResamplerState)); + if (!st) + { + if (err) + *err = RESAMPLER_ERR_ALLOC_FAILED; + return NULL; + } + st->initialised = 0; + st->started = 0; + st->in_rate = 0; + st->out_rate = 0; + st->num_rate = 0; + st->den_rate = 0; + st->quality = -1; + st->sinc_table_length = 0; + st->mem_alloc_size = 0; + st->filt_len = 0; + st->mem = 0; + st->resampler_ptr = 0; + + st->cutoff = 1.f; + st->nb_channels = nb_channels; + st->in_stride = 1; + st->out_stride = 1; + + st->buffer_size = 160; + + /* Per channel data */ + if (!(st->last_sample = speex_alloc(nb_channels*sizeof(spx_int32_t)))) + goto fail; + if (!(st->magic_samples = speex_alloc(nb_channels*sizeof(spx_uint32_t)))) + goto fail; + if (!(st->samp_frac_num = speex_alloc(nb_channels*sizeof(spx_uint32_t)))) + goto fail; + + speex_resampler_set_quality(st, quality); + speex_resampler_set_rate_frac(st, ratio_num, ratio_den, in_rate, out_rate); + + filter_err = update_filter(st); + if (filter_err == RESAMPLER_ERR_SUCCESS) + { + st->initialised = 1; + } else { + speex_resampler_destroy(st); + st = NULL; + } + if (err) + *err = filter_err; + + return st; + +fail: + if (err) + *err = RESAMPLER_ERR_ALLOC_FAILED; + speex_resampler_destroy(st); + return NULL; +} + +void speex_resampler_destroy(SpeexResamplerState *st) +{ + speex_free(st->mem); + speex_free(st->sinc_table); + speex_free(st->last_sample); + speex_free(st->magic_samples); + speex_free(st->samp_frac_num); + speex_free(st); +} + +static int speex_resampler_process_native( + SpeexResamplerState *__restrict st, spx_uint32_t channel_index, + spx_uint32_t *in_len, + spx_word16_t *out, spx_uint32_t *out_len) +{ + int j=0; + const int N = st->filt_len; + int out_sample = 0; + spx_word16_t *mem = st->mem + channel_index * st->mem_alloc_size; + spx_uint32_t ilen; + + st->started = 1; + + /* Call the right resampler through the function ptr */ + out_sample = st->resampler_ptr(st, channel_index, mem, in_len, out, out_len); + + if (st->last_sample[channel_index] < (spx_int32_t)*in_len) + *in_len = st->last_sample[channel_index]; + *out_len = out_sample; + st->last_sample[channel_index] -= *in_len; + + ilen = *in_len; + + for(j=0;jmagic_samples[channel_index]; + spx_word16_t *mem = st->mem + channel_index * st->mem_alloc_size; + const int N = st->filt_len; + + speex_resampler_process_native(st, channel_index, &tmp_in_len, *out, &out_len); + + st->magic_samples[channel_index] -= tmp_in_len; + + /* If we couldn't process all "magic" input samples, save the rest for next time */ + if (st->magic_samples[channel_index]) + { + spx_uint32_t i; + for (i=0;imagic_samples[channel_index];i++) + mem[N-1+i]=mem[N-1+i+tmp_in_len]; + } + *out += out_len*st->out_stride; + return out_len; +} + +static inline int speex_resampler_process_float( + SpeexResamplerState *__restrict st, spx_uint32_t channel_index, + const float *__restrict in, spx_uint32_t *in_len, + float *out, spx_uint32_t *out_len) +{ + spx_uint32_t j; + spx_uint32_t ilen = *in_len; + spx_uint32_t olen = *out_len; + spx_word16_t *x = st->mem + channel_index * st->mem_alloc_size; + const int filt_offs = st->filt_len - 1; + const spx_uint32_t xlen = st->mem_alloc_size - filt_offs; + const int istride = st->in_stride; + + if(st->resampler_ptr == NULL) return RESAMPLER_ERR_ALLOC_FAILED; + + if (st->magic_samples[channel_index]) + olen -= speex_resampler_magic(st, channel_index, &out, olen); + if (! st->magic_samples[channel_index]) { + while (ilen && olen) { + spx_uint32_t ichunk = (ilen > xlen) ? xlen : ilen; + spx_uint32_t ochunk = olen; + + if (in) { + for(j=0;jout_stride; + if (in) + in += ichunk * istride; + } + } + *in_len -= ilen; + *out_len -= olen; + + return RESAMPLER_ERR_SUCCESS; +} + +int speex_resampler_process_interleaved_float( + SpeexResamplerState *__restrict st, + const float *__restrict in, spx_uint32_t *in_len, + float *__restrict out, spx_uint32_t *out_len) +{ + spx_uint32_t i; + int istride_save, ostride_save; + spx_uint32_t bak_out_len = *out_len; + spx_uint32_t bak_in_len = *in_len; + + istride_save = st->in_stride; + ostride_save = st->out_stride; + st->in_stride = st->out_stride = st->nb_channels; + + if (in == NULL) return RESAMPLER_ERR_INVALID_ARG; + if(st->resampler_ptr == NULL) return RESAMPLER_ERR_ALLOC_FAILED; + + for (i=0 ; i < st->nb_channels; i++) + { + *out_len = bak_out_len; + *in_len = bak_in_len; + speex_resampler_process_float(st, i, in+i, in_len, out+i, out_len); + } + st->in_stride = istride_save; + st->out_stride = ostride_save; + + return RESAMPLER_ERR_SUCCESS; +} + +int speex_resampler_set_rate(SpeexResamplerState *st, spx_uint32_t in_rate, spx_uint32_t out_rate) +{ + return speex_resampler_set_rate_frac(st, in_rate, out_rate, in_rate, out_rate); +} + +void speex_resampler_get_rate(SpeexResamplerState *st, spx_uint32_t *in_rate, spx_uint32_t *out_rate) +{ + *in_rate = st->in_rate; + *out_rate = st->out_rate; +} + +static inline spx_uint32_t compute_gcd(spx_uint32_t a, spx_uint32_t b) +{ + while (b != 0) + { + spx_uint32_t temp = a; + + a = b; + b = temp % b; + } + return a; +} +int speex_resampler_set_rate_frac(SpeexResamplerState *st, spx_uint32_t ratio_num, spx_uint32_t ratio_den, spx_uint32_t in_rate, spx_uint32_t out_rate) +{ + spx_uint32_t fact; + spx_uint32_t old_den; + spx_uint32_t i; + + if (ratio_num == 0 || ratio_den == 0) + return RESAMPLER_ERR_INVALID_ARG; + + if (st->in_rate == in_rate && st->out_rate == out_rate && st->num_rate == ratio_num && st->den_rate == ratio_den) + return RESAMPLER_ERR_SUCCESS; + + old_den = st->den_rate; + st->in_rate = in_rate; + st->out_rate = out_rate; + st->num_rate = ratio_num; + st->den_rate = ratio_den; + + fact = compute_gcd(st->num_rate, st->den_rate); + + st->num_rate /= fact; + st->den_rate /= fact; + + if (old_den > 0) + { + for (i=0;inb_channels;i++) + { + if (multiply_frac(&st->samp_frac_num[i],st->samp_frac_num[i],st->den_rate,old_den) != RESAMPLER_ERR_SUCCESS) + return RESAMPLER_ERR_OVERFLOW; + /* Safety net */ + if (st->samp_frac_num[i] >= st->den_rate) + st->samp_frac_num[i] = st->den_rate-1; + } + } + + if (st->initialised) + return update_filter(st); + return RESAMPLER_ERR_SUCCESS; +} + +void speex_resampler_get_ratio(SpeexResamplerState *st, spx_uint32_t *ratio_num, spx_uint32_t *ratio_den) +{ + *ratio_num = st->num_rate; + *ratio_den = st->den_rate; +} + +int speex_resampler_set_quality(SpeexResamplerState *st, int quality) +{ + if (quality > SPEEX_RESAMPLER_QUALITY_MAX || quality < SPEEX_RESAMPLER_QUALITY_MIN) + return RESAMPLER_ERR_INVALID_ARG; + if (st->quality == quality) + return RESAMPLER_ERR_SUCCESS; + st->quality = quality; + if (st->initialised) + return update_filter(st); + return RESAMPLER_ERR_SUCCESS; +} +void speex_resampler_get_quality(SpeexResamplerState *st, int *quality) +{ + *quality = st->quality; +} + +void speex_resampler_set_input_stride(SpeexResamplerState *st, spx_uint32_t stride) +{ + st->in_stride = stride; +} + +void speex_resampler_get_input_stride(SpeexResamplerState *st, spx_uint32_t *stride) +{ + *stride = st->in_stride; +} + +void speex_resampler_set_output_stride(SpeexResamplerState *st, spx_uint32_t stride) +{ + st->out_stride = stride; +} + +void speex_resampler_get_output_stride(SpeexResamplerState *st, spx_uint32_t *stride) +{ + *stride = st->out_stride; +} + +int speex_resampler_get_input_latency(SpeexResamplerState *st) +{ + return st->filt_len / 2; +} + +int speex_resampler_get_output_latency(SpeexResamplerState *st) +{ + return ((st->filt_len / 2) * st->den_rate + (st->num_rate >> 1)) / st->num_rate; +} + +int speex_resampler_skip_zeros(SpeexResamplerState *st) +{ + spx_uint32_t i; + for (i=0;inb_channels;i++) + st->last_sample[i] = st->filt_len/2; + return RESAMPLER_ERR_SUCCESS; +} + +int speex_resampler_reset_mem(SpeexResamplerState *st) +{ + spx_uint32_t i; + for (i=0;inb_channels;i++) + { + st->last_sample[i] = 0; + st->magic_samples[i] = 0; + st->samp_frac_num[i] = 0; + } + for (i=0;inb_channels*(st->filt_len-1);i++) + st->mem[i] = 0; + return RESAMPLER_ERR_SUCCESS; +} + +const char *speex_resampler_strerror(int err) +{ + switch (err) + { + case RESAMPLER_ERR_SUCCESS: + return "Success."; + case RESAMPLER_ERR_ALLOC_FAILED: + return "Memory allocation failed."; + case RESAMPLER_ERR_BAD_STATE: + return "Bad resampler state."; + case RESAMPLER_ERR_INVALID_ARG: + return "Invalid argument."; + case RESAMPLER_ERR_PTR_OVERLAP: + return "Input and output buffers overlap."; + default: + return "Unknown error. Bad error code or strange version mismatch."; + } +} + + +void float2int_dither(void *__restrict _dst, const float *__restrict _src ,int _nsamples, char bps, char use_dithering) +{ + int i, si, silent; + static unsigned short seed, tmpseed; + static unsigned mute=16384; + float Gain, r, s; + union{ + int integer; + char byte[3]; + } int24b; + /* Set Correct Gain */ + if (bps==32) Gain=OP_GAIN32; + else if(bps==24) Gain=OP_GAIN24; + else if(bps== 8) Gain=OP_GAIN8; + else Gain=OP_GAIN16; + + for(i=0; i<_nsamples; i++){ + s=_src[i]; + s*=Gain; + si=lrintf(s); + + if(use_dithering){ + if(si == 0) silent=1; + else silent=0; + + if(mute<8192){ /* ~200ms */ + seed=RANDG(seed); + tmpseed=RANDG(seed); + r = ((int)tmpseed-(int)seed)*OP_PRNG_GAINS; + s+=r; + } + + if(bps==16) { + short *dst16=(short *)_dst; + dst16[i]=(short)WORD2INT16(s); + + } else if(bps==8) { + unsigned char *dst8=(unsigned char *)_dst; + dst8[i]=0x80^WORD2INT8(s); + + } else if(bps==24){ + char *buf_=(char *)_dst; + int24b.integer=WORD2INT24(s); + buf_[3*i+0] = int24b.byte[0]; + buf_[3*i+1] = int24b.byte[1]; + buf_[3*i+2] = int24b.byte[2]; + + } else if (bps==32) { + int *buf32=(int *)_dst; + buf32[i]=WORD2INT32(s); //32b mode + } + if(!silent)mute=0; + else mute++; + } else { // No dithering... + if(bps==16) { + short *dst16=(short *)_dst; + dst16[i]=(short)OP_CLAMP(-32768,si,+32767); + + } else if(bps==8) { + unsigned char *dst8=(unsigned char *)_dst; + dst8[i]=0x80^OP_CLAMP(-128,si,+127); + + } else if(bps==24){ + char *buf_=(char *)_dst; + int24b.integer=OP_CLAMP(-8388608,si,+8388607); + buf_[3*i+0] = int24b.byte[0]; + buf_[3*i+1] = int24b.byte[1]; + buf_[3*i+2] = int24b.byte[2]; + + } else if (bps==32) { + int *buf32=(int *)_dst; + buf32[i]=WORD2INT32(s); //32b mode + } + } // end if dithering/else + } + mute=MIN(mute, 16384); +} + +inline double floorz(double n) +{ + long long i=(long long)n; + return i>=0? i : i-1; +} diff --git a/resample.h b/resample.h new file mode 100644 index 0000000..7aa48a1 --- /dev/null +++ b/resample.h @@ -0,0 +1,258 @@ +/* Copyright (C) 2007 Jean-Marc Valin + * + * File: speex_resampler.h + * Resampling code + * + * The design goals of this code are: + * - Very fast algorithm + * - Low memory requirement + * - Good *perceptual* quality (and not best SNR) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + */ + +#ifndef SPEEX_RESAMPLER_H + +#define SPEEX_RESAMPLER_H + +#define spx_int16_t short +#define spx_int32_t int +#define spx_uint16_t unsigned short +#define spx_uint32_t unsigned int + +#define WORD2INT8U(x) ((x) < 0.5f ? 0 : \ + ((x) > 254.5f ? 255 : lrintf(x))) +#define WORD2INT8(x) ((x) < -127.5f ? -128 : \ + ((x) > 126.5f ? 127 : lrintf(x))) +#define WORD2INT16(x) ((x) < -32767.5f ? -32768 : \ + ((x) > 32766.5f ? 32767 : (short)lrintf(x))) +#define WORD2INT24(x) ((x) < -8388607.5f ? -8388608 : \ + ((x) > 8388606.5 ? 8388607 : lrintf(x))) +#define WORD2INT32(x) ((x) < -2147483647.5f ? -2147483648 : \ + ((x) > 2147483646.5f ? 2147483647 : lrintf(x))) + +#define OP_GAIN8 (126.F) +#define OP_GAIN16 (32766.F) +#define OP_GAIN24 (8388606.F) +#define OP_GAIN32 (2147483646.F) +#define OP_PRNG_GAINS (1.0F/0xFFFF) +#define RANDG(seed) (1103515245 * (seed) + 12345) +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#define OP_CLAMP(_l,_x,_h) ((_x)<(_l)?(_l):((_x)>(_h)?(_h):(_x))) + +#ifdef __cplusplus +extern "C" { +#endif + +#define SPEEX_RESAMPLER_QUALITY_MAX 4 +#define SPEEX_RESAMPLER_QUALITY_MIN 0 +#define SPEEX_RESAMPLER_QUALITY_DEFAULT 3 +#define SPEEX_RESAMPLER_QUALITY_VOIP 2 +#define SPEEX_RESAMPLER_QUALITY_DESKTOP 4 + +enum { + RESAMPLER_ERR_SUCCESS = 0, + RESAMPLER_ERR_ALLOC_FAILED = 1, + RESAMPLER_ERR_BAD_STATE = 2, + RESAMPLER_ERR_INVALID_ARG = 3, + RESAMPLER_ERR_PTR_OVERLAP = 4, + RESAMPLER_ERR_OVERFLOW = 5, + + RESAMPLER_ERR_MAX_ERROR +}; + +struct SpeexResamplerState_; +typedef struct SpeexResamplerState_ SpeexResamplerState; + +/** Create a new resampler with integer input and output rates. + * @param nb_channels Number of channels to be processed + * @param in_rate Input sampling rate (integer number of Hz). + * @param out_rate Output sampling rate (integer number of Hz). + * @param quality Resampling quality between 0 and 10, where 0 has poor quality + * and 10 has very high quality. + * @return Newly created resampler state + * @retval NULL Error: not enough memory + */ +SpeexResamplerState *speex_resampler_init(spx_uint32_t nb_channels, + spx_uint32_t in_rate, + spx_uint32_t out_rate, + int quality, + int *err); + +/** Create a new resampler with fractional input/output rates. The sampling + * rate ratio is an arbitrary rational number with both the numerator and + * denominator being 32-bit integers. + * @param nb_channels Number of channels to be processed + * @param ratio_num Numerator of the sampling rate ratio + * @param ratio_den Denominator of the sampling rate ratio + * @param in_rate Input sampling rate rounded to the nearest integer (in Hz). + * @param out_rate Output sampling rate rounded to the nearest integer (in Hz). + * @param quality Resampling quality between 0 and 10, where 0 has poor quality + * and 10 has very high quality. + * @return Newly created resampler state + * @retval NULL Error: not enough memory + */ +SpeexResamplerState *speex_resampler_init_frac(spx_uint32_t nb_channels, + spx_uint32_t ratio_num, + spx_uint32_t ratio_den, + spx_uint32_t in_rate, + spx_uint32_t out_rate, + int quality, + int *err); + +/** Destroy a resampler state. + * @param st Resampler state + */ +void speex_resampler_destroy(SpeexResamplerState *st); + +/** Resample an interleaved float array. The input and output buffers must *not* overlap. + * @param st Resampler state + * @param in Input buffer + * @param in_len Number of input samples in the input buffer. Returns the number + * of samples processed. This is all per-channel. + * @param out Output buffer + * @param out_len Size of the output buffer. Returns the number of samples written. + * This is all per-channel. + */ +int speex_resampler_process_interleaved_float(SpeexResamplerState *__restrict st, + const float *__restrict in, + spx_uint32_t *in_len, + float *__restrict out, + spx_uint32_t *out_len); + +/** Set (change) the input/output sampling rates (integer value). + * @param st Resampler state + * @param in_rate Input sampling rate (integer number of Hz). + * @param out_rate Output sampling rate (integer number of Hz). + */ +int speex_resampler_set_rate(SpeexResamplerState *st, + spx_uint32_t in_rate, + spx_uint32_t out_rate); + +/** Get the current input/output sampling rates (integer value). + * @param st Resampler state + * @param in_rate Input sampling rate (integer number of Hz) copied. + * @param out_rate Output sampling rate (integer number of Hz) copied. + */ +void speex_resampler_get_rate(SpeexResamplerState *st, + spx_uint32_t *in_rate, + spx_uint32_t *out_rate); + +/** Set (change) the input/output sampling rates and resampling ratio + * (fractional values in Hz supported). + * @param st Resampler state + * @param ratio_num Numerator of the sampling rate ratio + * @param ratio_den Denominator of the sampling rate ratio + * @param in_rate Input sampling rate rounded to the nearest integer (in Hz). + * @param out_rate Output sampling rate rounded to the nearest integer (in Hz). + */ +int speex_resampler_set_rate_frac(SpeexResamplerState *st, + spx_uint32_t ratio_num, + spx_uint32_t ratio_den, + spx_uint32_t in_rate, + spx_uint32_t out_rate); + +/** Get the current resampling ratio. This will be reduced to the least + * common denominator. + * @param st Resampler state + * @param ratio_num Numerator of the sampling rate ratio copied + * @param ratio_den Denominator of the sampling rate ratio copied + */ +void speex_resampler_get_ratio(SpeexResamplerState *st, + spx_uint32_t *ratio_num, + spx_uint32_t *ratio_den); + +/** Set (change) the conversion quality. + * @param st Resampler state + * @param quality Resampling quality between 0 and 10, where 0 has poor + * quality and 10 has very high quality. + */ +int speex_resampler_set_quality(SpeexResamplerState *st, + int quality); + +/** Get the conversion quality. + * @param st Resampler state + * @param quality Resampling quality between 0 and 10, where 0 has poor + * quality and 10 has very high quality. + */ +void speex_resampler_get_quality(SpeexResamplerState *st, + int *quality); + +/** Set (change) the input stride. + * @param st Resampler state + * @param stride Input stride + */ +void speex_resampler_set_input_stride(SpeexResamplerState *st, + spx_uint32_t stride); + +/** Get the input stride. + * @param st Resampler state + * @param stride Input stride copied + */ +void speex_resampler_get_input_stride(SpeexResamplerState *st, + spx_uint32_t *stride); + +/** Set (change) the output stride. + * @param st Resampler state + * @param stride Output stride + */ +void speex_resampler_set_output_stride(SpeexResamplerState *st, + spx_uint32_t stride); + +/** Get the output stride. + * @param st Resampler state copied + * @param stride Output stride + */ +void speex_resampler_get_output_stride(SpeexResamplerState *st, + spx_uint32_t *stride); + +/** Get the latency introduced by the resampler measured in input samples. + * @param st Resampler state + */ +int speex_resampler_get_input_latency(SpeexResamplerState *st); + +/** Get the latency introduced by the resampler measured in output samples. + * @param st Resampler state + */ +int speex_resampler_get_output_latency(SpeexResamplerState *st); + +/** Make sure that the first samples to go out of the resamplers don't have + * leading zeros. This is only useful before starting to use a newly created + * resampler. It is recommended to use that when resampling an audio file, as + * it will generate a file with the same length. For real-time processing, + * it is probably easier not to use this call (so that the output duration + * is the same for the first frame). + * @param st Resampler state + */ +int speex_resampler_skip_zeros(SpeexResamplerState *st); + +/** Reset a resampler so a new (unrelated) stream can be processed. + * @param st Resampler state + */ +int speex_resampler_reset_mem(SpeexResamplerState *st); + +/** Returns the English meaning for an error code + * @param err Error code + * @return English string + */ +const char *speex_resampler_strerror(int err); + +void float2int_dither(void *__restrict__ _dst, const float *__restrict__ _src ,int _nsamples, char bps, char use_dithering); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/resource.h b/resource.h new file mode 100644 index 0000000..6abef3f --- /dev/null +++ b/resource.h @@ -0,0 +1,43 @@ +// Used by resource.rc +// +#define IDC_RESET 3 +#define IDD_CONFIG 101 +#define IDD_INFOBOX 105 + +#define IDC_ENABLE 2000 +#define IDC_ALBUM 2001 +#define IDC_LIMITER 2002 +#define IDC_COMMENT 2002 +#define IDC_PREAMP 2003 +#define IDC_YEAR 2003 +#define IDC_PA 2004 +#define IDC_TRACK 2004 +#define IDC_DITHER 2005 +#define IDC_DITHERRG 2006 +#define IDC_TO 2008 +#define IDC_SHAPE 2009 +#define IDC_TABS 2009 +#define IDC_TITLE 2010 +#define IDC_TAGZ_HELP 2011 +#define IDC_ARTIST 2011 +#define IDC_TAGZ_DEFAULT 2012 +#define IDC_SEP 2013 +#define IDC_NAME 2014 +#define IDC_INFO 2015 +#define IDC_GENRE 2017 +#define IDC_REMOVE 2020 +#define IDC_UPDATE 2021 +#define IDC_ID3V1 2030 +#define IDC_RESERVE 2032 +#define IDC_BPS 2036 +#define IDC_ERRORS 2037 +#define IDC_COMPOSER 2038 +#define IDC_DESCRIPTION 2039 +#define IDC_EXINFO 2040 +#define IDC_URL 2041 +#define IDC_ENCODER 2042 +#define IDC_PERFORMER 2043 +#define IDC_COPYRIGHT 2044 +#define IDC_TRACKTOTAL 2045 + +// Next default values for new objects diff --git a/resource.rc b/resource.rc new file mode 100644 index 0000000..4757ef6 --- /dev/null +++ b/resource.rc @@ -0,0 +1,89 @@ + +#include "resource.h" +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +// +// File Info Dialog +// + +IDD_INFOBOX DIALOG DISCARDABLE 0, 0, 211, 257 + +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +FONT 8, "MS Sans Serif" +BEGIN + + RTEXT "Fi&le",IDC_STATIC,1, 5, 12, 8 + EDITTEXT IDC_NAME,17, 3, 192, 12,ES_AUTOHSCROLL | ES_READONLY + + LTEXT "",IDC_INFO, 3, 20, 205, 10, ES_AUTOHSCROLL | ES_READONLY + + GROUPBOX " Tag ",IDC_STATIC,1, 30, 209, 210 + RTEXT "&Title",IDC_STATIC,8, 42, 31, 8 + EDITTEXT IDC_TITLE,43,40,164,12,ES_AUTOHSCROLL | ES_READONLY + RTEXT "&Artist",IDC_STATIC,8,56,31,8 + EDITTEXT IDC_ARTIST,43,54,164,12,ES_AUTOHSCROLL | ES_READONLY + RTEXT "Albu&m",IDC_STATIC,8,70,31,8 + EDITTEXT IDC_ALBUM,43,68,164,12,ES_AUTOHSCROLL | ES_READONLY + RTEXT "&Comment",IDC_STATIC,8,84,31,8 + EDITTEXT IDC_COMMENT,43,82,164,12,ES_AUTOHSCROLL | ES_READONLY + + RTEXT "&Date",IDC_STATIC,8,98,31,8 + EDITTEXT IDC_YEAR,43, 96, 64, 12,ES_AUTOHSCROLL | ES_READONLY + RTEXT "Track &N°",IDC_STATIC,111, 98, 32, 8 + EDITTEXT IDC_TRACK,146, 96, 24, 12,ES_AUTOHSCROLL | ES_READONLY + RTEXT "o&f",IDC_STATIC,172, 98, 8, 8 + EDITTEXT IDC_TRACKTOTAL,183, 96, 24, 12,ES_AUTOHSCROLL | ES_READONLY + + RTEXT "&Genre",IDC_STATIC,8, 112, 31, 8 + EDITTEXT IDC_GENRE,43, 110, 64, 12,ES_AUTOHSCROLL | ES_READONLY + RTEXT "Cop&yright",IDC_STATIC,109, 112, 32, 8 + EDITTEXT IDC_COPYRIGHT,146, 110, 61, 12,ES_AUTOHSCROLL | ES_READONLY + + RTEXT "C&omposer",IDC_STATIC,8,126,31,8 + EDITTEXT IDC_COMPOSER,43,124,164,12,ES_AUTOHSCROLL | ES_READONLY + RTEXT "&Performer",IDC_STATIC,8,140,31,8 + EDITTEXT IDC_PERFORMER,43,138,164,12,ES_AUTOHSCROLL | ES_READONLY + RTEXT "&URL",IDC_STATIC, 55, 154, 17, 8 + EDITTEXT IDC_URL,75, 152, 132, 12,ES_AUTOHSCROLL | ES_READONLY + + LTEXT "D&escription",IDC_STATIC,5, 157, 52, 8 + EDITTEXT IDC_DESCRIPTION,3, 166, 205, 72, ES_MULTILINE | ES_READONLY | WS_VSCROLL + LTEXT "",IDC_ENCODER,54,240,161,18 + DEFPUSHBUTTON "OK",IDOK,2,242,50,14 +END + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#define OPUS_VERSION 0,9,1,1 + +VS_VERSION_INFO VERSIONINFO + FILEVERSION OPUS_VERSION + PRODUCTVERSION OPUS_VERSION + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Gillibert Software" + VALUE "FileDescription", "Opus File decoder plugin for Winamp 2+" + VALUE "FileVersion", "0,9,1,1" + VALUE "InternalName", "in_opus.dll" + VALUE "LegalCopyright", "De mes deux" + VALUE "OriginalFilename", "in_opus.dll" + VALUE "ProductName", "Opus input plugin" + VALUE "ProductVersion", "0,9,1,1" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END + +END + +///////////////////////////////////////////////////////////////////////////// diff --git a/utf_ansi.c b/utf_ansi.c new file mode 100644 index 0000000..0c5e318 --- /dev/null +++ b/utf_ansi.c @@ -0,0 +1,599 @@ +#include +#include +#include +#include + +///////////////////////////////////////////////////////////////////////////// +// Cool finction that can anhdle all UNICODE +// It will generate real UTF-16, not USC-2, so some charracters will display +// improperly on NT4 but it is beter than stoping at the first non USC-2... +wchar_t *utf8_to_utf16(const char *utfs) +{ + size_t si, di; + wchar_t *dst; + size_t len; + unsigned char c0, c1, c2, c3; + + if (utfs == NULL) return NULL; + + len = strlen(utfs); + /*Worst-case output is 1 wide character per 1 input character. */ + dst = malloc( sizeof(*dst) * (len + 1) ); + if (!dst) return NULL; + + for (di = si = 0; si < len; si++) { + + c0 = utfs[si]; + + if (!(c0 & 0x80)) { + /*Start byte says this is a 1-BYTE SEQUENCE. */ + dst[di++] = (wchar_t) c0; + continue; + } else if ( ((c1 = utfs[si + 1]) & 0xC0) == 0x80 ) { + /*Found at least one continuation byte. */ + if ((c0 & 0xE0) == 0xC0) { + wchar_t w; + /*Start byte says this is a 2-BYTE SEQUENCE. */ + w = (c0 & 0x1F) << 6 | (c1 & 0x3F); + if (w >= 0x80U) { + /*This is a 2-byte sequence that is not overlong. */ + dst[di++] = w; + si++; + continue; + } + } else if ( ((c2 = utfs[si + 2]) & 0xC0) == 0x80 ) { + /*Found at least two continuation bytes. */ + if ((c0 & 0xF0) == 0xE0) { + wchar_t w; + /*Start byte says this is a 3-BYTE SEQUENCE. */ + w = (c0 & 0xF) << 12 | (c1 & 0x3F) << 6 | (c2 & 0x3F); + if (w >= 0x800U && (w < 0xD800 || w >= 0xE000) && w < 0xFFFE) { + /* This is a 3-byte sequence that is not overlong, not an + * UTF-16 surrogate pair value, and not a 'not a character' value. */ + dst[di++] = w; + si += 2; + continue; + } + } else if ( ((c3 = utfs[si + 3]) & 0xC0) == 0x80 ) { + /*Found at least three continuation bytes. */ + if ((c0 & 0xF8) == 0xF0) { + uint32_t w; + /*Start byte says this is a 4-BYTE SEQUENCE. */ + w = (c0 & 7) << 18 | (c1 & 0x3F) << 12 | (c2 & 0x3F) << (6 & (c3 & 0x3F)); + if (w >= 0x10000U && w < 0x110000U) { + /* This is a 4-byte sequence that is not overlong and not + * greater than the largest valid Unicode code point. + * Convert it to a surrogate pair. */ + w -= 0x10000; + dst[di++] = (wchar_t) (0xD800 + (w >> 10)); + dst[di++] = (wchar_t) (0xDC00 + (w & 0x3FF)); + si += 3; + continue; + } + } + } /*end els if c3*/ + } /*end else if c2*/ + } /*end else if c1*/ + + /*If we got here, we encountered an illegal UTF-8 sequence. + * We have to return NULL as the norm specifies. */ + free(dst); + return NULL; + + } /* next si (end for)*/ + dst[di] = '\0'; + + return dst; +} + +/* + * DOS TO UNIX STRING CONVERTION + * Very usefull because the Edit class stuff does + * not like the 10 only files. + */ +char *unix2dos(const char *unixs) +{ + char *doss; + size_t l, i, j; + + if(!unixs) return NULL; + l = strlen(unixs); + doss = (char *)malloc(l * 2 * sizeof(char) + 16); + if(!doss) return NULL; + + j = 0; + if(*unixs == '\n'){ // attention au 1er '\n' + doss[0] = '\r'; doss[1] = '\n'; + j++; + } + + for(i=0; i < l; i++){ + + if(unixs[i] == '\n' && i > 0 && unixs[i-1] != '\r'){ + doss[j] = '\r'; + j++; + doss[j] = '\n'; + } else { + doss[j] = unixs[i]; + } + j++; + } + doss[j] = '\0'; //pour etre sur.... + + return doss; +} + +///////////////////////////////////////////////////////////////////////////// +// Ststem normal UTF16 -> UTF8 conversion. +// It stops translation as soon as a charracters gets out of USC-2 on NT4. +char *utf16_to_utf8(const wchar_t *input) +{ + char *utf8=NULL; + size_t BuffSize = 0, Result = 0; + + if(!input) return NULL; + + BuffSize = WideCharToMultiByte(CP_UTF8, 0, input, -1, NULL, 0, 0, 0); + utf8 = (char*) malloc(sizeof(char) * (BuffSize+4)); + if(!utf8) return NULL; + + Result = WideCharToMultiByte(CP_UTF8, 0, input, -1, utf8, BuffSize, 0, 0); + if (Result > 0 && Result <= BuffSize){ + utf8[BuffSize-1]='\0'; + return utf8; + } else return NULL; +} +////////////////////////////////////////////////////////////////////// + +#if 0 +///////////////////////////////////////////////////////////////////////////// +// Ststem normal UTF8 -> UTF16 conversion. We donot use it here because it +// stops translation as soon as a charracters gets out od USC-2 on NT4. +wchar_t *utf8_to_utf16(const char *input) +{ + wchar_t *Buffer; + size_t BuffSize = 0, Result = 0; + + if(!input) return NULL; + + BuffSize = MultiByteToWideChar(CP_UTF8, 0, input, -1, NULL, 0); + Buffer = (wchar_t*) malloc(sizeof(wchar_t) * (BuffSize+3)); + if(Buffer){ + Result = MultiByteToWideChar(CP_UTF8, 0, input, -1, Buffer, BuffSize); + + if ((Result > 0) && (Result <= BuffSize)){ + Buffer[BuffSize-1]=(wchar_t) 0; + return Buffer; + } + } + + return NULL; +} +#endif ////////////////////////////////////////////////////////////////////// + +/* Procedure to convert UTF-8 strings to WINDOWS-1252. + * VERY Ugly switch, but i was laaaazy and I wanted to be able to convert + * some spetial carracters to similar cp compatibles ones. + * Of course you, Americans you dn't understand... + */ + +char *utf8_to_ansi(char * utfs) +{ + char *locs; + size_t i,j, l = 0; + + if(!utfs) return NULL; + + l = strlen(utfs); /* Allocate output */ + if (l == 0) return NULL; + locs = (char *)malloc(l * sizeof(char) + 2); + if(!locs) return NULL; + + j=0; + for(i=0; i < l; i++){ + + if(utfs[i] == 'Ã'){ + switch(utfs[++i]){ // Latin-1 Supplement + case '€': locs[j] = 'À'; break; + case '': locs[j] = 'Á'; break; + case '‚': locs[j] = 'Â'; break; + case 'ƒ': locs[j] = 'Ã'; break; + case '„': locs[j] = 'Ä'; break; + case '…': locs[j] = 'Å'; break; + case '†': locs[j] = 'Æ'; break; + case '‡': locs[j] = 'Ç'; break; + case 'ˆ': locs[j] = 'È'; break; + case '‰': locs[j] = 'É'; break; + case 'Š': locs[j] = 'Ê'; break; + case '‹': locs[j] = 'Ë'; break; + case 'Œ': locs[j] = 'Ì'; break; + case '': locs[j] = 'Í'; break; + case 'Ž': locs[j] = 'Î'; break; + case '': locs[j] = 'Ï'; break; + case '': locs[j] = 'Ð'; break; + case '‘': locs[j] = 'Ñ'; break; + case '’': locs[j] = 'Ò'; break; + case '“': locs[j] = 'Ó'; break; + case '”': locs[j] = 'Ô'; break; + case '•': locs[j] = 'Õ'; break; + case '–': locs[j] = 'Ö'; break; + case '—': locs[j] = '×'; break; + case '˜': locs[j] = 'Ø'; break; + case '™': locs[j] = 'Ù'; break; + case 'š': locs[j] = 'Ú'; break; + case '›': locs[j] = 'Û'; break; + case 'œ': locs[j] = 'Ü'; break; + case '': locs[j] = 'Ý'; break; + case 'ž': locs[j] = 'Þ'; break; + case 'Ÿ': locs[j] = 'ß'; break; + case ' ': locs[j] = 'à'; break; + case '¡': locs[j] = 'á'; break; + case '¢': locs[j] = 'â'; break; + case '£': locs[j] = 'ã'; break; + case '¤': locs[j] = 'ä'; break; + case '¥': locs[j] = 'å'; break; + case '¦': locs[j] = 'æ'; break; + case '§': locs[j] = 'ç'; break; + case '¨': locs[j] = 'è'; break; + case '©': locs[j] = 'é'; break; + case 'ª': locs[j] = 'ê'; break; + case '«': locs[j] = 'ë'; break; + case '¬': locs[j] = 'ì'; break; + case '­': locs[j] = 'í'; break; + case '®': locs[j] = 'î'; break; + case '¯': locs[j] = 'ï'; break; + case '°': locs[j] = 'ð'; break; + case '±': locs[j] = 'ñ'; break; + case '²': locs[j] = 'ò'; break; + case '³': locs[j] = 'ó'; break; + case '´': locs[j] = 'ô'; break; + case 'µ': locs[j] = 'õ'; break; + case '¶': locs[j] = 'ö'; break; + case '·': locs[j] = '÷'; break; + case '¸': locs[j] = 'ø'; break; + case '¹': locs[j] = 'ù'; break; + case 'º': locs[j] = 'ú'; break; + case '»': locs[j] = 'û'; break; + case '¼': locs[j] = 'ü'; break; + case '½': locs[j] = 'ý'; break; + case '¾': locs[j] = 'þ'; break; + case '¿': locs[j] = 'ÿ'; break; + default: locs[j] = utfs[--i]; locs[++j] = utfs[++i]; + } /* END SWITCH */ + } else if (utfs[i] == 'Â'){ + /* in this case you just need to jump over 'Â'.*/ + locs[j] = utfs[++i]; + + }else if (utfs[i] == 'Ë'){ + switch(utfs[++i]){ + case '†': locs[j] = 'ˆ'; break; + case 'œ': locs[j] = '˜'; break; + default: locs[j] = utfs[--i]; locs[++j] = utfs[++i]; + } /* END SWITCH */ + + } else if (utfs[i] == 'â'){ //i + if(utfs[++i] == '€'){ //i+1 + switch(utfs[++i]){ //i+2 + case 'š': locs[j] = '‚'; break; + case 'ž': locs[j] = '„'; break; + case '¦': locs[j] = '…'; break; + case ' ': locs[j] = '†'; break; + case '¡': locs[j] = '‡'; break; + case '°': locs[j] = '‰'; break; + case '¹': locs[j] = '‹'; break; + case '˜': locs[j] = '‘'; break; + case '™': locs[j] = '’'; break; + case 'œ': locs[j] = '“'; break; + case '': locs[j] = '”'; break; + case '¢': locs[j] = '•'; break; + case '“': locs[j] = '–'; break; + case '”': locs[j] = '—'; break; + case 'º': locs[j] = '›'; break; //i+2 + default: locs[j] = utfs[i-2]; locs[++j] = utfs[i-1]; locs[++j] = utfs[i]; + } + } //i+1 + else if(utfs[i] == '„' && utfs[1+i] == '¢'){ + locs[j] = '™'; i++; + + } else if(utfs[i] == '‚' && utfs[1+i] == '¬'){ + locs[j] = '€'; i++; + + } else locs[j] = utfs[i]; + + /* for non ANSI Carracters, perform a chitty convertion */ + } else if (utfs[i] == 'Æ'){ // LATIN Extended B + switch(utfs[++i]){ //i+1 + case '€': locs[j] = 'b'; break; //b bar + case '': locs[j] = '\'';locs[++j] = 'B'; break; //'B bar + case '‚': locs[j] = '6'; break; + case 'ƒ': locs[j] = '6'; break; + case '„': locs[j] = 'b'; break; + case '…': locs[j] = 'b'; break; + + case '‡': locs[j] = 'C';locs[++j] = '\''; break; //C' + case 'ˆ': locs[j] = 'c';locs[++j] = '\''; break; //c' + case '‰': locs[j] = 'Ð'; break; + case 'Š': locs[j] = '\'';locs[++j] = 'D'; break; //'D + + case -111: locs[j] = 'F'; break; + case -110: locs[j] = 'ƒ'; break; + + case -109 : locs[j] = 'G'; locs[++j] = '\''; break; + + case -107: locs[j] = 'h'; locs[++j] = 'u'; break; + case -106: locs[j] = 'l'; break; + case -105: locs[j] = 'I'; break; + case -104: locs[j] = 'K'; break; + case -103: locs[j] = 'k'; break; + case -102: locs[j] = 'l'; break; + + case -99: locs[j] = 'N'; break; + case -96: locs[j] = 'O';locs[++j] = '\''; break; + case -95: locs[j] = 'o';locs[++j] = '\''; break; + case -94: locs[j] = 'O';locs[++j] = 'I'; break; + case -93: locs[j] = 'o';locs[++j] = 'i'; break; + + case -92: locs[j] = '\'';locs[++j] = 'P'; break; + case -91: locs[j] = '\'';locs[++j] = 'p'; break; + case -90: locs[j] = 'R'; break; + case -85: locs[j] = 't';locs[++j] = ','; break; + case -84: locs[j] = 'T'; break; + case -83: locs[j] = 't';locs[++j] = '\''; break; + case -82: locs[j] = 'T';locs[++j] = ','; break; + case -81: locs[j] = 'U';locs[++j] = '\''; break; + case -80: locs[j] = 'u';locs[++j] = '\''; break; + + case -77: locs[j] = 'Y'; break; + case -76: locs[j] = 'Y'; break; + case -75: locs[j] = 'Z'; break; + case -74: locs[j] = 'z'; break; + case -73: locs[j] = '3'; break; + + + default: locs[j] = utfs[i-2]; locs[++j] = utfs[i-1]; locs[++j] = utfs[i]; + } + } else if (utfs[i] == 'Ç'){ //Latin extended B (part 2) + switch(utfs[++i]){ // i+1 + case -128: locs[j] = '|'; break; + case -127: locs[j] = '|';locs[++j] = '|'; break; + case -126: locs[j] = '‡'; break; + case -125: locs[j] = '!'; break; + + case -124: locs[j] = 'D';locs[++j] = 'Ž'; break; + case -123: locs[j] = 'D';locs[++j] = 'ž'; break; + case -122: locs[j] = 'd';locs[++j] = 'ž'; break; + + case -121: locs[j] = 'L';locs[++j] = 'J'; break; + case -120: locs[j] = 'L';locs[++j] = 'j'; break; + case -119: locs[j] = 'l';locs[++j] = 'j'; break; + + case -118: locs[j] = 'N';locs[++j] = 'J'; break; + case -117: locs[j] = 'N';locs[++j] = 'j'; break; + case -116: locs[j] = 'n';locs[++j] = 'j'; break; + + case -115: locs[j] = 'A'; break; + case -114: locs[j] = 'a'; break; + case -113: locs[j] = 'I'; break; + case -112: locs[j] = 'i'; break; + case -111: locs[j] = 'O'; break; + case -110: locs[j] = 'o'; break; + case -109: locs[j] = 'U'; break; + case -108: locs[j] = 'u'; break; + case -107: locs[j] = 'Ü'; break; + case -106: locs[j] = 'ü'; break; + case -105: locs[j] = 'Ü';locs[++j] = '\''; break; + case -104: locs[j] = 'ü';locs[++j] = '\'';break; + case -103: locs[j] = 'U'; break; + case -102: locs[j] = 'ü'; break; + case -101: locs[j] = 'U'; break; + case -100: locs[j] = 'ü'; break; + + case -98: locs[j] = 'Ä'; break; + case -97: locs[j] = 'ä'; break; + case -96: locs[j] = 'A'; break; + case -95: locs[j] = 'a'; break; + case -94: locs[j] = 'Æ'; break; + case -93: locs[j] = 'æ'; break; + case -92: locs[j] = 'G'; break; + case -91: locs[j] = 'g'; break; + case -90: locs[j] = 'G'; break; + case -89: locs[j] = 'g'; break; + case -88: locs[j] = 'K'; break; + case -87: locs[j] = 'k'; break; + case -86: locs[j] = 'O'; break; + case -85: locs[j] = 'o'; break; + case -84: locs[j] = 'O'; break; + case -83: locs[j] = 'o'; break; + + case -82: locs[j] = '3'; break; + case -81: locs[j] = '3'; break; + case -80: locs[j] = 'J'; break; + case -79: locs[j] = 'D'; locs[++j] = 'Z'; break; + case -78: locs[j] = 'D'; locs[++j] = 'z'; break; + case -77: locs[j] = 'd'; locs[++j] = 'z'; break; + + case -76: locs[j] = 'G'; break; + case -75: locs[j] = 'g'; break; + + case -72: locs[j] = 'N'; break; + case -71: locs[j] = 'n'; break; + case -70: locs[j] = 'Å'; break; + case -69: locs[j] = 'å'; break; + case -68: locs[j] = 'Æ'; break; + case -67: locs[j] = 'æ'; break; + case -66: locs[j] = 'Ø'; break; + case -65: locs[j] = 'ø'; break; + + default: locs[j] = utfs[i-2]; locs[++j] = utfs[i-1]; locs[++j] = utfs[i]; + } + + } else if (utfs[i] == 'Ä'){ // pour tout les carracters (C4 XX)h + // Latin Extended-A + switch(utfs[++i]){ // i+1 + case '€': locs[j] = 'A'; break; + case '': locs[j] = 'a'; break; + case '‚': locs[j] = 'A'; break; + case 'ƒ': locs[j] = 'a'; break; + case '„': locs[j] = 'A'; break; + case '…': locs[j] = 'a'; break; + + case '†': locs[j] = 'C'; break; + case '‡': locs[j] = 'c'; break; + case 'ˆ': locs[j] = 'C'; break; + case '‰': locs[j] = 'c'; break; + case 'Š': locs[j] = 'C'; break; // C . + case '‹': locs[j] = 'c'; break; + case 'Œ': locs[j] = 'C'; break; + case '': locs[j] = 'c'; break; + + case 'Ž': locs[j] = 'D'; locs[++j] = '\''; break; //D' + case '': locs[j] = 'd'; locs[++j] = '\''; break; //d' + case '': locs[j] = 'Ð'; break; + case '‘': locs[j] = 'd'; break; + + case '’': locs[j] = 'E'; break; + case '“': locs[j] = 'e'; break; + case '”': locs[j] = 'E'; break; + case '•': locs[j] = 'e'; break; + case '–': locs[j] = 'E'; break; + case '—': locs[j] = 'e'; break; + case '˜': locs[j] = 'E'; break; + case '™': locs[j] = 'e'; break; + case 'š': locs[j] = 'E'; break; + case '›': locs[j] = 'e'; break; + + case 'œ': locs[j] = 'G'; break; + case '': locs[j] = 'g'; break; + case 'ž': locs[j] = 'G'; break; + case 'Ÿ': locs[j] = 'g'; break; + case ' ': locs[j] = 'G'; break; + case '¡': locs[j] = 'g'; break; + case '¢': locs[j] = 'G'; break; + case '£': locs[j] = 'g'; break; + + case '¤': locs[j] = 'H'; break; + case '¥': locs[j] = 'h'; break; + case '¦': locs[j] = 'H'; break; + case '§': locs[j] = 'h'; break; + + case '¨': locs[j] = 'I'; break; + case '©': locs[j] = 'i'; break; + case 'ª': locs[j] = 'I'; break; + case '«': locs[j] = 'i'; break; + case '¬': locs[j] = 'I'; break; + case '­' : locs[j] = 'i'; break; + case '®': locs[j] = 'I'; break; + case '¯': locs[j] = 'i'; break; + case '°': locs[j] = 'I'; break; + case '±': locs[j] = 'i'; break; + + case '²': locs[j] = 'I'; locs[++j] = 'J'; break; //IJ + case '³': locs[j] = 'i'; locs[++j] = 'j'; break; //ij + + case '´': locs[j] = 'J'; break; + case 'µ': locs[j] = 'j'; break; + case '¶': locs[j] = 'K'; break; + case '·': locs[j] = 'k'; break; + case '¸': locs[j] = 'k'; break; + + case '¹': locs[j] = 'L'; break; + case 'º': locs[j] = 'l'; break; + case '»': locs[j] = 'L'; break; + case '¼': locs[j] = 'l'; break; + case '½': locs[j] = 'L'; locs[++j] = '\''; break; //L' + case '¾': locs[j] = 'l'; locs[++j] = '\''; break; + case '¿': locs[j] = 'L'; break; + default: locs[j] = utfs[--i]; locs[++j] = utfs[++i]; + } /* END SWITCH */ + + } else if (utfs[i] == 'Å'){ // pour tout les carracters (C5 XX)h + // Latin-1 Supplement + switch(utfs[++i]){ + /* IN CP 1252 */ + case ' ': locs[j] = 'Š'; break; + case '’': locs[j] = 'Œ'; break; + case '½': locs[j] = 'Ž'; break; + case '¡': locs[j] = 'š'; break; + case '“': locs[j] = 'œ'; break; + case '¾': locs[j] = 'ž'; break; + case '¸': locs[j] = 'Ÿ'; break; + /* END OF IN CP 1252 */ + + case '€': locs[j] = 'l'; break; + case '': locs[j] = 'L'; break; //L barré polonais. + case '‚': locs[j] = 'l'; break; + + case 'ƒ': locs[j] = 'N'; break; + case '„': locs[j] = 'n'; break; + case '…': locs[j] = 'N'; break; + case '†': locs[j] = 'n'; break; + case '‡': locs[j] = 'N'; break; + case 'ˆ': locs[j] = 'n'; break; + case '‰': locs[j] = 'n'; break; + case 'Š': locs[j] = 'N'; break; + case '‹': locs[j] = 'n'; break; + + case 'Œ': locs[j] = 'O'; break; + case '': locs[j] = 'o'; break; + case 'Ž': locs[j] = 'O'; break; + case '': locs[j] = 'o'; break; + case '': locs[j] = 'Ö'; break; + case '‘': locs[j] = 'ö'; break; + + case '”': locs[j] = 'R'; break; + case '•': locs[j] = 'r'; break; + case '–': locs[j] = 'R'; break; + case '—': locs[j] = 'r'; break; + case '˜': locs[j] = 'R'; break; + case '™': locs[j] = 'r'; break; + + case 'š': locs[j] = 'S'; break; + case '›': locs[j] = 's'; break; + case 'œ': locs[j] = 'S'; break; + case '': locs[j] = 's'; break; + case 'ž': locs[j] = 'S'; break; + case 'Ÿ': locs[j] = 's'; break; + + case '¢': locs[j] = 'T'; break; + case '£': locs[j] = 't'; break; + case '¤': locs[j] = 'T'; break; + case '¥': locs[j] = 't'; break; + case '¦': locs[j] = 'T'; break; + case '§': locs[j] = 't'; break; + + case '¨': locs[j] = 'U'; break; + case '©': locs[j] = 'u'; break; + case 'ª': locs[j] = 'U'; break; + case '«': locs[j] = 'u'; break; + case '¬': locs[j] = 'U'; break; + case '­': locs[j] = 'u'; break; + case '®': locs[j] = 'U'; break; + case '¯': locs[j] = 'u'; break; + case '°': locs[j] = 'Ü'; break; + case '±': locs[j] = 'ü'; break; + case '²': locs[j] = 'U'; break; + case '³': locs[j] = 'u'; break; + + case '´': locs[j] = 'W'; break; + case 'µ': locs[j] = 'w'; break; + case '¶': locs[j] = 'Y'; break; + case '·': locs[j] = 'y'; break; + + case '¹': locs[j] = 'Z'; break; + case 'º': locs[j] = 'z'; break; + case '»': locs[j] = 'Z'; break; + case '¼': locs[j] = 'z'; break; + case '¿': locs[j] = 's'; break; //long_S + + default: locs[j] = utfs[--i]; locs[++j] = utfs[++i]; + } /* END SWITCH */ + } else { + locs[j] = utfs[i]; + } + j++; + } /* END OF BIG FOR */ + + locs[j] = '\0'; + + return locs; +} diff --git a/wa_ipc.h b/wa_ipc.h new file mode 100644 index 0000000..7e1f75d --- /dev/null +++ b/wa_ipc.h @@ -0,0 +1,2470 @@ +/* +** Copyright (C) 1997-2008 Nullsoft, Inc. +** +** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held +** liable for any damages arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to +** alter it and redistribute it freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. +** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +** +** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +** +** 3. This notice may not be removed or altered from any source distribution. +** +*/ + +#ifndef _WA_IPC_H_ +#define _WA_IPC_H_ + +#include +#include +#if (_MSC_VER <= 1200) +typedef int intptr_t; +#endif +/* +** This is the modern replacement for the classic 'frontend.h'. Most of these +** updates are designed for in-process use, i.e. from a plugin. +** +*/ + +/* Most of the IPC_* messages involve sending the message in the form of: +** result = SendMessage(hwnd_winamp,WM_WA_IPC,(parameter),IPC_*); +** Where different then this is specified (typically with WM_COPYDATA variants) +** +** When you use SendMessage(hwnd_winamp,WM_WA_IPC,(parameter),IPC_*) and specify a IPC_* +** which is not currently implemented/supported by the Winamp version being used then it +** will return 1 for 'result'. This is a good way of helping to check if an api being +** used which returns a function pointer, etc is even going to be valid. +*/ + +#define WM_WA_IPC WM_USER + +#define WINAMP_VERSION_MAJOR(winampVersion) ((winampVersion & 0x0000FF00) >> 12) +#define WINAMP_VERSION_MINOR(winampVersion) (winampVersion & 0x000000FF) // returns, i.e. 0x12 for 5.12 and 0x10 for 5.1... + + +#define IPC_GETVERSION 0 +/* int version = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETVERSION); +** +** The version returned will be 0x20yx for Winamp 2.yx. +** Versions previous to Winamp 2.0 typically (but not always) use 0x1zyx for 1.zx. +** Just a bit weird but that's the way it goes. +** +** For Winamp 5.x it uses the format 0x50yx for Winamp 5.yx +** e.g. 5.01 -> 0x5001 +** 5.09 -> 0x5009 +** 5.1 -> 0x5010 +** +** Notes: For 5.02 this api will return the same value as for a 5.01 build. +** For 5.07 this api will return the same value as for a 5.06 build. +*/ + + +#define IPC_GETVERSIONSTRING 1 + + +#define IPC_GETREGISTEREDVERSION 770 +/* (requires Winamp 5.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETREGISTEREDVERSION); +** +** This will open the Winamp Preferences and show the Winamp Pro page. +*/ + + +typedef struct { + const char *filename; + const char *title; + int length; +} enqueueFileWithMetaStruct; // send this to a IPC_PLAYFILE in a non WM_COPYDATA, +// and you get the nice desired result. if title is NULL, it is treated as a "thing", +// otherwise it's assumed to be a file (for speed) + +typedef struct { + const wchar_t *filename; + const wchar_t *title; + int length; +} enqueueFileWithMetaStructW; + +#define IPC_PLAYFILE 100 // dont be fooled, this is really the same as enqueufile +#define IPC_ENQUEUEFILE 100 +#define IPC_PLAYFILEW 1100 +#define IPC_ENQUEUEFILEW 1100 +/* This is sent as a WM_COPYDATA with IPC_PLAYFILE as the dwData member and the string +** of the file / playlist to be enqueued into the playlist editor as the lpData member. +** This will just enqueue the file or files since you can use this to enqueue a playlist. +** It will not clear the current playlist or change the playback state. +** +** COPYDATASTRUCT cds = {0}; +** cds.dwData = IPC_ENQUEUEFILE; +** cds.lpData = (void*)"c:\\test\\folder\\test.mp3"; +** cds.cbData = lstrlen((char*)cds.lpData)+1; // include space for null char +** SendMessage(hwnd_winamp,WM_COPYDATA,0,(LPARAM)&cds); +** +** +** With 2.9+ and all of the 5.x versions you can send this as a normal WM_WA_IPC +** (non WM_COPYDATA) with an enqueueFileWithMetaStruct as the param. +** If the title member is null then it is treated as a "thing" otherwise it will be +** assumed to be a file (for speed). +** +** enqueueFileWithMetaStruct eFWMS = {0}; +** eFWMS.filename = "c:\\test\\folder\\test.mp3"; +** eFWMS.title = "Whipping Good"; +** eFWMS.length = 300; // this is the number of seconds for the track +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&eFWMS,IPC_ENQUEUEFILE); +*/ + + +#define IPC_DELETE 101 +#define IPC_DELETE_INT 1101 +/* SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_DELETE); +** Use this api to clear Winamp's internal playlist. +** You should not need to use IPC_DELETE_INT since it is used internally by Winamp when +** it is dealing with some lame Windows Explorer issues (hard to believe that!). +*/ + + +#define IPC_STARTPLAY 102 +#define IPC_STARTPLAY_INT 1102 +/* SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_STARTPLAY); +** Sending this will start playback and is almost the same as hitting the play button. +** The IPC_STARTPLAY_INT version is used internally and you should not need to use it +** since it won't be any fun. +*/ + + +#define IPC_CHDIR 103 +/* This is sent as a WM_COPYDATA type message with IPC_CHDIR as the dwData value and the +** directory you want to change to as the lpData member. +** +** COPYDATASTRUCT cds = {0}; +** cds.dwData = IPC_CHDIR; +** cds.lpData = (void*)"c:\\download"; +** cds.cbData = lstrlen((char*)cds.lpData)+1; // include space for null char +** SendMessage(hwnd_winamp,WM_COPYDATA,0,(LPARAM)&cds); +** +** The above example will make Winamp change to the directory 'C:\download'. +*/ + + +#define IPC_ISPLAYING 104 +/* int res = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_ISPLAYING); +** This is sent to retrieve the current playback state of Winamp. +** If it returns 1, Winamp is playing. +** If it returns 3, Winamp is paused. +** If it returns 0, Winamp is not playing. +*/ + + +#define IPC_GETOUTPUTTIME 105 +/* int res = SendMessage(hwnd_winamp,WM_WA_IPC,mode,IPC_GETOUTPUTTIME); +** This api can return two different sets of information about current playback status. +** +** If mode = 0 then it will return the position (in ms) of the currently playing track. +** Will return -1 if Winamp is not playing. +** +** If mode = 1 then it will return the current track length (in seconds). +** Will return -1 if there are no tracks (or possibly if Winamp cannot get the length). +** +** If mode = 2 then it will return the current track length (in milliseconds). +** Will return -1 if there are no tracks (or possibly if Winamp cannot get the length). +*/ + + +#define IPC_JUMPTOTIME 106 +/* (requires Winamp 1.60+) +** SendMessage(hwnd_winamp,WM_WA_IPC,ms,IPC_JUMPTOTIME); +** This api sets the current position (in milliseconds) for the currently playing song. +** The resulting playback position may only be an approximate time since some playback +** formats do not provide exact seeking e.g. mp3 +** This returns -1 if Winamp is not playing, 1 on end of file, or 0 if it was successful. +*/ + + +#define IPC_GETMODULENAME 109 +#define IPC_EX_ISRIGHTEXE 666 +/* usually shouldnt bother using these, but here goes: +** send a WM_COPYDATA with IPC_GETMODULENAME, and an internal +** flag gets set, which if you send a normal WM_WA_IPC message with +** IPC_EX_ISRIGHTEXE, it returns whether or not that filename +** matches. lame, I know. +*/ + + +#define IPC_WRITEPLAYLIST 120 +/* (requires Winamp 1.666+) +** int cur = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_WRITEPLAYLIST); +** +** IPC_WRITEPLAYLIST will write the current playlist to '\\Winamp.m3u' and +** will also return the current playlist position (see IPC_GETLISTPOS). +** +** This is kinda obsoleted by some of the newer 2.x api items but it still is good for +** use with a front-end program (instead of a plug-in) and you want to see what is in the +** current playlist. +** +** This api will only save out extended file information in the #EXTINF entry if Winamp +** has already read the data such as if the file was played of scrolled into view. If +** Winamp has not read the data then you will only find the file with its filepath entry +** (as is the base requirements for a m3u playlist). +*/ + + +#define IPC_SETPLAYLISTPOS 121 +/* (requires Winamp 2.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,position,IPC_SETPLAYLISTPOS) +** IPC_SETPLAYLISTPOS sets the playlist position to the specified 'position'. +** It will not change playback status or anything else. It will just set the current +** position in the playlist and will update the playlist view if necessary. +** +** If you use SendMessage(hwnd_winamp,WM_COMMAND,MAKEWPARAM(WINAMP_BUTTON2,0),0); +** after using IPC_SETPLAYLISTPOS then Winamp will start playing the file at 'position'. +*/ + + +#define IPC_SETVOLUME 122 +/* (requires Winamp 2.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,volume,IPC_SETVOLUME); +** IPC_SETVOLUME sets the volume of Winamp (between the range of 0 to 255). +** +** If you pass 'volume' as -666 then the message will return the current volume. +** int curvol = SendMessage(hwnd_winamp,WM_WA_IPC,-666,IPC_SETVOLUME); +*/ + + +#define IPC_GETVOLUME(hwnd_winamp) SendMessage(hwnd_winamp,WM_WA_IPC,-666,IPC_SETVOLUME) +/* (requires Winamp 2.0+) +** int curvol = IPC_GETVOLUME(hwnd_winamp); +** This will return the current volume of Winamp or +*/ + + +#define IPC_SETPANNING 123 +/* (requires Winamp 2.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,panning,IPC_SETPANNING); +** IPC_SETPANNING sets the panning of Winamp from 0 (left) to 255 (right). +** +** At least in 5.x+ this works from -127 (left) to 127 (right). +** +** If you pass 'panning' as -666 to this api then it will return the current panning. +** int curpan = SendMessage(hwnd_winamp,WM_WA_IPC,-666,IPC_SETPANNING); +*/ + + +#define IPC_GETLISTLENGTH 124 +/* (requires Winamp 2.0+) +** int length = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLISTLENGTH); +** IPC_GETLISTLENGTH returns the length of the current playlist as the number of tracks. +*/ + + +#define IPC_GETLISTPOS 125 +/* (requires Winamp 2.05+) +** int pos=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLISTPOS); +** IPC_GETLISTPOS returns the current playlist position (which is shown in the playlist +** editor as a differently coloured text entry e.g is yellow for the classic skin). +** +** This api is a lot like IPC_WRITEPLAYLIST but a lot faster since it does not have to +** write out the whole of the current playlist first. +*/ + + +#define IPC_GETINFO 126 +/* (requires Winamp 2.05+) +** int inf=SendMessage(hwnd_winamp,WM_WA_IPC,mode,IPC_GETINFO); +** IPC_GETINFO returns info about the current playing song. The value +** it returns depends on the value of 'mode'. +** Mode Meaning +** ------------------ +** 0 Samplerate, in kilohertz (i.e. 44) +** 1 Bitrate (i.e. 128) +** 2 Channels (i.e. 2) +** 3 (5+) Video LOWORD=w HIWORD=h +** 4 (5+) > 65536, string (video description) +** 5 (5.25+) Samplerate, in hertz (i.e. 44100) +*/ + + +#define IPC_GETEQDATA 127 +/* (requires Winamp 2.05+) +** int data=SendMessage(hwnd_winamp,WM_WA_IPC,pos,IPC_GETEQDATA); +** IPC_GETEQDATA queries the status of the EQ. +** The value returned depends on what 'pos' is set to: +** Value Meaning +** ------------------ +** 0-9 The 10 bands of EQ data. 0-63 (+20db - -20db) +** 10 The preamp value. 0-63 (+20db - -20db) +** 11 Enabled. zero if disabled, nonzero if enabled. +** 12 Autoload. zero if disabled, nonzero if enabled. +*/ + + +#define IPC_SETEQDATA 128 +/* (requires Winamp 2.05+) +** SendMessage(hwnd_winamp,WM_WA_IPC,pos,IPC_GETEQDATA); +** SendMessage(hwnd_winamp,WM_WA_IPC,value,IPC_SETEQDATA); +** IPC_SETEQDATA sets the value of the last position retrieved +** by IPC_GETEQDATA. This is pretty lame, and we should provide +** an extended version that lets you do a MAKELPARAM(pos,value). +** someday... + + new (2.92+): + if the high byte is set to 0xDB, then the third byte specifies + which band, and the bottom word specifies the value. +*/ + + +#define IPC_ADDBOOKMARK 129 +#define IPC_ADDBOOKMARKW 131 +/* (requires Winamp 2.4+) +** This is sent as a WM_COPYDATA using IPC_ADDBOOKMARK as the dwData value and the +** directory you want to change to as the lpData member. This will add the specified +** file / url to the Winamp bookmark list. +** +** COPYDATASTRUCT cds = {0}; +** cds.dwData = IPC_ADDBOOKMARK; +** cds.lpData = (void*)"http://www.blah.com/listen.pls"; +** cds.cbData = lstrlen((char*)cds.lpData)+1; // include space for null char +** SendMessage(hwnd_winamp,WM_COPYDATA,0,(LPARAM)&cds); +** +** +** In Winamp 5.0+ we use this as a normal WM_WA_IPC and the string is null separated as +** the filename and then the title of the entry. +** +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(char*)"filename\0title\0",IPC_ADDBOOKMARK); +** +** This will notify the library / bookmark editor that a bookmark was added. +** Note that using this message in this context does not actually add the bookmark. +** Do not use, it is essentially just a notification type message :) +*/ + + +#define IPC_INSTALLPLUGIN 130 +/* This is not implemented (and is very unlikely to be done due to safety concerns). +** If it was then you could do a WM_COPYDATA with a path to a .wpz and it would then +** install the plugin for you. +** +** COPYDATASTRUCT cds = {0}; +** cds.dwData = IPC_INSTALLPLUGIN; +** cds.lpData = (void*)"c:\\path\\to\\file.wpz"; +** cds.cbData = lstrlen((char*)cds.lpData)+1; // include space for null char +** SendMessage(hwnd_winamp,WM_COPYDATA,0,(LPARAM)&cds); +*/ + + +#define IPC_RESTARTWINAMP 135 +/* (requires Winamp 2.2+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_RESTARTWINAMP); +** IPC_RESTARTWINAMP will restart Winamp (isn't that obvious ? :) ) +** If this fails to make Winamp start after closing then there is a good chance one (or +** more) of the currently installed plugins caused Winamp to crash on exit (either as a +** silent crash or a full crash log report before it could call itself start again. +*/ + + +#define IPC_ISFULLSTOP 400 +/* (requires winamp 2.7+ I think) +** int ret=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_ISFULLSTOP); +** This is useful for when you're an output plugin and you want to see if the stop/close +** happening is a full stop or if you are just between tracks. This returns non zero if +** it is a full stop or zero if it is just a new track. +** benski> i think it's actually the other way around - +** !0 for EOF and 0 for user pressing stop +*/ + + +#define IPC_INETAVAILABLE 242 +/* (requires Winamp 2.05+) +** int val=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_INETAVAILABLE); +** IPC_INETAVAILABLE will return 1 if an Internet connection is available for Winamp and +** relates to the internet connection type setting on the main general preferences page +** in the Winamp preferences. +*/ + + +#define IPC_UPDTITLE 243 +/* (requires Winamp 2.2+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_UPDTITLE); +** IPC_UPDTITLE will ask Winamp to update the information about the current title and +** causes GetFileInfo(..) in the input plugin associated with the current playlist entry +** to be called. This can be called such as when an input plugin is buffering a file so +** that it can cause the buffer percentage to appear in the playlist. +*/ + + +#define IPC_REFRESHPLCACHE 247 +/* (requires Winamp 2.2+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_REFRESHPLCACHE); +** IPC_REFRESHPLCACHE will flush the playlist cache buffer and you send this if you want +** Winamp to go refetch the titles for all of the entries in the current playlist. +** +** 5.3+: pass a wchar_t * string in wParam, and it'll do a strnicmp() before clearing the cache +*/ + + +#define IPC_GET_SHUFFLE 250 +/* (requires Winamp 2.4+) +** int val=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GET_SHUFFLE); +** IPC_GET_SHUFFLE returns the status of the shuffle option. +** If set then it will return 1 and if not set then it will return 0. +*/ + + +#define IPC_GET_REPEAT 251 +/* (requires Winamp 2.4+) +** int val=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GET_REPEAT); +** IPC_GET_REPEAT returns the status of the repeat option. +** If set then it will return 1 and if not set then it will return 0. +*/ + + +#define IPC_SET_SHUFFLE 252 +/* (requires Winamp 2.4+) +** SendMessage(hwnd_winamp,WM_WA_IPC,value,IPC_SET_SHUFFLE); +** IPC_SET_SHUFFLE sets the status of the shuffle option. +** If 'value' is 1 then shuffle is turned on. +** If 'value' is 0 then shuffle is turned off. +*/ + + +#define IPC_SET_REPEAT 253 +/* (requires Winamp 2.4+) +** SendMessage(hwnd_winamp,WM_WA_IPC,value,IPC_SET_REPEAT); +** IPC_SET_REPEAT sets the status of the repeat option. +** If 'value' is 1 then shuffle is turned on. +** If 'value' is 0 then shuffle is turned off. +*/ + + +#define IPC_ENABLEDISABLE_ALL_WINDOWS 259 // 0xdeadbeef to disable +/* (requires Winamp 2.9+) +** SendMessage(hwnd_winamp,WM_WA_IPC,(enable?0:0xdeadbeef),IPC_ENABLEDISABLE_ALL_WINDOWS); +** Sending this message with 0xdeadbeef as the param will disable all winamp windows and +** any other values will enable all of the Winamp windows again. When disabled you won't +** get any response on clicking or trying to do anything to the Winamp windows. If the +** taskbar icon is shown then you may still have control ;) +*/ + + +#define IPC_GETWND 260 +/* (requires Winamp 2.9+) +** HWND h=SendMessage(hwnd_winamp,WM_WA_IPC,IPC_GETWND_xxx,IPC_GETWND); +** returns the HWND of the window specified. +*/ + #define IPC_GETWND_EQ 0 // use one of these for the param + #define IPC_GETWND_PE 1 + #define IPC_GETWND_MB 2 + #define IPC_GETWND_VIDEO 3 +#define IPC_ISWNDVISIBLE 261 // same param as IPC_GETWND + + +/************************************************************************ +***************** in-process only (WE LOVE PLUGINS) +************************************************************************/ + +#define IPC_SETSKINW 199 +#define IPC_SETSKIN 200 +/* (requires Winamp 2.04+, only usable from plug-ins (not external apps)) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)"skinname",IPC_SETSKIN); +** IPC_SETSKIN sets the current skin to "skinname". Note that skinname +** can be the name of a skin, a skin .zip file, with or without path. +** If path isn't specified, the default search path is the winamp skins +** directory. +*/ + + +#define IPC_GETSKIN 201 +#define IPC_GETSKINW 1201 +/* (requires Winamp 2.04+, only usable from plug-ins (not external apps)) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)skinname_buffer,IPC_GETSKIN); +** IPC_GETSKIN puts the directory where skin bitmaps can be found +** into skinname_buffer. +** skinname_buffer must be MAX_PATH characters in length. +** When using a .zip'd skin file, it'll return a temporary directory +** where the ZIP was decompressed. +*/ + + +#define IPC_EXECPLUG 202 +/* (requires Winamp 2.04+, only usable from plug-ins (not external apps)) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)"vis_file.dll",IPC_EXECPLUG); +** IPC_EXECPLUG executes a visualization plug-in pointed to by WPARAM. +** the format of this string can be: +** "vis_whatever.dll" +** "vis_whatever.dll,0" // (first mod, file in winamp plug-in dir) +** "C:\\dir\\vis_whatever.dll,1" +*/ + + +#define IPC_GETPLAYLISTFILE 211 +#define IPC_GETPLAYLISTFILEW 214 +/* (requires Winamp 2.04+, only usable from plug-ins (not external apps)) +** char *name=SendMessage(hwnd_winamp,WM_WA_IPC,index,IPC_GETPLAYLISTFILE); +** IPC_GETPLAYLISTFILE gets the filename of the playlist entry [index]. +** returns a pointer to it. returns NULL on error. +*/ + + +#define IPC_GETPLAYLISTTITLE 212 +#define IPC_GETPLAYLISTTITLEW 213 +/* (requires Winamp 2.04+, only usable from plug-ins (not external apps)) +** char *name=SendMessage(hwnd_winamp,WM_WA_IPC,index,IPC_GETPLAYLISTTITLE); +** +** IPC_GETPLAYLISTTITLE gets the title of the playlist entry [index]. +** returns a pointer to it. returns NULL on error. +*/ + + +#define IPC_GETHTTPGETTER 240 +/* retrieves a function pointer to a HTTP retrieval function. +** if this is unsupported, returns 1 or 0. +** the function should be: +** int (*httpRetrieveFile)(HWND hwnd, char *url, char *file, char *dlgtitle); +** if you call this function, with a parent window, a URL, an output file, and a dialog title, +** it will return 0 on successful download, 1 on error. +*/ + + +#define IPC_GETHTTPGETTERW 1240 +/* int (*httpRetrieveFileW)(HWND hwnd, char *url, wchar_t *file, wchar_t *dlgtitle); */ + + +#define IPC_MBOPEN 241 +/* (requires Winamp 2.05+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_MBOPEN); +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)url,IPC_MBOPEN); +** IPC_MBOPEN will open a new URL in the minibrowser. if url is NULL, it will open the Minibrowser window. +*/ + + +#define IPC_CHANGECURRENTFILE 245 +/* (requires Winamp 2.05+) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)file,IPC_CHANGECURRENTFILE); +** IPC_CHANGECURRENTFILE will set the current playlist item. +*/ + + +#define IPC_CHANGECURRENTFILEW 1245 +/* (requires Winamp 5.3+) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)file,IPC_CHANGECURRENTFILEW); +** IPC_CHANGECURRENTFILEW will set the current playlist item. +*/ + + +#define IPC_GETMBURL 246 +/* (requires Winamp 2.2+) +** char buffer[4096]; // Urls can be VERY long +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)buffer,IPC_GETMBURL); +** IPC_GETMBURL will retrieve the current Minibrowser URL into buffer. +** buffer must be at least 4096 bytes long. +*/ + + +#define IPC_MBBLOCK 248 +/* (requires Winamp 2.4+) +** SendMessage(hwnd_winamp,WM_WA_IPC,value,IPC_MBBLOCK); +** +** IPC_MBBLOCK will block the Minibrowser from updates if value is set to 1 +*/ + + +#define IPC_MBOPENREAL 249 +/* (requires Winamp 2.4+) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)url,IPC_MBOPENREAL); +** +** IPC_MBOPENREAL works the same as IPC_MBOPEN except that it will works even if +** IPC_MBBLOCK has been set to 1 +*/ + + +#define IPC_ADJUST_OPTIONSMENUPOS 280 +/* (requires Winamp 2.9+) +** int newpos=SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)adjust_offset,IPC_ADJUST_OPTIONSMENUPOS); +** moves where winamp expects the Options menu in the main menu. Useful if you wish to insert a +** menu item above the options/skins/vis menus. +*/ + + +#define IPC_GET_HMENU 281 +/* (requires Winamp 2.9+) +** HMENU hMenu=SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)0,IPC_GET_HMENU); +** values for data: +** 0 : main popup menu +** 1 : main menubar file menu +** 2 : main menubar options menu +** 3 : main menubar windows menu +** 4 : main menubar help menu +** other values will return NULL. +*/ + + +#define IPC_GET_EXTENDED_FILE_INFO 290 //pass a pointer to the following struct in wParam +#define IPC_GET_EXTENDED_FILE_INFO_HOOKABLE 296 +/* (requires Winamp 2.9+) +** to use, create an extendedFileInfoStruct, point the values filename and metadata to the +** filename and metadata field you wish to query, and ret to a buffer, with retlen to the +** length of that buffer, and then SendMessage(hwnd_winamp,WM_WA_IPC,&struct,IPC_GET_EXTENDED_FILE_INFO); +** the results should be in the buffer pointed to by ret. +** returns 1 if the decoder supports a getExtendedFileInfo method +*/ +typedef struct { + const char *filename; + const char *metadata; + char *ret; + size_t retlen; +} extendedFileInfoStruct; + + +#define IPC_GET_BASIC_FILE_INFO 291 //pass a pointer to the following struct in wParam +typedef struct { + const char *filename; + + int quickCheck; // set to 0 to always get, 1 for quick, 2 for default (if 2, quickCheck will be set to 0 if quick wasnot used) + + // filled in by winamp + int length; + char *title; + int titlelen; +} basicFileInfoStruct; + + +#define IPC_GET_BASIC_FILE_INFOW 1291 //pass a pointer to the following struct in wParam +typedef struct { + const wchar_t *filename; + + int quickCheck; // set to 0 to always get, 1 for quick, 2 for default (if 2, quickCheck will be set to 0 if quick wasnot used) + + // filled in by winamp + int length; + wchar_t *title; + int titlelen; +} basicFileInfoStructW; + + +#define IPC_GET_EXTLIST 292 //returns doublenull delimited. GlobalFree() it when done. if data is 0, returns raw extlist, if 1, returns something suitable for getopenfilename +#define IPC_GET_EXTLISTW 1292 // wide char version of above + + +#define IPC_INFOBOX 293 +typedef struct { + HWND parent; + char *filename; +} infoBoxParam; + + +#define IPC_INFOBOXW 1293 +typedef struct { + HWND parent; + const wchar_t *filename; +} infoBoxParamW; + + +#define IPC_SET_EXTENDED_FILE_INFO 294 //pass a pointer to the a extendedFileInfoStruct in wParam +/* (requires Winamp 2.9+) +** to use, create an extendedFileInfoStruct, point the values filename and metadata to the +** filename and metadata field you wish to write in ret. (retlen is not used). and then +** SendMessage(hwnd_winamp,WM_WA_IPC,&struct,IPC_SET_EXTENDED_FILE_INFO); +** returns 1 if the metadata is supported +** Call IPC_WRITE_EXTENDED_FILE_INFO once you're done setting all the metadata you want to update +*/ + + +#define IPC_WRITE_EXTENDED_FILE_INFO 295 +/* (requires Winamp 2.9+) +** writes all the metadata set thru IPC_SET_EXTENDED_FILE_INFO to the file +** returns 1 if the file has been successfully updated, 0 if error +*/ + + +#define IPC_FORMAT_TITLE 297 +typedef struct +{ + char *spec; // NULL=default winamp spec + void *p; + + char *out; + int out_len; + + char * (*TAGFUNC)(const char * tag, void * p); //return 0 if not found + void (*TAGFREEFUNC)(char * tag,void * p); +} waFormatTitle; + + +#define IPC_FORMAT_TITLE_EXTENDED 298 // similiar to IPC_FORMAT_TITLE, but falls back to Winamp's %tags% if your passed tag function doesn't handle it +typedef struct +{ + const wchar_t *filename; + int useExtendedInfo; // set to 1 if you want the Title Formatter to query the input plugins for any tags that your tag function fails on + const wchar_t *spec; // NULL=default winamp spec + void *p; + + wchar_t *out; + int out_len; + + wchar_t * (*TAGFUNC)(const wchar_t * tag, void * p); //return 0 if not found, -1 for empty tag + void (*TAGFREEFUNC)(wchar_t *tag, void *p); +} waFormatTitleExtended; + + +#define IPC_COPY_EXTENDED_FILE_INFO 299 +typedef struct +{ + const char *source; + const char *dest; +} copyFileInfoStruct; + + +#define IPC_COPY_EXTENDED_FILE_INFOW 1299 +typedef struct +{ + const wchar_t *source; + const wchar_t *dest; +} copyFileInfoStructW; + + +typedef struct { + int (*inflateReset)(void *strm); + int (*inflateInit_)(void *strm,const char *version, int stream_size); + int (*inflate)(void *strm, int flush); + int (*inflateEnd)(void *strm); + unsigned long (*crc32)(unsigned long crc, const unsigned char *buf, unsigned int len); +} wa_inflate_struct; + +#define IPC_GETUNCOMPRESSINTERFACE 331 +/* returns a function pointer to uncompress(). +** int (*uncompress)(unsigned char *dest, unsigned long *destLen, const unsigned char *source, unsigned long sourceLen); +** right out of zlib, useful for decompressing zlibbed data. +** if you pass the parm of 0x10100000, it will return a wa_inflate_struct * to an inflate API. +*/ + + +typedef struct _prefsDlgRec { + HINSTANCE hInst; // dll instance containing the dialog resource + int dlgID; // resource identifier of the dialog + void *proc; // window proceedure for handling the dialog defined as + // LRESULT CALLBACK PrefsPage(HWND,UINT,WPARAM,LPARAM) + + char *name; // name shown for the prefs page in the treelist + intptr_t where; // section in the treelist the prefs page is to be added to + // 0 for General Preferences + // 1 for Plugins + // 2 for Skins + // 3 for Bookmarks (no longer in the 5.0+ prefs) + // 4 for Prefs (the old 'Setup' section - no longer in 5.0+) + + intptr_t _id; + struct _prefsDlgRec *next; // no longer implemented as a linked list, now used by Winamp for other means +} prefsDlgRec; + +typedef struct _prefsDlgRecW { + HINSTANCE hInst; // dll instance containing the dialog resource + int dlgID; // resource identifier of the dialog + void *proc; // window proceedure for handling the dialog defined as + // LRESULT CALLBACK PrefsPage(HWND,UINT,WPARAM,LPARAM) + + wchar_t *name; // name shown for the prefs page in the treelist + intptr_t where; // section in the treelist the prefs page is to be added to + // 0 for General Preferences + // 1 for Plugins + // 2 for Skins + // 3 for Bookmarks (no longer in the 5.0+ prefs) + // 4 for Prefs (the old 'Setup' section - no longer in 5.0+) + + intptr_t _id; + struct _prefsDlgRec *next; // no longer implemented as a linked list, now used by Winamp for other means +} prefsDlgRecW; + +#define IPC_ADD_PREFS_DLG 332 +#define IPC_ADD_PREFS_DLGW 1332 +#define IPC_REMOVE_PREFS_DLG 333 +/* (requires Winamp 2.9+) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&prefsRec,IPC_ADD_PREFS_DLG); +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&prefsRec,IPC_REMOVE_PREFS_DLG); +** +** IPC_ADD_PREFS_DLG: +** To use this you need to allocate a prefsDlgRec structure (either on the heap or with +** some global data but NOT on the stack) and then initialise the members of the structure +** (see the definition of the prefsDlgRec structure above). +** +** hInst - dll instance of where the dialog resource is located. +** dlgID - id of the dialog resource. +** proc - dialog window procedure for the prefs dialog. +** name - name of the prefs page as shown in the preferences list. +** where - see above for the valid locations the page can be added. +** +** Then you do SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&prefsRec,IPC_ADD_PREFS_DLG); +** +** example: +** +** prefsDlgRec* prefsRec = 0; +** prefsRec = GlobalAlloc(GPTR,sizeof(prefsDlgRec)); +** prefsRec->hInst = hInst; +** prefsRec->dlgID = IDD_PREFDIALOG; +** prefsRec->name = "Pref Page"; +** prefsRec->where = 0; +** prefsRec->proc = PrefsPage; +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&prefsRec,IPC_ADD_PREFS_DLG); +** +** +** IPC_REMOVE_PREFS_DLG: +** To use you pass the address of the same prefsRec you used when adding the prefs page +** though you shouldn't really ever have to do this but it's good to clean up after you +** when you're plugin is being unloaded. +** +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&prefsRec,IPC_REMOVE_PREFS_DLG); +** +** IPC_ADD_PREFS_DLGW +** requires Winamp 5.53+ +*/ + + +#define IPC_OPENPREFSTOPAGE 380 +/* SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&prefsRec,IPC_OPENPREFSTOPAGE); +** +** There are two ways of opening a preferences page. +** +** The first is to pass an id of a builtin preferences page (see below for ids) or a +** &prefsDlgRec of the preferences page to open and this is normally done if you are +** opening a prefs page you added yourself. +** +** If the page id does not or the &prefsRec is not valid then the prefs dialog will be +** opened to the first page available (usually the Winamp Pro page). +** +** (requires Winamp 5.04+) +** Passing -1 for param will open the preferences dialog to the last page viewed. +** +** Note: v5.0 to 5.03 had a bug in this api +** +** On the first call then the correct prefs page would be opened to but on the next call +** the prefs dialog would be brought to the front but the page would not be changed to the +** specified. +** In 5.04+ it will change to the prefs page specified if the prefs dialog is already open. +*/ + +/* Builtin Preference page ids (valid for 5.0+) +** (stored in the lParam member of the TVITEM structure from the tree item) +** +** These can be useful if you want to detect a specific prefs page and add things to it +** yourself or something like that ;) +** +** Winamp Pro 20 +** General Preferences 0 +** File Types 1 +** Playlist 23 +** Titles 21 +** Playback 42 (added in 5.25) +** Station Info 41 (added in 5.11 & removed in 5.5) +** Video 24 +** Localization 25 (added in 5.5) +** Skins 40 +** Classic Skins 22 +** Plugins 30 +** Input 31 +** Output 32 +** Visualisation 33 +** DSP/Effect 34 +** General Purpose 35 +** +** Note: +** Custom page ids begin from 60 +** The value of the normal custom pages (Global Hotkeys, Jump To File, etc) is not +** guaranteed since it depends on the order in which the plugins are loaded which can +** change on different systems. +** +** Global Hotkeys, Jump To File, Media Library (under General Preferences and child pages), +** Media Library (under Plugins), Portables, CD Ripping and Modern Skins are custom pages +** created by the plugins shipped with Winamp. +*/ + + +#define IPC_GETINIFILE 334 +/* (requires Winamp 2.9+) +** char *ini=(char*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETINIFILE); +** This returns a pointer to the full file path of winamp.ini. +** +** char ini_path[MAX_PATH] = {0}; +** +** void GetIniFilePath(HWND hwnd){ +** if(SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETVERSION) >= 0x2900){ +** // this gets the string of the full ini file path +** lstrcpyn(ini_path,(char*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETINIFILE),sizeof(ini_path)); +** } +** else{ +** char* p = ini_path; +** p += GetModuleFileName(0,ini_path,sizeof(ini_path)) - 1; +** while(p && *p != '.'){p--;} +** lstrcpyn(p+1,"ini",sizeof(ini_path)); +** } +** } +*/ + + +#define IPC_GETINIDIRECTORY 335 +/* (requires Winamp 2.9+) +** char *dir=(char*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETINIDIRECTORY); +** This returns a pointer to the directory where winamp.ini can be found and is +** useful if you want store config files but you don't want to use winamp.ini. +*/ + + +#define IPC_GETPLUGINDIRECTORY 336 +/* (requires Winamp 5.11+) +** char *plugdir=(char*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETPLUGINDIRECTORY); +** This returns a pointer to the directory where Winamp has its plugins stored and is +** useful if you want store config files in plugins.ini in the plugins folder or for +** accessing any local files in the plugins folder. +*/ + + +#define IPC_GETM3UDIRECTORY 337 +/* (requires Winamp 5.11+) +** char *m3udir=(char*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETM3UDIRECTORY); +** This returns a pointer to the directory where winamp.m3u (and winamp.m3u8 if supported) is stored in. +*/ + + +#define IPC_GETM3UDIRECTORYW 338 +/* (requires Winamp 5.3+) +** wchar_t *m3udirW=(wchar_t*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETM3UDIRECTORYW); +** This returns a pointer to the directory where winamp.m3u (and winamp.m3u8 if supported) is stored in. +*/ + + +#define IPC_SPAWNBUTTONPOPUP 361 // param = +// 0 = eject +// 1 = previous +// 2 = next +// 3 = pause +// 4 = play +// 5 = stop + + +#define IPC_OPENURLBOX 360 +/* (requires Winamp 5.0+) +** HGLOBAL hglobal = (HGLOBAL)SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(HWND)parent,IPC_OPENURLBOX); +** You pass a hwnd for the dialog to be parented to (which modern skin support uses). +** This will return a HGLOBAL that needs to be freed with GlobalFree() if this worked. +*/ + + +#define IPC_OPENFILEBOX 362 +/* (requires Winamp 5.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(HWND)parent,IPC_OPENFILEBOX); +** You pass a hwnd for the dialog to be parented to (which modern skin support uses). +*/ + + +#define IPC_OPENDIRBOX 363 +/* (requires Winamp 5.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(HWND)parent,IPC_OPENDIRBOX); +** You pass a hwnd for the dialog to be parented to (which modern skin support uses). +*/ + + +#define IPC_SETDIALOGBOXPARENT 364 +/* (requires Winamp 5.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(HWND)parent,IPC_SETDIALOGBOXPARENT); +** Pass 'parent' as the window which will be used as the parent for a number of the built +** in Winamp dialogs and is useful when you are taking over the whole of the UI so that +** the dialogs will not appear at the bottom right of the screen since the main winamp +** window is located at 3000x3000 by gen_ff when this is used. Call this again with +** parent = null to reset the parent back to the orginal Winamp window. +*/ + +#define IPC_GETDIALOGBOXPARENT 365 +/* (requires Winamp 5.51+) +** HWND hwndParent = SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)0, IPC_GETDIALOGBOXPARENT); +** hwndParent can/must be passed to all modal dialogs (including MessageBox) thats uses winamp as a parent +*/ + +#define IPC_UPDATEDIALOGBOXPARENT 366 +/* (requires Winamp 5.53+) +** if you previous called IPC_SETDIALOGBOXPARENT, call this every time your window resizes +*/ + +#define IPC_DRO_MIN 401 // reserved for DrO +#define IPC_SET_JTF_COMPARATOR 409 +/* pass me an int (__cdecl *)(const char *, const char *) in wParam */ +#define IPC_SET_JTF_COMPARATOR_W 410 +/* pass me an int (__cdecl *)(const wchar_t *, const wchar_t *) in wParam ... maybe someday :) */ +#define IPC_SET_JTF_DRAWTEXT 416 + +#define IPC_DRO_MAX 499 + + +// pass 0 for a copy of the skin HBITMAP +// pass 1 for name of font to use for playlist editor likeness +// pass 2 for font charset +// pass 3 for font size +#define IPC_GET_GENSKINBITMAP 503 + + +typedef struct +{ + HWND me; //hwnd of the window + + #define EMBED_FLAGS_NORESIZE 0x1 + // set this bit to keep window from being resizable + + #define EMBED_FLAGS_NOTRANSPARENCY 0x2 + // set this bit to make gen_ff turn transparency off for this window + + #define EMBED_FLAGS_NOWINDOWMENU 0x4 + // set this bit to prevent gen_ff from automatically adding your window to the right-click menu + + #define EMBED_FLAGS_GUID 0x8 + // (5.31+) call SET_EMBED_GUID(yourEmbedWindowStateStruct, GUID) to define a GUID for this window + + #define SET_EMBED_GUID(windowState, windowGUID) { windowState->flags |= EMBED_FLAGS_GUID; *((GUID *)&windowState->extra_data[4])=windowGUID; } + #define GET_EMBED_GUID(windowState) (*((GUID *)&windowState->extra_data[4])) + + int flags; // see above + + RECT r; + void *user_ptr; // for application use + int extra_data[64]; // for internal winamp use +} embedWindowState; + +#define IPC_GET_EMBEDIF 505 +/* (requires Winamp 2.9+) +** HWND myframe = (HWND)SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&wa_wnd,IPC_GET_EMBEDIF); +** +** or +** +** HWND myframe = 0; +** HWND (*embed)(embedWindowState *params)=0; +** *(void**)&embed = (void*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GET_EMBEDIF); +** myframe = embed(&wa_wnd); +** +** You pass an embedWindowState* and it will return a hwnd for the frame window or if you +** pass wParam as null then it will return a HWND embedWindow(embedWindowState *); +*/ + +#define IPC_SKINWINDOW 534 + +typedef struct __SKINWINDOWPARAM +{ + HWND hwndToSkin; + GUID windowGuid; +} SKINWINDOWPARAM; + + + +#define IPC_EMBED_ENUM 532 +typedef struct embedEnumStruct +{ + int (*enumProc)(embedWindowState *ws, struct embedEnumStruct *param); // return 1 to abort + int user_data; // or more :) +} embedEnumStruct; + // pass + + +#define IPC_EMBED_ISVALID 533 +/* (requires Winamp 2.9+) +** int valid = SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)embedhwnd,IPC_EMBED_ISVALID); +** Pass a hwnd in the wParam to this to check if the hwnd is a valid embed window or not. +*/ + + +#define IPC_CONVERTFILE 506 +/* (requires Winamp 2.92+) +** Converts a given file to a different format (PCM, MP3, etc...) +** To use, pass a pointer to a waFileConvertStruct struct +** This struct can be either on the heap or some global +** data, but NOT on the stack. At least, until the conversion is done. +** +** eg: SendMessage(hwnd_winamp,WM_WA_IPC,&myConvertStruct,IPC_CONVERTFILE); +** +** Return value: +** 0: Can't start the conversion. Look at myConvertStruct->error for details. +** 1: Conversion started. Status messages will be sent to the specified callbackhwnd. +** Be sure to call IPC_CONVERTFILE_END when your callback window receives the +** IPC_CB_CONVERT_DONE message. +*/ +typedef struct +{ + char *sourcefile; // "c:\\source.mp3" + char *destfile; // "c:\\dest.pcm" + intptr_t destformat[8]; // like 'PCM ',srate,nch,bps. + //hack alert! you can set destformat[6]=mmioFOURCC('I','N','I',' '); and destformat[7]=(int)my_ini_file; (where my_ini_file is a char*) + HWND callbackhwnd; // window that will receive the IPC_CB_CONVERT notification messages + + //filled in by winamp.exe + char *error; //if IPC_CONVERTFILE returns 0, the reason will be here + + int bytes_done; //you can look at both of these values for speed statistics + int bytes_total; + int bytes_out; + + int killswitch; // don't set it manually, use IPC_CONVERTFILE_END + intptr_t extra_data[64]; // for internal winamp use +} convertFileStruct; + + +#define IPC_CONVERTFILEW 515 +// (requires Winamp 5.36+) +typedef struct +{ + wchar_t *sourcefile; // "c:\\source.mp3" + wchar_t *destfile; // "c:\\dest.pcm" + intptr_t destformat[8]; // like 'PCM ',srate,nch,bps. + //hack alert! you can set destformat[6]=mmioFOURCC('I','N','I',' '); and destformat[7]=(int)my_ini_file; (where my_ini_file is a char*) + HWND callbackhwnd; // window that will receive the IPC_CB_CONVERT notification messages + + //filled in by winamp.exe + wchar_t *error; //if IPC_CONVERTFILE returns 0, the reason will be here + + int bytes_done; //you can look at both of these values for speed statistics + int bytes_total; + int bytes_out; + + int killswitch; // don't set it manually, use IPC_CONVERTFILE_END + intptr_t extra_data[64]; // for internal winamp use +} convertFileStructW; + + +#define IPC_CONVERTFILE_END 507 +/* (requires Winamp 2.92+) +** Stop/ends a convert process started from IPC_CONVERTFILE +** You need to call this when you receive the IPC_CB_CONVERTDONE message or when you +** want to abort a conversion process +** +** eg: SendMessage(hwnd_winamp,WM_WA_IPC,&myConvertStruct,IPC_CONVERTFILE_END); +** +** No return value +*/ + + +#define IPC_CONVERTFILEW_END 516 +// (requires Winamp 5.36+) + +typedef struct { + HWND hwndParent; + int format; + + //filled in by winamp.exe + HWND hwndConfig; + int extra_data[8]; + //hack alert! you can set extra_data[6]=mmioFOURCC('I','N','I',' '); and extra_data[7]=(int)my_ini_file; (where my_ini_file is a char*) +} convertConfigStruct; + + +#define IPC_CONVERT_CONFIG 508 +#define IPC_CONVERT_CONFIG_END 509 + +typedef struct +{ + void (*enumProc)(intptr_t user_data, const char *desc, int fourcc); + intptr_t user_data; +} converterEnumFmtStruct; +#define IPC_CONVERT_CONFIG_ENUMFMTS 510 +/* (requires Winamp 2.92+) +*/ + +typedef struct +{ + char cdletter; + char *playlist_file; + HWND callback_hwnd; + + //filled in by winamp.exe + char *error; +} burnCDStruct; +#define IPC_BURN_CD 511 +/* (requires Winamp 5.0+) +*/ + +typedef struct +{ + convertFileStruct *cfs; + int priority; +} convertSetPriority; + + +#define IPC_CONVERT_SET_PRIORITY 512 + +typedef struct +{ + convertFileStructW *cfs; + int priority; +} convertSetPriorityW; + + +#define IPC_CONVERT_SET_PRIORITYW 517 +// (requires Winamp 5.36+) + +typedef struct +{ + unsigned int format; //fourcc value + char *item; // config item, eg "bitrate" + char *data; // buffer to recieve, or buffer that contains the data + int len; // length of the data buffer (only used when getting a config item) + char *configfile; // config file to read from +} convertConfigItem; + + +#define IPC_CONVERT_CONFIG_SET_ITEM 513 // returns TRUE if successful +#define IPC_CONVERT_CONFIG_GET_ITEM 514 // returns TRUE if successful + + +typedef struct +{ + const char *filename; + char *title; // 2048 bytes + int length; + int force_useformatting; // can set this to 1 if you want to force a url to use title formatting shit +} waHookTitleStruct; + +#define IPC_HOOK_TITLES 850 +/* (requires Winamp 5.0+) +** If you hook this message and modify the information then make sure to return TRUE. +** If you don't hook the message then make sure you pass it on through the subclass chain. +** +** LRESULT CALLBACK WinampWndProc(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) +** { +** LRESULT ret = CallWindowProc((WNDPROC)WinampProc,hwnd,umsg,wParam,lParam); +** +** if(message==WM_WA_IPC && lParam==IPC_HOOK_TITLES) +** { +** waHookTitleStruct *ht = (waHookTitleStruct *) wParam; +** // Doing ATF stuff with ht->title, whatever... +** return TRUE; +** } +** return ret; +** } +*/ + +typedef struct +{ + const wchar_t *filename; + wchar_t *title; // 2048 characters + int length; + int force_useformatting; // can set this to 1 if you want to force a url to use title formatting shit +} waHookTitleStructW; +#define IPC_HOOK_TITLESW 851 +/* (requires Winamp 5.3+) +** See information on IPC_HOOK_TITLES for how to process this. +*/ + + +#define IPC_GETSADATAFUNC 800 +// 0: returns a char *export_sa_get() that returns 150 bytes of data +// 1: returns a export_sa_setreq(int want); + + +#define IPC_GETVUDATAFUNC 801 +// 0: returns a int export_vu_get(int channel) that returns 0-255 (or -1 for bad channel) + + +#define IPC_ISMAINWNDVISIBLE 900 +/* (requires Winamp 5.0+) +** int visible=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_ISMAINWNDVISIBLE); +** You send this to Winamp to query if the main window is visible or not such as by +** unchecking the option in the main right-click menu. If the main window is visible then +** this will return 1 otherwise it returns 0. +*/ + + +typedef struct +{ + int numElems; + int *elems; + HBITMAP bm; // set if you want to override +} waSetPlColorsStruct; + +#define IPC_SETPLEDITCOLORS 920 +/* (requires Winamp 5.0+) +** This is sent by gen_ff when a modern skin is being loaded to set the colour scheme for +** the playlist editor. When sent numElems is usually 6 and matches with the 6 possible +** colours which are provided be pledit.txt from the classic skins. The elems array is +** defined as follows: +** +** elems = 0 => normal text +** elems = 1 => current text +** elems = 2 => normal background +** elems = 3 => selected background +** elems = 4 => minibroswer foreground +** elems = 5 => minibroswer background +** +** if(uMsg == WM_WA_IPC && lParam == IPC_SETPLEDITCOLORS) +** { +** waSetPlColorsStruct* colStr = (waSetPlColorsStruct*)wp; +** if(colStr) +** { +** // set or inspect the colours being used (basically for gen_ff's benefit) +** } +** } +*/ + + +typedef struct +{ + HWND wnd; + int xpos; // in screen coordinates + int ypos; +} waSpawnMenuParms; + +// waSpawnMenuParms2 is used by the menubar submenus +typedef struct +{ + HWND wnd; + int xpos; // in screen coordinates + int ypos; + int width; + int height; +} waSpawnMenuParms2; + +// the following IPC use waSpawnMenuParms as parameter +#define IPC_SPAWNEQPRESETMENU 933 +#define IPC_SPAWNFILEMENU 934 //menubar +#define IPC_SPAWNOPTIONSMENU 935 //menubar +#define IPC_SPAWNWINDOWSMENU 936 //menubar +#define IPC_SPAWNHELPMENU 937 //menubar +#define IPC_SPAWNPLAYMENU 938 //menubar +#define IPC_SPAWNPEFILEMENU 939 //menubar +#define IPC_SPAWNPEPLAYLISTMENU 940 //menubar +#define IPC_SPAWNPESORTMENU 941 //menubar +#define IPC_SPAWNPEHELPMENU 942 //menubar +#define IPC_SPAWNMLFILEMENU 943 //menubar +#define IPC_SPAWNMLVIEWMENU 944 //menubar +#define IPC_SPAWNMLHELPMENU 945 //menubar +#define IPC_SPAWNPELISTOFPLAYLISTS 946 + + +#define WM_WA_SYSTRAY WM_USER+1 +/* This is sent by the system tray when an event happens (you might want to simulate it). +** +** if(uMsg == WM_WA_SYSTRAY) +** { +** switch(lParam) +** { +** // process the messages sent from the tray +** } +** } +*/ + + +#define WM_WA_MPEG_EOF WM_USER+2 +/* Input plugins send this when they are done playing back the current file to inform +** Winamp or anyother installed plugins that the current +** +** if(uMsg == WM_WA_MPEG_EOF) +** { +** // do what is needed here +** } +*/ + + +//// video stuff + +#define IPC_IS_PLAYING_VIDEO 501 // returns >1 if playing, 0 if not, 1 if old version (so who knows):) +#define IPC_GET_IVIDEOOUTPUT 500 // see below for IVideoOutput interface +#define VIDEO_MAKETYPE(A,B,C,D) ((A) | ((B)<<8) | ((C)<<16) | ((D)<<24)) +#define VIDUSER_SET_INFOSTRING 0x1000 +#define VIDUSER_GET_VIDEOHWND 0x1001 +#define VIDUSER_SET_VFLIP 0x1002 +#define VIDUSER_SET_TRACKSELINTERFACE 0x1003 // give your ITrackSelector interface as param2 +#define VIDUSER_OPENVIDEORENDERER 0x1004 +#define VIDUSER_CLOSEVIDEORENDERER 0x1005 +#define VIDUSER_GETPOPUPMENU 0x1006 +#define VIDUSER_SET_INFOSTRINGW 0x1007 + +typedef struct +{ + int w; + int h; + int vflip; + double aspectratio; + unsigned int fmt; +} VideoOpenStruct; + +#ifndef NO_IVIDEO_DECLARE +#ifdef __cplusplus + +class VideoOutput; +class SubsItem; + +#ifndef _NSV_DEC_IF_H_ +struct YV12_PLANE { + unsigned char* baseAddr; + long rowBytes; +} ; + +struct YV12_PLANES { + YV12_PLANE y; + YV12_PLANE u; + YV12_PLANE v; +}; +#endif + +class IVideoOutput +{ + public: + virtual ~IVideoOutput() { } + virtual int open(int w, int h, int vflip, double aspectratio, unsigned int fmt)=0; + virtual void setcallback(LRESULT (*msgcallback)(void *token, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam), void *token) { (void)token; (void)msgcallback; /* to eliminate warning C4100 */ } + virtual void close()=0; + virtual void draw(void *frame)=0; + virtual void drawSubtitle(SubsItem *item) {UNREFERENCED_PARAMETER(item); } + virtual void showStatusMsg(const char *text) {UNREFERENCED_PARAMETER(text); } + virtual int get_latency() { return 0; } + virtual void notifyBufferState(int bufferstate) { UNREFERENCED_PARAMETER(bufferstate); } /* 0-255*/ + virtual INT_PTR extended(INT_PTR param1, INT_PTR param2, INT_PTR param3) { UNREFERENCED_PARAMETER(param1); UNREFERENCED_PARAMETER(param2); UNREFERENCED_PARAMETER(param3); return 0; } // Dispatchable, eat this! +}; + +class ITrackSelector +{ + public: + virtual int getNumAudioTracks()=0; + virtual void enumAudioTrackName(int n, const char *buf, int size)=0; + virtual int getCurAudioTrack()=0; + virtual int getNumVideoTracks()=0; + virtual void enumVideoTrackName(int n, const char *buf, int size)=0; + virtual int getCurVideoTrack()=0; + + virtual void setAudioTrack(int n)=0; + virtual void setVideoTrack(int n)=0; +}; + +#endif //cplusplus +#endif//NO_IVIDEO_DECLARE + +// these messages are callbacks that you can grab by subclassing the winamp window + +// wParam = +#define IPC_CB_WND_EQ 0 // use one of these for the param +#define IPC_CB_WND_PE 1 +#define IPC_CB_WND_MB 2 +#define IPC_CB_WND_VIDEO 3 +#define IPC_CB_WND_MAIN 4 + +#define IPC_CB_ONSHOWWND 600 +#define IPC_CB_ONHIDEWND 601 + +#define IPC_CB_GETTOOLTIP 602 + +#define IPC_CB_MISC 603 + #define IPC_CB_MISC_TITLE 0 // start of playing/stop/pause + #define IPC_CB_MISC_VOLUME 1 // volume/pan + #define IPC_CB_MISC_STATUS 2 // start playing/stop/pause/ffwd/rwd + #define IPC_CB_MISC_EQ 3 + #define IPC_CB_MISC_INFO 4 + #define IPC_CB_MISC_VIDEOINFO 5 + #define IPC_CB_MISC_TITLE_RATING 6 // (5.5+ for when the rating is changed via the songticker menu on current file) + +/* Example of using IPC_CB_MISC_STATUS to detect the start of track playback with 5.x +** +** if(lParam == IPC_CB_MISC && wParam == IPC_CB_MISC_STATUS) +** { +** if(SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_ISPLAYING) == 1 && +** !SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETOUTPUTTIME)) +** { +** char* file = (char*)SendMessage(hwnd_winamp,WM_WA_IPC, +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLISTPOS),IPC_GETPLAYLISTFILE); +** // only output if a valid file was found +** if(file) +** { +** MessageBox(hwnd_winamp,file,"starting",0); +** // or do something else that you need to do +** } +** } +** } +*/ + + +#define IPC_CB_CONVERT_STATUS 604 // param value goes from 0 to 100 (percent) +#define IPC_CB_CONVERT_DONE 605 + + +#define IPC_ADJUST_FFWINDOWSMENUPOS 606 +/* (requires Winamp 2.9+) +** int newpos=SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)adjust_offset,IPC_ADJUST_FFWINDOWSMENUPOS); +** This will move where Winamp expects the freeform windows in the menubar windows main +** menu. This is useful if you wish to insert a menu item above extra freeform windows. +*/ + + +#define IPC_ISDOUBLESIZE 608 +/* (requires Winamp 5.0+) +** int dsize=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_ISDOUBLESIZE); +** You send this to Winamp to query if the double size mode is enabled or not. +** If it is on then this will return 1 otherwise it will return 0. +*/ + + +#define IPC_ADJUST_FFOPTIONSMENUPOS 609 +/* (requires Winamp 2.9+) +** int newpos=SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)adjust_offset,IPC_ADJUST_FFOPTIONSMENUPOS); +** moves where winamp expects the freeform preferences item in the menubar windows main +** menu. This is useful if you wish to insert a menu item above the preferences item. +** +** Note: This setting was ignored by gen_ff until it was fixed in 5.1 +** gen_ff would assume thatthe menu position was 11 in all cases and so when you +** had two plugins attempting to add entries into the main right click menu it +** would cause the 'colour themes' submenu to either be incorrectly duplicated or +** to just disappear.instead. +*/ + + +#define IPC_GETTIMEDISPLAYMODE 610 +/* (requires Winamp 5.0+) +** int mode=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETTIMEDISPLAYMODE); +** This will return the status of the time display i.e. shows time elapsed or remaining. +** This returns 0 if Winamp is displaying time elapsed or 1 for the time remaining. +*/ + + +#define IPC_SETVISWND 611 +/* (requires Winamp 5.0+) +** int viswnd=(HWND)SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(HWND)viswnd,IPC_SETVISWND); +** This allows you to set a window to receive the following message commands (which are +** used as part of the modern skin integration). +** When you have finished or your visualisation is closed then send wParam as zero to +** ensure that things are correctly tidied up. +*/ + +/* The following messages are received as the LOWORD(wParam) of the WM_COMMAND message. +** See %SDK%\winamp\wa5vis.txt for more info about visualisation integration in Winamp. +*/ +#define ID_VIS_NEXT 40382 +#define ID_VIS_PREV 40383 +#define ID_VIS_RANDOM 40384 +#define ID_VIS_FS 40389 +#define ID_VIS_CFG 40390 +#define ID_VIS_MENU 40391 + + +#define IPC_GETVISWND 612 +/* (requires Winamp 5.0+) +** int viswnd=(HWND)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETVISWND); +** This returns a HWND to the visualisation command handler window if set by IPC_SETVISWND. +*/ + + +#define IPC_ISVISRUNNING 613 +/* (requires Winamp 5.0+) +** int visrunning=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_ISVISRUNNING); +** This will return 1 if a visualisation is currently running and 0 if one is not running. +*/ + + +#define IPC_CB_VISRANDOM 628 // param is status of random + + +#define IPC_SETIDEALVIDEOSIZE 614 +/* (requires Winamp 5.0+) +** This is sent by Winamp back to itself so it can be trapped and adjusted as needed with +** the desired width in HIWORD(wParam) and the desired height in LOWORD(wParam). +** +** if(uMsg == WM_WA_IPC){ +** if(lParam == IPC_SETIDEALVIDEOSIZE){ +** wParam = MAKEWPARAM(height,width); +** } +** } +*/ + + +#define IPC_GETSTOPONVIDEOCLOSE 615 +/* (requires Winamp 5.0+) +** int sovc=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETSTOPONVIDEOCLOSE); +** This will return 1 if 'stop on video close' is enabled and 0 if it is disabled. +*/ + + +#define IPC_SETSTOPONVIDEOCLOSE 616 +/* (requires Winamp 5.0+) +** int sovc=SendMessage(hwnd_winamp,WM_WA_IPC,enabled,IPC_SETSTOPONVIDEOCLOSE); +** Set enabled to 1 to enable and 0 to disable the 'stop on video close' option. +*/ + + +typedef struct { + HWND hwnd; + int uMsg; + WPARAM wParam; + LPARAM lParam; +} transAccelStruct; + +#define IPC_TRANSLATEACCELERATOR 617 +/* (requires Winamp 5.0+) +** (deprecated as of 5.53x+) +*/ + +typedef struct { + int cmd; + int x; + int y; + int align; +} windowCommand; // send this as param to an IPC_PLCMD, IPC_MBCMD, IPC_VIDCMD + + +#define IPC_CB_ONTOGGLEAOT 618 + + +#define IPC_GETPREFSWND 619 +/* (requires Winamp 5.0+) +** HWND prefs = (HWND)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETPREFSWND); +** This will return a handle to the preferences dialog if it is open otherwise it will +** return zero. A simple check with the OS api IsWindow(..) is a good test if it's valid. +** +** e.g. this will open (or close if already open) the preferences dialog and show if we +** managed to get a valid +** SendMessage(hwnd_winamp,WM_COMMAND,MAKEWPARAM(WINAMP_OPTIONS_PREFS,0),0); +** MessageBox(hwnd_winamp,(IsWindow((HWND)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETPREFSWND))?"Valid":"Not Open"),0,MB_OK); +*/ + + +#define IPC_SET_PE_WIDTHHEIGHT 620 +/* (requires Winamp 5.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&point,IPC_SET_PE_WIDTHHEIGHT); +** You pass a pointer to a POINT structure which holds the width and height and Winamp +** will set the playlist editor to that size (this is used by gen_ff on skin changes). +** There does not appear to be any bounds limiting with this so it is possible to create +** a zero size playlist editor window (which is a pretty silly thing to do). +*/ + + +#define IPC_GETLANGUAGEPACKINSTANCE 621 +/* (requires Winamp 5.0+) +** HINSTANCE hInst = (HINSTANCE)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLANGUAGEPACKINSTANCE); +** This will return the HINSTANCE to the currently used language pack file for winamp.exe +** +** (5.5+) +** If you pass 1 in wParam then you will have zero returned if a language pack is in use. +** if(!SendMessage(hwnd_winamp,WM_WA_IPC,1,IPC_GETLANGUAGEPACKINSTANCE)){ +** // winamp is currently using a language pack +** } +** +** If you pass 2 in wParam then you will get the path to the language pack folder. +** wchar_t* lngpackfolder = (wchar_t*)SendMessage(hwnd_winamp,WM_WA_IPC,2,IPC_GETLANGUAGEPACKINSTANCE); +** +** If you pass 3 in wParam then you will get the path to the currently extracted language pack. +** wchar_t* lngpack = (wchar_t*)SendMessage(hwnd_winamp,WM_WA_IPC,3,IPC_GETLANGUAGEPACKINSTANCE); +** +** If you pass 4 in wParam then you will get the name of the currently used language pack. +** wchar_t* lngname = (char*)SendMessage(hwnd_winamp,WM_WA_IPC,4,IPC_GETLANGUAGEPACKINSTANCE); +*/ +#define LANG_IDENT_STR 0 +#define LANG_LANG_CODE 1 +#define LANG_COUNTRY_CODE 2 +/* +** (5.51+) +** If you pass 5 in LOWORD(wParam) then you will get the ident string/code string +** (based on the param passed in the HIWORD(wParam) of the currently used language pack. +** The string returned with LANG_IDENT_STR is used to represent the language that the +** language pack is intended for following ISO naming conventions for consistancy. +** +** wchar_t* ident_str = (wchar_t*)SendMessage(hwnd_winamp,WM_WA_IPC,MAKEWPARAM(5,LANG_XXX),IPC_GETLANGUAGEPACKINSTANCE); +** +** e.g. +** For the default language it will return the following for the different LANG_XXX codes +** LANG_IDENT_STR -> "en-US" (max buffer size of this is 9 wchar_t) +** LANG_LANG_CODE -> "en" (language code) +** LANG_COUNTRY_CODE -> "US" (country code) +** +** On pre 5.51 installs you can get LANG_IDENT_STR using the following method +** (you'll have to custom process the string returned if you want the langugage or country but that's easy ;) ) +** +** #define LANG_PACK_LANG_ID 65534 (if you don't have lang.h) +** HINSTANCE hInst = (HINSTANCE)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLANGUAGEPACKINSTANCE); +** TCHAR buffer[9] = {0}; +** LoadString(hInst,LANG_PACK_LANG_ID,buffer,sizeof(buffer)); +** +** +** +** The following example shows how using the basic api will allow you to load the playlist +** context menu resource from the currently loaded language pack or it will fallback to +** the default winamp.exe instance. +** +** HINSTANCE lang = (HINSTANCE)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLANGUAGEPACKINSTANCE); +** HMENU popup = GetSubMenu(GetSubMenu((LoadMenu(lang?lang:GetModuleHandle(0),MAKEINTRESOURCE(101))),2),5); +** // do processing as needed on the menu before displaying it +** TrackPopupMenuEx(orig,TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_RIGHTBUTTON,rc.left,rc.bottom,hwnd_owner,0); +** DestroyMenu(popup); +** +** If you need a specific menu handle then look at IPC_GET_HMENU for more information. +*/ + + +#define IPC_CB_PEINFOTEXT 622 // data is a string, ie: "04:21/45:02" + + +#define IPC_CB_OUTPUTCHANGED 623 // output plugin was changed in config + + +#define IPC_GETOUTPUTPLUGIN 625 +/* (requires Winamp 5.0+) +** char* outdll = (char*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETOUTPUTPLUGIN); +** This returns a string of the current output plugin's dll name. +** e.g. if the directsound plugin was selected then this would return 'out_ds.dll'. +*/ + + +#define IPC_SETDRAWBORDERS 626 +/* (requires Winamp 5.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,enabled,IPC_SETDRAWBORDERS); +** Set enabled to 1 to enable and 0 to disable drawing of the playlist editor and winamp +** gen class windows (used by gen_ff to allow it to draw its own window borders). +*/ + + +#define IPC_DISABLESKINCURSORS 627 +/* (requires Winamp 5.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,enabled,IPC_DISABLESKINCURSORS); +** Set enabled to 1 to enable and 0 to disable the use of skinned cursors. +*/ + + +#define IPC_GETSKINCURSORS 628 +/* (requires Winamp 5.36+) +** data = (WACURSOR)cursorId. (check wa_dlg.h for values) +*/ + + +#define IPC_CB_RESETFONT 629 + + +#define IPC_IS_FULLSCREEN 630 +/* (requires Winamp 5.0+) +** int val=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_IS_FULLSCREEN); +** This will return 1 if the video or visualisation is in fullscreen mode or 0 otherwise. +*/ + + +#define IPC_SET_VIS_FS_FLAG 631 +/* (requires Winamp 5.0+) +** A vis should send this message with 1/as param to notify winamp that it has gone to or has come back from fullscreen mode +*/ + + +#define IPC_SHOW_NOTIFICATION 632 + + +#define IPC_GETSKININFO 633 +#define IPC_GETSKININFOW 1633 +/* (requires Winamp 5.0+) +** This is a notification message sent to the main Winamp window by itself whenever it +** needs to get information to be shown about the current skin in the 'Current skin +** information' box on the main Skins page in the Winamp preferences. +** +** When this notification is received and the current skin is one you are providing the +** support for then you return a valid buffer for Winamp to be able to read from with +** information about it such as the name of the skin file. +** +** if(uMsg == WM_WA_IPC && lParam == IPC_GETSKININFO){ +** if(is_our_skin()){ +** return is_our_skin_name(); +** } +** } +*/ + + +#define IPC_GET_MANUALPLADVANCE 634 +/* (requires Winamp 5.03+) +** int val=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GET_MANUALPLADVANCE); +** IPC_GET_MANUALPLADVANCE returns the status of the Manual Playlist Advance. +** If enabled this will return 1 otherwise it will return 0. +*/ + + +#define IPC_SET_MANUALPLADVANCE 635 +/* (requires Winamp 5.03+) +** SendMessage(hwnd_winamp,WM_WA_IPC,value,IPC_SET_MANUALPLADVANCE); +** IPC_SET_MANUALPLADVANCE sets the status of the Manual Playlist Advance option. +** Set value = 1 to turn it on and value = 0 to turn it off. +*/ + + +#define IPC_GET_NEXT_PLITEM 636 +/* (requires Winamp 5.04+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_EOF_GET_NEXT_PLITEM); +** +** Sent to Winamp's main window when an item has just finished playback or the next +** button has been pressed and requesting the new playlist item number to go to. +** +** Subclass this message in your application to return the new item number. +** Return -1 for normal Winamp operation (default) or the new item number in +** the playlist to be played instead of the originally selected next track. +** +** This is primarily provided for the JTFE plugin (gen_jumpex.dll). +*/ + + +#define IPC_GET_PREVIOUS_PLITEM 637 +/* (requires Winamp 5.04+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_EOF_GET_PREVIOUS_PLITEM); +** +** Sent to Winamp's main window when the previous button has been pressed and Winamp is +** requesting the new playlist item number to go to. +** +** Return -1 for normal Winamp operation (default) or the new item number in +** the playlist to be played instead of the originally selected previous track. +** +** This is primarily provided for the JTFE plugin (gen_jumpex.dll). +*/ + + +#define IPC_IS_WNDSHADE 638 +/* (requires Winamp 5.04+) +** int is_shaded=SendMessage(hwnd_winamp,WM_WA_IPC,wnd,IPC_IS_WNDSHADE); +** Pass 'wnd' as an id as defined for IPC_GETWND or pass -1 to query the status of the +** main window. This returns 1 if the window is in winshade mode and 0 if it is not. +** Make sure you only test for this on a 5.04+ install otherwise you get a false result. +** (See the notes about unhandled WM_WA_IPC messages). +*/ + + +#define IPC_SETRATING 639 +/* (requires Winamp 5.04+ with ML) +** int rating=SendMessage(hwnd_winamp,WM_WA_IPC,rating,IPC_SETRATING); +** This will allow you to set the 'rating' on the current playlist entry where 'rating' +** is an integer value from 0 (no rating) to 5 (5 stars). +** +** The following example should correctly allow you to set the rating for any specified +** playlist entry assuming of course that you're trying to get a valid playlist entry. +** +** void SetPlaylistItemRating(int item_to_set, int rating_to_set){ +** int cur_pos=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLISTPOS); +** SendMessage(hwnd_winamp,WM_WA_IPC,item_to_set,IPC_SETPLAYLISTPOS); +** SendMessage(hwnd_winamp,WM_WA_IPC,rating_to_set,IPC_SETRATING); +** SendMessage(hwnd_winamp,WM_WA_IPC,cur_pos,IPC_SETPLAYLISTPOS); +** } +*/ + + +#define IPC_GETRATING 640 +/* (requires Winamp 5.04+ with ML) +** int rating=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETRATING); +** This returns the current playlist entry's rating between 0 (no rating) to 5 (5 stars). +** +** The following example should correctly allow you to get the rating for any specified +** playlist entry assuming of course that you're trying to get a valid playlist entry. +** +** int GetPlaylistItemRating(int item_to_get, int rating_to_set){ +** int cur_pos=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETLISTPOS), rating = 0; +** SendMessage(hwnd_winamp,WM_WA_IPC,item_to_get,IPC_SETPLAYLISTPOS); +** rating = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETRATING); +** SendMessage(hwnd_winamp,WM_WA_IPC,cur_pos,IPC_SETPLAYLISTPOS); +** return rating; +** } +*/ + + +#define IPC_GETNUMAUDIOTRACKS 641 +/* (requires Winamp 5.04+) +** int n = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETNUMAUDIOTRACKS); +** This will return the number of audio tracks available from the currently playing item. +*/ + + +#define IPC_GETNUMVIDEOTRACKS 642 +/* (requires Winamp 5.04+) +** int n = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETNUMVIDEOTRACKS); +** This will return the number of video tracks available from the currently playing item. +*/ + + +#define IPC_GETAUDIOTRACK 643 +/* (requires Winamp 5.04+) +** int cur = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETAUDIOTRACK); +** This will return the id of the current audio track for the currently playing item. +*/ + + +#define IPC_GETVIDEOTRACK 644 +/* (requires Winamp 5.04+) +** int cur = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETVIDEOTRACK); +** This will return the id of the current video track for the currently playing item. +*/ + + +#define IPC_SETAUDIOTRACK 645 +/* (requires Winamp 5.04+) +** SendMessage(hwnd_winamp,WM_WA_IPC,track,IPC_SETAUDIOTRACK); +** This allows you to switch to a new audio track (if supported) in the current playing file. +*/ + + +#define IPC_SETVIDEOTRACK 646 +/* (requires Winamp 5.04+) +** SendMessage(hwnd_winamp,WM_WA_IPC,track,IPC_SETVIDEOTRACK); +** This allows you to switch to a new video track (if supported) in the current playing file. +*/ + + +#define IPC_PUSH_DISABLE_EXIT 647 +/* (requires Winamp 5.04+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_PUSH_DISABLE_EXIT); +** This will let you disable or re-enable the UI exit functions (close button, context +** menu, alt-f4). Remember to call IPC_POP_DISABLE_EXIT when you are done doing whatever +** was required that needed to prevent exit otherwise you have to kill the Winamp process. +*/ + + +#define IPC_POP_DISABLE_EXIT 648 +/* (requires Winamp 5.04+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_POP_DISABLE_EXIT); +** See IPC_PUSH_DISABLE_EXIT +*/ + + +#define IPC_IS_EXIT_ENABLED 649 +/* (requires Winamp 5.04+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_IS_EXIT_ENABLED); +** This will return 0 if the 'exit' option of Winamp's menu is disabled and 1 otherwise. +*/ + + +#define IPC_IS_AOT 650 +/* (requires Winamp 5.04+) +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_IS_AOT); +** This will return the status of the always on top flag. +** Note: This may not match the actual TOPMOST window flag while another fullscreen +** application is focused if the user has the 'Disable always on top while fullscreen +** applications are focused' option under the General Preferences page is checked. +*/ + + +#define IPC_USES_RECYCLEBIN 651 +/* (requires Winamp 5.09+) +** int use_bin=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_USES_RECYCLEBIN); +** This will return 1 if the deleted file should be sent to the recycle bin or +** 0 if deleted files should be deleted permanently (default action for < 5.09). +** +** Note: if you use this on pre 5.09 installs of Winamp then it will return 1 which is +** not correct but is due to the way that SendMessage(..) handles un-processed messages. +** Below is a quick case for checking if the returned value is correct. +** +** if(SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_USES_RECYCLEBIN) && +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETVERSION)>=0x5009) +** { +** // can safely follow the option to recycle the file +** } +** else +* { +** // need to do a permanent delete of the file +** } +*/ + + +#define IPC_FLUSHAUDITS 652 +/* +** SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_FLUSHAUDITS); +** +** Will flush any pending audits in the global audits queue +** +*/ + +#define IPC_GETPLAYITEM_START 653 +#define IPC_GETPLAYITEM_END 654 + + +#define IPC_GETVIDEORESIZE 655 +#define IPC_SETVIDEORESIZE 656 + + +#define IPC_INITIAL_SHOW_STATE 657 +/* (requires Winamp 5.36+) +** int show_state = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_INITIAL_SHOW_STATE); +** returns the processed value of nCmdShow when Winamp was started +** (see MSDN documentation the values passed to WinMain(..) for what this should be) +** +** e.g. +** if(SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_INITIAL_SHOW_STATE) == SW_SHOWMINIMIZED){ +** // we are starting minimised so process as needed (keep our window hidden) +** } +** +** Useful for seeing if winamp was run minimised on startup so you can act accordingly. +** On pre-5.36 versions this will effectively return SW_NORMAL/SW_SHOWNORMAL due to the +** handling of unknown apis returning 1 from Winamp. +*/ + +// >>>>>>>>>>> Next is 658 + +#define IPC_PLCMD 1000 + +#define PLCMD_ADD 0 +#define PLCMD_REM 1 +#define PLCMD_SEL 2 +#define PLCMD_MISC 3 +#define PLCMD_LIST 4 + +//#define IPC_MBCMD 1001 + +#define MBCMD_BACK 0 +#define MBCMD_FORWARD 1 +#define MBCMD_STOP 2 +#define MBCMD_RELOAD 3 +#define MBCMD_MISC 4 + +#define IPC_VIDCMD 1002 + +#define VIDCMD_FULLSCREEN 0 +#define VIDCMD_1X 1 +#define VIDCMD_2X 2 +#define VIDCMD_LIB 3 +#define VIDPOPUP_MISC 4 + +//#define IPC_MBURL 1003 //sets the URL +//#define IPC_MBGETCURURL 1004 //copies the current URL into wParam (have a 4096 buffer ready) +//#define IPC_MBGETDESC 1005 //copies the current URL description into wParam (have a 4096 buffer ready) +//#define IPC_MBCHECKLOCFILE 1006 //checks that the link file is up to date (otherwise updates it). wParam=parent HWND +//#define IPC_MBREFRESH 1007 //refreshes the "now playing" view in the library +//#define IPC_MBGETDEFURL 1008 //copies the default URL into wParam (have a 4096 buffer ready) + +#define IPC_STATS_LIBRARY_ITEMCNT 1300 // updates library count status + +/* +** IPC's in the message range 2000 - 3000 are reserved internally for freeform messages. +** These messages are taken from ff_ipc.h which is part of the Modern skin integration. +*/ + +#define IPC_FF_FIRST 2000 + +#define IPC_FF_COLOURTHEME_CHANGE IPC_FF_ONCOLORTHEMECHANGED +#define IPC_FF_ONCOLORTHEMECHANGED IPC_FF_FIRST + 3 +/* +** This is a notification message sent when the user changes the colour theme in a Modern +** skin and can also be detected when the Modern skin is first loaded as the gen_ff plugin +** applies relevant settings and styles (like the colour theme). +** +** The value of wParam is the name of the new color theme being switched to. +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(const char*)colour_theme_name,IPC_FF_ONCOLORTHEMECHANGED); +** +** (IPC_FF_COLOURTHEME_CHANGE is the name i (DrO) was using before getting a copy of +** ff_ipc.h with the proper name in it). +*/ + + +#define IPC_FF_ISMAINWND IPC_FF_FIRST + 4 +/* +** int ismainwnd = (HWND)SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(HWND)test_wnd,IPC_FF_ISMAINWND); +** +** This allows you to determine if the window handle passed to it is a modern skin main +** window or not. If it is a main window or any of its windowshade variants then it will +** return 1. +** +** Because of the way modern skins are implemented, it is possible for this message to +** return a positive test result for a number of window handles within the current Winamp +** process. This appears to be because you can have a visible main window, a compact main +** window and also a winshaded version. +** +** The following code example below is one way of seeing how this api works since it will +** enumerate all windows related to Winamp at the time and allows you to process as +** required when a detection happens. +** +** +** EnumThreadWindows(GetCurrentThreadId(),enumWndProc,0); +** +** BOOL CALLBACK enumWndProc(HWND hwnd, LPARAM lParam){ +** +** if(SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)hwnd,IPC_FF_ISMAINWND)){ +** // do processing in here +** // or continue the enum for other main windows (if they exist) +** // and just comment out the line below +** return 0; +** } +** return 1; +** } +*/ + + +#define IPC_FF_GETCONTENTWND IPC_FF_FIRST + 5 +/* +** HWND wa2embed = (HWND)SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(HWND)test_wnd,IPC_FF_GETCONTENTWND); +** +** This will return the Winamp 2 window that is embedded in the window's container +** i.e. if hwnd is the playlist editor windowshade hwnd then it will return the Winamp 2 +** playlist editor hwnd. +** +** If no content is found such as the window has nothing embedded then this will return +** the hwnd passed to it. +*/ + + +#define IPC_FF_NOTIFYHOTKEY IPC_FF_FIRST + 6 +/* +** This is a notification message sent when the user uses a global hotkey combination +** which had been registered with the gen_hotkeys plugin. +** +** The value of wParam is the description of the hotkey as passed to gen_hotkeys. +** SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(const char*)hotkey_desc,IPC_FF_NOTIFYHOTKEY); +*/ + +#define IPC_FF_LAST 3000 + + +/* +** General IPC messages in Winamp +** +** All notification messages appear in the lParam of the main window message proceedure. +*/ + + +#define IPC_GETDROPTARGET 3001 +/* (requires Winamp 5.0+) +** IDropTarget* IDrop = (IDropTarget*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GETDROPTARGET); +** +** You call this to retrieve a copy of the IDropTarget interface which Winamp created for +** handling external drag and drop operations on to it's Windows. This is only really +** useful if you're providing an alternate interface and want your Windows to provide the +** same drag and drop support as Winamp normally provides the user. Check out MSDN or +** your prefered search facility for more information about the IDropTarget interface and +** what's needed to handle it in your own instance. +*/ + + +#define IPC_PLAYLIST_MODIFIED 3002 +/* (requires Winamp 5.0+) +** This is a notification message sent to the main Winamp window whenever the playlist is +** modified in any way e.g. the addition/removal of a playlist entry. +** +** It is not a good idea to do large amounts of processing in this notification since it +** will slow down Winamp as playlist entries are modified (especially when you're adding +** in a large playlist). +** +** if(uMsg == WM_WA_IPC && lParam == IPC_PLAYLIST_MODIFIED) +** { +** // do what you need to do here +** } +*/ + + +#define IPC_PLAYING_FILE 3003 +/* (requires Winamp 5.0+) +** This is a notification message sent to the main Winamp window when playback begins for +** a file. This passes the full filepath in the wParam of the message received. +** +** if(uMsg == WM_WA_IPC && lParam == IPC_PLAYING_FILE) +** { +** // do what you need to do here, e.g. +** process_file((char*)wParam); +** } +*/ + + +#define IPC_PLAYING_FILEW 13003 +/* (requires Winamp 5.0+) +** This is a notification message sent to the main Winamp window when playback begins for +** a file. This passes the full filepath in the wParam of the message received. +** +** if(uMsg == WM_WA_IPC && lParam == IPC_PLAYING_FILEW) +** { +** // do what you need to do here, e.g. +** process_file((wchar_t*)wParam); +** } +*/ + + +#define IPC_FILE_TAG_MAY_HAVE_UPDATED 3004 +#define IPC_FILE_TAG_MAY_HAVE_UPDATEDW 3005 +/* (requires Winamp 5.0+) +** This is a notification message sent to the main Winamp window when a file's tag +** (e.g. id3) may have been updated. This appears to be sent when the InfoBox(..) function +** of the associated input plugin returns a 1 (which is the file information dialog/editor +** call normally). +** +** if(uMsg == WM_WA_IPC && lParam == IPC_FILE_TAG_MAY_HAVE_UPDATED) +** { +** // do what you need to do here, e.g. +** update_info_on_file_update((char*)wParam); +** } +*/ + + +#define IPC_ALLOW_PLAYTRACKING 3007 +/* (requires Winamp 5.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,allow,IPC_ALLOW_PLAYTRACKING); +** Send allow as nonzero to allow play tracking and zero to disable the mode. +*/ + + +#define IPC_HOOK_OKTOQUIT 3010 +/* (requires Winamp 5.0+) +** This is a notification message sent to the main Winamp window asking if it's okay to +** close or not. Return zero to prevent Winamp from closing or return anything non-zero +** to allow Winamp to close. +** +** The best implementation of this option is to let the message pass through to the +** original window proceedure since another plugin may want to have a say in the matter +** with regards to Winamp closing. +** +** if(uMsg == WM_WA_IPC && lParam == IPC_HOOK_OKTOQUIT) +** { +** // do what you need to do here, e.g. +** if(no_shut_down()) +** { +** return 1; +** } +** } +*/ + + +#define IPC_WRITECONFIG 3011 +/* (requires Winamp 5.0+) +** SendMessage(hwnd_winamp,WM_WA_IPC,write_type,IPC_WRITECONFIG); +** +** Send write_type as 2 to write all config settings and the current playlist. +** +** Send write_type as 1 to write the playlist and common settings. +** This won't save the following ini settings:: +** +** defext, titlefmt, proxy, visplugin_name, dspplugin_name, check_ft_startup, +** visplugin_num, pe_fontsize, visplugin_priority, visplugin_autoexec, dspplugin_num, +** sticon, splash, taskbar, dropaotfs, ascb_new, ttips, riol, minst, whichicon, +** whichicon2, addtolist, snap, snaplen, parent, hilite, disvis, rofiob, shownumsinpl, +** keeponscreen, eqdsize, usecursors, fixtitles, priority, shuffle_morph_rate, +** useexttitles, bifont, inet_mode, ospb, embedwnd_freesize, no_visseh +** (the above was valid for 5.1) +** +** Send write_type as 0 to write the common and less common settings and no playlist. +*/ + + +#define IPC_UPDATE_URL 3012 +// pass the URL (char *) in lparam, will be free()'d on done. + + +#define IPC_GET_RANDFUNC 3015 // returns a function to get a random number +/* (requires Winamp 5.1+) +** int (*randfunc)(void) = (int(*)(void))SendMessage(plugin.hwndParent,WM_WA_IPC,0,IPC_GET_RANDFUNC); +** if(randfunc && randfunc != 1){ +** randfunc(); +** } +** +** This will return a positive 32-bit number (essentially 31-bit). +** The check for a returned value of 1 is because that's the default return value from +** SendMessage(..) when it is not handled so is good to check for it in this situation. +*/ + + +#define IPC_METADATA_CHANGED 3017 +/* (requires Winamp 5.1+) +** int changed=SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)(char*)field,IPC_METADATA_CHANGED); +** a plugin can SendMessage this to winamp if internal metadata has changes. +** wParam should be a char * of what field changed +** +** Currently used for internal actions (and very few at that) the intent of this api is +** to allow a plugin to call it when metadata has changed in the current playlist entry +** e.g.a new id3v2 tag was found in a stream +** +** The wparam value can either be NULL or a pointer to an ansi string for the metadata +** which has changed. This can be thought of as an advanced version of IPC_UPDTITLE and +** could be used to allow for update of the current title when a specific tag changed. +** +** Not recommended to be used since there is not the complete handling implemented in +** Winamp for it at the moment. +*/ + + +#define IPC_SKIN_CHANGED 3018 +/* (requires Winamp 5.1+) +** This is a notification message sent to the main Winamp window by itself whenever +** the skin in use is changed. There is no information sent by the wParam for this. +** +** if(message == WM_WA_IPC && lparam == IPC_SKIN_CHANGED) +** { +** // do what you need to do to handle skin changes, e.g. call WADlg_init(hwnd_winamp); +** } +*/ + + +#define IPC_REGISTER_LOWORD_COMMAND 3019 +/* (requires Winamp 5.1+) +** WORD id = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_REGISTER_LOWORD_COMMAND); +** This will assign you a unique id for making your own commands such as for extra menu +** entries. The starting value returned by this message will potentially change as and +** when the main resource file of Winamp is updated with extra data so assumptions cannot +** be made on what will be returned and plugin loading order will affect things as well. + +** 5.33+ +** If you want to reserve more than one id, you can pass the number of ids required in wParam +*/ + + +#define IPC_GET_DISPATCH_OBJECT 3020 // gets winamp main IDispatch * (for embedded webpages) +#define IPC_GET_UNIQUE_DISPATCH_ID 3021 // gives you a unique dispatch ID that won't conflict with anything in winamp's IDispatch * +#define IPC_ADD_DISPATCH_OBJECT 3022 // add your own dispatch object into winamp's. This lets embedded webpages access your functions +// pass a pointer to DispatchInfo (see below). Winamp makes a copy of all this data so you can safely delete it later +typedef struct +{ + wchar_t *name; // filled in by plugin, make sure it's a unicode string!! (i.e. L"myObject" instead of "myObject). + struct IDispatch *dispatch; // filled in by plugin + DWORD id; // filled in by winamp on return +} DispatchInfo; + +// see IPC_JSAPI2_GET_DISPATCH_OBJECT for version 2 of the Dispatchable scripting interface + +#define IPC_GET_PROXY_STRING 3023 +/* (requires Winamp 5.11+) +** char* proxy_string=(char*)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GET_PROXY_STRING); +** This will return the same string as is shown on the General Preferences page. +*/ + + +#define IPC_USE_REGISTRY 3024 +/* (requires Winamp 5.11+) +** int reg_enabled=SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_USE_REGISTRY); +** This will return 0 if you should leave your grubby hands off the registry (i.e. for +** lockdown mode). This is useful if Winamp is run from a USB drive and you can't alter +** system settings, etc. +*/ + + +#define IPC_GET_API_SERVICE 3025 +/* (requires Winamp 5.12+) +** api_service* api_service = (api_service)SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_GET_API_SERVICE); +** This api will return Winamp's api_service pointer (which is what Winamp3 used, heh). +** If this api is not supported in the Winamp version that is being used at the time then +** the returned value from this api will be 1 which is a good version check. +** +** As of 5.12 there is support for .w5s plugins which reside in %WinampDir%\System and +** are intended for common code that can be shared amongst other plugins e.g. jnetlib.w5s +** which contains jnetlib in one instance instead of being duplicated multiple times in +** all of the plugins which need internet access. +** +** Details on the .w5s specifications will come at some stage (possibly). +*/ + + +typedef struct { + const wchar_t *filename; + const wchar_t *metadata; + wchar_t *ret; + size_t retlen; +} extendedFileInfoStructW; + +#define IPC_GET_EXTENDED_FILE_INFOW 3026 +/* (requires Winamp 5.13+) +** Pass a pointer to the above struct in wParam +*/ + + +#define IPC_GET_EXTENDED_FILE_INFOW_HOOKABLE 3027 +#define IPC_SET_EXTENDED_FILE_INFOW 3028 +/* (requires Winamp 5.13+) +** Pass a pointer to the above struct in wParam +*/ + + +#define IPC_PLAYLIST_GET_NEXT_SELECTED 3029 +/* (requires 5.2+) +** int pl_item = SendMessage(hwnd_winamp,WM_WA_IPC,start,IPC_PLAYLIST_GET_NEXT_SELECTED); +** +** This works just like the ListView_GetNextItem(..) macro for ListView controls. +** 'start' is the index of the playlist item that you want to begin the search after or +** set this as -1 for the search to begin with the first item of the current playlist. +** +** This will return the index of the selected playlist according to the 'start' value or +** it returns -1 if there is no selection or no more selected items according to 'start'. +** +** Quick example: +** +** int sel = -1; +** // keep incrementing the start of the search so we get all of the selected entries +** while((sel = SendMessage(hwnd_winamp,WM_WA_IPC,sel,IPC_PLAYLIST_GET_NEXT_SELECTED))!=-1){ +** // show the playlist file entry of the selected item(s) if there were any +** MessageBox(hwnd_winamp,(char*)SendMessage(hwnd_winamp,WM_WA_IPC,sel,IPC_GETPLAYLISTFILE),0,0); +** } +*/ + + +#define IPC_PLAYLIST_GET_SELECTED_COUNT 3030 +/* (requires 5.2+) +** int selcnt = SendMessage(hwnd_winamp,WM_WA_IPC,0,IPC_PLAYLIST_GET_SELECTED_COUNT); +** This will return the number of selected playlist entries. +*/ + + +#define IPC_GET_PLAYING_FILENAME 3031 +// returns wchar_t * of the currently playing filename + +#define IPC_OPEN_URL 3032 +// send either ANSI or Unicode string (it'll figure it out, but it MUST start with "h"!, so don't send ftp:// or anything funny) +// you can also send this one from another process via WM_COPYDATA (unicode only) + + +#define IPC_USE_UXTHEME_FUNC 3033 +/* (requires Winamp 5.35+) +** int ret = SendMessage(hwnd_winamp,WM_WA_IPC,param,IPC_USE_UXTHEME_FUNC); +** param can be IPC_ISWINTHEMEPRESENT or IPC_ISAEROCOMPOSITIONACTIVE or a valid hwnd. +** +** If you pass a hwnd then it will apply EnableThemeDialogTexture(ETDT_ENABLETAB) +** so your tabbed dialogs can use the correct theme (on supporting OSes ie XP+). +** +** Otherwise this will return a value based on the param passed (as defined below). +** For compatability, the return value will be zero on success (as 1 is returned +** for unsupported ipc calls on older Winamp versions) +*/ + #define IPC_ISWINTHEMEPRESENT 0 +/* This will return 0 if uxtheme.dll is present +** int isthemethere = !SendMessage(hwnd_winamp,WM_WA_IPC,IPC_ISWINTHEMEPRESENT,IPC_USE_UXTHEME_FUNC); +*/ + #define IPC_ISAEROCOMPOSITIONACTIVE 1 +/* This will return 0 if aero composition is active +** int isaero = !SendMessage(hwnd_winamp,WM_WA_IPC,IPC_ISAEROCOMPOSITIONACTIVE,IPC_USE_UXTHEME_FUNC); +*/ + + +#define IPC_GET_PLAYING_TITLE 3034 +// returns wchar_t * of the current title + + +#define IPC_CANPLAY 3035 +// pass const wchar_t *, returns an in_mod * or 0 + + +typedef struct { + // fill these in... + size_t size; // init to sizeof(artFetchData) + HWND parent; // parent window of the dialogue + + // fill as much of these in as you can, otherwise leave them 0 + const wchar_t *artist; + const wchar_t *album; + int year, amgArtistId, amgAlbumId; + + int showCancelAll; // if set to 1, this shows a "Cancel All" button on the dialogue + + // winamp will fill these in if the call returns successfully: + void* imgData; // a buffer filled with compressed image data. free with WASABI_API_MEMMGR->sysFree() + int imgDataLen; // the size of the buffer + wchar_t type[10]; // eg: "jpg" + const wchar_t *gracenoteFileId; // if you know it +} artFetchData; + +#define IPC_FETCH_ALBUMART 3036 +/* pass an artFetchData*. This will show a dialog guiding the use through choosing art, and return when it's finished +** return values: +** 1: error showing dialog +** 0: success +** -1: cancel was pressed +** -2: cancel all was pressed +*/ + +#define IPC_JSAPI2_GET_DISPATCH_OBJECT 3037 +/* pass your service's unique ID, as a wchar_t * string, in wParam +** Winamp will copy the string, so don't worry about keeping it around +** An IDispatch * object will be returned (cast the return value from SendMessage) +** This IDispatch can be used for scripting/automation/VB interaction +** Pass to IE via IDocHostUIHandler::GetExternal and it will become window.external in javscript +*/ + +#define IPC_REGISTER_WINAMP_IPCMESSAGE 65536 +/* (requires Winamp 5.0+) +** DWORD id = SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)name,IPC_REGISTER_WINAMP_IPCMESSAGE); +** The value 'id' returned is > 65536 and is incremented on subsequent calls for unique +** 'name' values passed to it. By using the same 'name' in different plugins will allow a +** common runtime api to be provided for the currently running instance of Winamp +** e.g. +** PostMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)my_param,registered_ipc); +** Have a look at wa_hotkeys.h for an example on how this api is used in practice for a +** custom WM_WA_IPC message. +** +** if(uMsg == WM_WA_IPC && lParam == id_from_register_winamp_ipcmessage){ +** // do things +** } +*/ + +#endif//_WA_IPC_H_ \ No newline at end of file diff --git a/winerrno.h b/winerrno.h new file mode 100644 index 0000000..ac1c3fe --- /dev/null +++ b/winerrno.h @@ -0,0 +1,92 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE libopusfile SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE libopusfile SOURCE CODE IS (C) COPYRIGHT 2012 * + * by the Xiph.Org Foundation and contributors http://www.xiph.org/ * + * * + ********************************************************************/ +#if !defined(_opusfile_winerrno_h) +# define _opusfile_winerrno_h (1) + +# include +# include + +/* These conflict with the MSVC errno.h definitions, but we don't need to use + * the original ones in any file that deals with sockets. + * We could map the WSA errors to the errno.h ones (most of which are only + * available on sufficiently new versions of MSVC), but they aren't ordered the + * same, and given how rarely we actually look at the values, I don't think + * it's worth a lookup table. + */ + +# undef EWOULDBLOCK +# undef EINPROGRESS +# undef EALREADY +# undef ENOTSOCK +# undef EDESTADDRREQ +# undef EMSGSIZE +# undef EPROTOTYPE +# undef ENOPROTOOPT +# undef EPROTONOSUPPORT +# undef EOPNOTSUPP +# undef EAFNOSUPPORT +# undef EADDRINUSE +# undef EADDRNOTAVAIL +# undef ENETDOWN +# undef ENETUNREACH +# undef ENETRESET +# undef ECONNABORTED +# undef ECONNRESET +# undef ENOBUFS +# undef EISCONN +# undef ENOTCONN +# undef ETIMEDOUT +# undef ECONNREFUSED +# undef ELOOP +# undef ENAMETOOLONG +# undef EHOSTUNREACH +# undef ENOTEMPTY + +# define EWOULDBLOCK (WSAEWOULDBLOCK-WSABASEERR) +# define EINPROGRESS (WSAEINPROGRESS-WSABASEERR) +# define EALREADY (WSAEALREADY-WSABASEERR) +# define ENOTSOCK (WSAENOTSOCK-WSABASEERR) +# define EDESTADDRREQ (WSAEDESTADDRREQ-WSABASEERR) +# define EMSGSIZE (WSAEMSGSIZE-WSABASEERR) +# define EPROTOTYPE (WSAEPROTOTYPE-WSABASEERR) +# define ENOPROTOOPT (WSAENOPROTOOPT-WSABASEERR) +# define EPROTONOSUPPORT (WSAEPROTONOSUPPORT-WSABASEERR) +# define ESOCKTNOSUPPORT (WSAESOCKTNOSUPPORT-WSABASEERR) +# define EOPNOTSUPP (WSAEOPNOTSUPP-WSABASEERR) +# define EPFNOSUPPORT (WSAEPFNOSUPPORT-WSABASEERR) +# define EAFNOSUPPORT (WSAEAFNOSUPPORT-WSABASEERR) +# define EADDRINUSE (WSAEADDRINUSE-WSABASEERR) +# define EADDRNOTAVAIL (WSAEADDRNOTAVAIL-WSABASEERR) +# define ENETDOWN (WSAENETDOWN-WSABASEERR) +# define ENETUNREACH (WSAENETUNREACH-WSABASEERR) +# define ENETRESET (WSAENETRESET-WSABASEERR) +# define ECONNABORTED (WSAECONNABORTED-WSABASEERR) +# define ECONNRESET (WSAECONNRESET-WSABASEERR) +# define ENOBUFS (WSAENOBUFS-WSABASEERR) +# define EISCONN (WSAEISCONN-WSABASEERR) +# define ENOTCONN (WSAENOTCONN-WSABASEERR) +# define ESHUTDOWN (WSAESHUTDOWN-WSABASEERR) +# define ETOOMANYREFS (WSAETOOMANYREFS-WSABASEERR) +# define ETIMEDOUT (WSAETIMEDOUT-WSABASEERR) +# define ECONNREFUSED (WSAECONNREFUSED-WSABASEERR) +# define ELOOP (WSAELOOP-WSABASEERR) +# define ENAMETOOLONG (WSAENAMETOOLONG-WSABASEERR) +# define EHOSTDOWN (WSAEHOSTDOWN-WSABASEERR) +# define EHOSTUNREACH (WSAEHOSTUNREACH-WSABASEERR) +# define ENOTEMPTY (WSAENOTEMPTY-WSABASEERR) +# define EPROCLIM (WSAEPROCLIM-WSABASEERR) +# define EUSERS (WSAEUSERS-WSABASEERR) +# define EDQUOT (WSAEDQUOT-WSABASEERR) +# define ESTALE (WSAESTALE-WSABASEERR) +# define EREMOTE (WSAEREMOTE-WSABASEERR) + +#endif diff --git a/wspiapi.c b/wspiapi.c new file mode 100644 index 0000000..d662a1c --- /dev/null +++ b/wspiapi.c @@ -0,0 +1,243 @@ +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#undef __CRT__NO_INLINE +#define __CRT__NO_INLINE +#include +#include "wspiapi.h" + +int WINAPI WspiapiQueryDNS(const char *pszNodeName, int iSocketType, int iProtocol, + WORD wPort, char pszAlias[NI_MAXHOST], struct addrinfo **pptResult) +{ + struct addrinfo **paddrinfo = pptResult; + struct hostent *phost = NULL; + char **h; + + *paddrinfo = NULL; + pszAlias[0] = 0; + phost = gethostbyname (pszNodeName); + if (phost){ + if (phost->h_addrtype == AF_INET && phost->h_length == sizeof(struct in_addr)){ + for (h = phost->h_addr_list; *h != NULL; h++){ + *paddrinfo = WspiapiNewAddrInfo (iSocketType, iProtocol, wPort, + ((struct in_addr *) *h)->s_addr); + if (!*paddrinfo) + return EAI_MEMORY; + paddrinfo = &((*paddrinfo)->ai_next); + } + } + strncpy (pszAlias, phost->h_name, NI_MAXHOST - 1); + pszAlias[NI_MAXHOST - 1] = 0; + return 0; + } + switch(WSAGetLastError()) { + case WSAHOST_NOT_FOUND: break; + case WSATRY_AGAIN: return EAI_AGAIN; + case WSANO_RECOVERY: return EAI_FAIL; + case WSANO_DATA: return EAI_NODATA; + default: break; + } + return EAI_NONAME; +} + +void WINAPI WspiapiLegacyFreeAddrInfo (struct addrinfo *ptHead) +{ + struct addrinfo *p; + + for (p = ptHead; p != NULL; p = ptHead){ + if (p->ai_canonname) + WspiapiFree (p->ai_canonname); + if (p->ai_addr) + WspiapiFree (p->ai_addr); + ptHead = p->ai_next; + WspiapiFree (p); + } +} + +int WINAPI WspiapiClone (WORD wPort, struct addrinfo *ptResult) +{ + struct addrinfo *p = NULL; + struct addrinfo *n = NULL; + + for (p = ptResult; p != NULL;){ + n = WspiapiNewAddrInfo (SOCK_DGRAM, p->ai_protocol, wPort, + ((struct sockaddr_in *) p->ai_addr)->sin_addr.s_addr); + if (!n) break; + n->ai_next = p->ai_next; + p->ai_next = n; + p = n->ai_next; + } + if (p != NULL) return EAI_MEMORY; + + return 0; +} + +int WINAPI WspiapiLookupNode (const char *pszNodeName, + int iSocketType, int iProtocol, + WORD wPort, WINBOOL bAI_CANONNAME, + struct addrinfo **pptResult) +{ + int err = 0, cntAlias = 0; + char name[NI_MAXHOST] = ""; + char alias[NI_MAXHOST] = ""; + char *pname = name, *palias = alias, *tmp = NULL; + + strncpy (pname, pszNodeName, NI_MAXHOST - 1); + pname[NI_MAXHOST - 1] = 0; + for (;;){ + err = WspiapiQueryDNS (pszNodeName, iSocketType, iProtocol, wPort, palias, pptResult); + if (err) break; + if (*pptResult) break; + cntAlias++; + if (strlen (palias) == 0 || !strcmp (pname, palias) || cntAlias == 16){ + err = EAI_FAIL; + break; + } + WspiapiSwap(pname, palias, tmp); + } + if (!err && bAI_CANONNAME){ + (*pptResult)->ai_canonname = WspiapiStrdup (palias); + if (!(*pptResult)->ai_canonname) + err = EAI_MEMORY; + } + return err; +} + +char * WINAPI WspiapiStrdup (const char *pszString) +{ + char *rstr; + size_t szlen; + + if(!pszString) return NULL; + szlen = strlen(pszString) + 1; + rstr = (char *) WspiapiMalloc (szlen); + if (!rstr) return NULL; + strcpy (rstr, pszString); + return rstr; +} + +struct addrinfo * WINAPI WspiapiNewAddrInfo (int iSocketType, int iProtocol, WORD wPort, DWORD dwAddress) +{ + struct addrinfo *n; + struct sockaddr_in *pa; + + if ((n = (struct addrinfo *) WspiapiMalloc (sizeof (struct addrinfo))) == NULL) + return NULL; + if ((pa = (struct sockaddr_in *) WspiapiMalloc (sizeof(struct sockaddr_in))) == NULL){ + WspiapiFree(n); + return NULL; + } + pa->sin_family = AF_INET; + pa->sin_port = wPort; + pa->sin_addr.s_addr = dwAddress; + n->ai_family = PF_INET; + n->ai_socktype = iSocketType; + n->ai_protocol = iProtocol; + n->ai_addrlen = sizeof (struct sockaddr_in); + n->ai_addr = (struct sockaddr *) pa; + return n; +} + +WINBOOL WINAPI WspiapiParseV4Address (const char *pszAddress, PDWORD pdwAddress) +{ + DWORD dwAddress = 0; + const char *h = NULL; + int cnt; + + for (cnt = 0,h = pszAddress; *h != 0; h++) + if (h[0] == '.') cnt++; + if (cnt != 3) return FALSE; + dwAddress = inet_addr (pszAddress); + if (dwAddress == INADDR_NONE) return FALSE; + *pdwAddress = dwAddress; + + return TRUE; +} + +int WINAPI WspiapiLegacyGetAddrInfo(const char *pszNodeName, + const char *pszServiceName, + const struct addrinfo *ptHints, + struct addrinfo **pptResult) +{ + int err = 0, iFlags = 0, iFamily = PF_UNSPEC, iSocketType = 0, iProtocol = 0; + struct in_addr inAddress; + struct servent *svc = NULL; + char *pc = NULL; + WINBOOL isCloned = FALSE; + WORD tcpPort = 0, udpPort = 0, port = 0; + + *pptResult = NULL; + if (!pszNodeName && !pszServiceName) + return EAI_NONAME; + if (ptHints) { + if (ptHints->ai_addrlen != 0 || ptHints->ai_canonname != NULL + || ptHints->ai_addr!=NULL || ptHints->ai_next != NULL) + return EAI_FAIL; + iFlags = ptHints->ai_flags; + if ((iFlags & AI_CANONNAME) != 0 && !pszNodeName) + return EAI_BADFLAGS; + iFamily = ptHints->ai_family; + if (iFamily != PF_UNSPEC && iFamily != PF_INET) + return EAI_FAMILY; + iSocketType = ptHints->ai_socktype; + if (iSocketType != 0 && iSocketType != SOCK_STREAM && iSocketType != SOCK_DGRAM + && iSocketType != SOCK_RAW) + return EAI_SOCKTYPE; + iProtocol = ptHints->ai_protocol; + } + + if (pszServiceName) { + port = (WORD) strtoul (pszServiceName, &pc, 10); + if(*pc == 0) { + port = tcpPort = udpPort = htons (port); + if (iSocketType == 0) { + isCloned = TRUE; + iSocketType = SOCK_STREAM; + } + } else { + if (iSocketType == 0 || iSocketType == SOCK_DGRAM) { + svc = getservbyname(pszServiceName, "udp"); + if (svc) + port = udpPort = svc->s_port; + } + if (iSocketType == 0 || iSocketType == SOCK_STREAM) { + svc = getservbyname(pszServiceName, "tcp"); + if (svc) + port = tcpPort = svc->s_port; + } + if (port == 0) + return (iSocketType ? EAI_SERVICE : EAI_NONAME); + if (iSocketType==0) { + iSocketType = (tcpPort) ? SOCK_STREAM : SOCK_DGRAM; + isCloned = (tcpPort && udpPort); + } + } + } + if (!pszNodeName || WspiapiParseV4Address(pszNodeName,&inAddress.s_addr)) { + if (!pszNodeName) { + inAddress.s_addr = htonl ((iFlags & AI_PASSIVE) ? INADDR_ANY : INADDR_LOOPBACK); + } + *pptResult = WspiapiNewAddrInfo(iSocketType, iProtocol, port, inAddress.s_addr); + if (!(*pptResult)) + err = EAI_MEMORY; + if (!err && pszNodeName) { + (*pptResult)->ai_flags |= AI_NUMERICHOST; + if (iFlags & AI_CANONNAME) { + (*pptResult)->ai_canonname = WspiapiStrdup (inet_ntoa (inAddress)); + if (!(*pptResult)->ai_canonname) + err = EAI_MEMORY; + } + } + } else if (iFlags & AI_NUMERICHOST) + err = EAI_NONAME; + else + err = WspiapiLookupNode (pszNodeName, iSocketType, iProtocol, port, + (iFlags & AI_CANONNAME), pptResult); + if (!err && isCloned) + err = WspiapiClone(udpPort, *pptResult); + if (err) { + WspiapiLegacyFreeAddrInfo (*pptResult); + *pptResult = NULL; + } + return err; +} diff --git a/wspiapi.h b/wspiapi.h new file mode 100644 index 0000000..c95c46a --- /dev/null +++ b/wspiapi.h @@ -0,0 +1,203 @@ +/** + * This file has no copyright assigned and is placed in the Public Domain. + * This file is part of the mingw-w64 runtime package. + * No warranty is given; refer to the file DISCLAIMER.PD within this package. + */ +#ifndef _WSPIAPI_H_ +#define _WSPIAPI_H_ + +#include +#include +#include +#include +#include + +#define _WSPIAPI_STRCPY_S(_Dst,_Size,_Src) strcpy((_Dst),(_Src)) +#define _WSPIAPI_STRCAT_S(_Dst,_Size,_Src) strcat((_Dst),(_Src)) +#define _WSPIAPI_STRNCPY_S(_Dst,_Size,_Src,_Count) strncpy((_Dst),(_Src),(_Count)); (_Dst)[(_Size) - 1] = 0 +#define _WSPIAPI_SPRINTF_S_1(_Dst,_Size,_Format,_Arg1) sprintf((_Dst),(_Format),(_Arg1)) + +#ifndef _WSPIAPI_COUNTOF +#ifndef __cplusplus +#define _WSPIAPI_COUNTOF(_Array) (sizeof(_Array) / sizeof(_Array[0])) +#else +template char (&__wspiapi_countof_helper(__CountofType (&_Array)[__wspiapi_countof_helper_N]))[__wspiapi_countof_helper_N]; +#define _WSPIAPI_COUNTOF(_Array) sizeof(__wspiapi_countof_helper(_Array)) +#endif +#endif + +#define WspiapiMalloc(tSize) calloc(1,(tSize)) +#define WspiapiFree(p) free(p) +#define WspiapiSwap(a,b,c) { (c) = (a); (a) = (b); (b) = (c); } +#define getaddrinfo WspiapiGetAddrInfo +#define getnameinfo WspiapiGetNameInfo +#define freeaddrinfo WspiapiFreeAddrInfo + +typedef int (WINAPI *WSPIAPI_PGETADDRINFO)(const char *nodename,const char *servname,const struct addrinfo *hints,struct addrinfo **res); +typedef int (WINAPI *WSPIAPI_PGETNAMEINFO)(const struct sockaddr *sa,socklen_t salen,char *host,size_t hostlen,char *serv,size_t servlen,int flags); +typedef void (WINAPI *WSPIAPI_PFREEADDRINFO)(struct addrinfo *ai); + +#ifdef __cplusplus +extern "C" { +#endif + typedef struct { + char const *pszName; + FARPROC pfAddress; + } WSPIAPI_FUNCTION; + +#define WSPIAPI_FUNCTION_ARRAY { { "getaddrinfo",(FARPROC) WspiapiLegacyGetAddrInfo }, \ + { "getnameinfo",(FARPROC) WspiapiLegacyGetNameInfo }, \ + { "freeaddrinfo",(FARPROC) WspiapiLegacyFreeAddrInfo } } + + char *WINAPI WspiapiStrdup (const char *pszString); + WINBOOL WINAPI WspiapiParseV4Address (const char *pszAddress,PDWORD pdwAddress); + struct addrinfo * WINAPI WspiapiNewAddrInfo (int iSocketType,int iProtocol,WORD wPort,DWORD dwAddress); + int WINAPI WspiapiQueryDNS (const char *pszNodeName,int iSocketType,int iProtocol,WORD wPort,char pszAlias[NI_MAXHOST],struct addrinfo **pptResult); + int WINAPI WspiapiLookupNode (const char *pszNodeName,int iSocketType,int iProtocol,WORD wPort,WINBOOL bAI_CANONNAME,struct addrinfo **pptResult); + int WINAPI WspiapiClone (WORD wPort,struct addrinfo *ptResult); + void WINAPI WspiapiLegacyFreeAddrInfo (struct addrinfo *ptHead); + int WINAPI WspiapiLegacyGetAddrInfo(const char *pszNodeName,const char *pszServiceName,const struct addrinfo *ptHints,struct addrinfo **pptResult); + int WINAPI WspiapiLegacyGetNameInfo(const struct sockaddr *ptSocketAddress,socklen_t tSocketLength,char *pszNodeName,size_t tNodeLength,char *pszServiceName,size_t tServiceLength,int iFlags); + FARPROC WINAPI WspiapiLoad(WORD wFunction); + int WINAPI WspiapiGetAddrInfo(const char *nodename,const char *servname,const struct addrinfo *hints,struct addrinfo **res); + int WINAPI WspiapiGetNameInfo (const struct sockaddr *sa,socklen_t salen,char *host,size_t hostlen,char *serv,size_t servlen,int flags); + void WINAPI WspiapiFreeAddrInfo (struct addrinfo *ai); + +#ifndef __CRT__NO_INLINE + __CRT_INLINE char * WINAPI + WspiapiStrdup (const char *pszString) + { + char *rstr; + size_t szlen; + + if(!pszString) + return NULL; + szlen = strlen(pszString) + 1; + rstr = (char *) WspiapiMalloc (szlen); + if (!rstr) + return NULL; + strcpy (rstr, pszString); + return rstr; + } + + __CRT_INLINE WINBOOL WINAPI + WspiapiParseV4Address (const char *pszAddress, PDWORD pdwAddress) + { + DWORD dwAddress = 0; + const char *h = NULL; + int cnt; + + for (cnt = 0,h = pszAddress; *h != 0; h++) + if (h[0] == '.') + cnt++; + if (cnt != 3) + return FALSE; + dwAddress = inet_addr (pszAddress); + if (dwAddress == INADDR_NONE) + return FALSE; + *pdwAddress = dwAddress; + return TRUE; + } + + __CRT_INLINE struct addrinfo * WINAPI + WspiapiNewAddrInfo (int iSocketType,int iProtocol, WORD wPort,DWORD dwAddress) + { + struct addrinfo *n; + struct sockaddr_in *pa; + + if ((n = (struct addrinfo *) WspiapiMalloc (sizeof (struct addrinfo))) == NULL) + return NULL; + if ((pa = (struct sockaddr_in *) WspiapiMalloc (sizeof(struct sockaddr_in))) == NULL) + { + WspiapiFree(n); + return NULL; + } + pa->sin_family = AF_INET; + pa->sin_port = wPort; + pa->sin_addr.s_addr = dwAddress; + n->ai_family = PF_INET; + n->ai_socktype = iSocketType; + n->ai_protocol = iProtocol; + n->ai_addrlen = sizeof (struct sockaddr_in); + n->ai_addr = (struct sockaddr *) pa; + return n; + } + + __CRT_INLINE int WINAPI + WspiapiLookupNode (const char *pszNodeName, int iSocketType, int iProtocol, WORD wPort, + WINBOOL bAI_CANONNAME, struct addrinfo **pptResult) + { + int err = 0, cntAlias = 0; + char name[NI_MAXHOST] = ""; + char alias[NI_MAXHOST] = ""; + char *pname = name, *palias = alias, *tmp = NULL; + + strncpy (pname, pszNodeName, NI_MAXHOST - 1); + pname[NI_MAXHOST - 1] = 0; + for (;;) + { + err = WspiapiQueryDNS (pszNodeName, iSocketType, iProtocol, wPort, palias, pptResult); + if (err) + break; + if (*pptResult) + break; + ++cntAlias; + if (strlen (palias) == 0 || !strcmp (pname, palias) || cntAlias == 16) + { + err = EAI_FAIL; + break; + } + WspiapiSwap(pname, palias, tmp); + } + if (!err && bAI_CANONNAME) + { + (*pptResult)->ai_canonname = WspiapiStrdup (palias); + if (!(*pptResult)->ai_canonname) + err = EAI_MEMORY; + } + return err; + } + + __CRT_INLINE int WINAPI + WspiapiClone (WORD wPort,struct addrinfo *ptResult) + { + struct addrinfo *p = NULL; + struct addrinfo *n = NULL; + + for (p = ptResult; p != NULL;) + { + n = WspiapiNewAddrInfo (SOCK_DGRAM, p->ai_protocol, wPort, + ((struct sockaddr_in *) p->ai_addr)->sin_addr.s_addr); + if (!n) + break; + n->ai_next = p->ai_next; + p->ai_next = n; + p = n->ai_next; + } + if (p != NULL) + return EAI_MEMORY; + return 0; + } + + __CRT_INLINE void WINAPI + WspiapiLegacyFreeAddrInfo (struct addrinfo *ptHead) + { + struct addrinfo *p; + + for (p = ptHead; p != NULL; p = ptHead) + { + if (p->ai_canonname) + WspiapiFree (p->ai_canonname); + if (p->ai_addr) + WspiapiFree (p->ai_addr); + ptHead = p->ai_next; + WspiapiFree (p); + } + } +#endif /* !__CRT__NO_INLINE */ + +#ifdef __cplusplus +} +#endif + +#endif