diff options
Diffstat (limited to 'Src/nsv/nsvplay/readers.cpp')
-rw-r--r-- | Src/nsv/nsvplay/readers.cpp | 949 |
1 files changed, 949 insertions, 0 deletions
diff --git a/Src/nsv/nsvplay/readers.cpp b/Src/nsv/nsvplay/readers.cpp new file mode 100644 index 00000000..ff048e70 --- /dev/null +++ b/Src/nsv/nsvplay/readers.cpp @@ -0,0 +1,949 @@ +#include <windows.h> +#include "main.h" +#include <sys/stat.h> + +#ifdef NO_WASABI +#include "../../jnetlib/httpget.h" +api_httpreceiver *CreateGet() +{ + return new JNL_HTTPGet; +} + +void ReleaseGet(api_httpreceiver *&get) +{ + delete (JNL_HTTPGet *)get; + get=0; +} +#else +#include "../..\Components\wac_network\wac_network_http_receiver_api.h" +#include <api.h> +#include <api/service/waservicefactory.h> +#include "../../Winamp/in2.h" +extern In_Module mod; + +waServiceFactory *httpFactory = 0; +api_httpreceiver *CreateGet() +{ + api_httpreceiver *get = 0; + if (!httpFactory && mod.service) + httpFactory = mod.service->service_getServiceByGuid(httpreceiverGUID); + + if (httpFactory) + get = (api_httpreceiver *)httpFactory->getInterface(); + + return get; +} + +void ReleaseGet(api_httpreceiver *&get) +{ + if (!get) + return ; + + if (!httpFactory && mod.service) + waServiceFactory *sf = mod.service->service_getServiceByGuid(httpreceiverGUID); + + if (httpFactory) + httpFactory->releaseInterface(get); + + get = 0; +} + + +#endif +#define MAX_MULTICONNECTS 8 + +class HTTPReader : public IDataReader +{ +public: + HTTPReader(const char *url); + ~HTTPReader(); + size_t read(char *buf, size_t len); + bool iseof() { return !!m_eof; } + char *gettitle() { return m_title; } + char *geterror() { return m_err; } + bool canseek() { return m_content_length != 0xFFFFFFFF && m_accept_ranges && !m_meta_interval; } + int seek(unsigned __int64 newpos) + { + if (!canseek()) return 1; + doConnect((int)newpos); + return 0; + } +unsigned __int64 getsize() { return m_content_length; } + char *getheader(char *header_name) + { + return m_get ? (char *)m_get->getheader(header_name) : NULL; + } +private: + + int serialconnect( int seekto , int timeout); + void doConnect(int seekto); + int getProxyInfo(const char *url, char *out); + char *m_url; + char *m_err; + char *m_title; + int m_eof; + int m_meta_init, m_meta_interval, m_meta_pos, m_meta_size, m_meta_buf_pos; + char m_meta_buf[4096]; + int m_read_headers; + unsigned int m_content_length; + int m_accept_ranges; + int m_is_uvox; + int m_uvox_readpos; + int m_uvox_enough_bytes; + char proxybuf[8400], *proxy; + + api_httpreceiver *m_get; + + // this structure is allocated once, and freed once + struct + { + char *url; //pointers into m_url + // these two are only active temporarily. + api_httpreceiver *get; + char *error; + } + m_cons[MAX_MULTICONNECTS]; + int m_numcons; + int m_newcons; + int m_serialized; + int m_mstimeout; + int m_contryptr; + int m_serialfailed; + int m_useaproxy; +}; + +HTTPReader::HTTPReader(const char *url) +{ + m_title = 0; + m_is_uvox = m_uvox_readpos = 0; + m_meta_init = m_meta_interval = m_meta_pos = m_meta_size = m_meta_buf_pos = 0; + m_meta_buf[0] = 0; + m_err = NULL; + m_eof = 0; + m_read_headers = 0; + m_content_length = 0xFFFFFFFF; + m_accept_ranges = 0; + m_get = NULL; + m_serialized = 0; + m_mstimeout = 0; + m_contryptr = 0; + m_newcons = 0; + m_serialfailed = 0; + m_useaproxy = 0; + + // TCP multiconnect + // JF> using ; as a delimiter is vomit inducing and breaks a lot of other + // code. I petition we use <> to delimit, and I'm making it do that. + + m_numcons = 0; + m_url = _strdup(url); + + int allowproxy = 1; + char *tmpurl = m_url; + while (m_numcons < MAX_MULTICONNECTS) + { + char *next = strstr( tmpurl, "<>" ); + if ( next ) *next = '\0'; + + if (tmpurl[0]) + { + m_cons[m_numcons].error = NULL; + m_cons[m_numcons].get = NULL; + m_cons[m_numcons++].url = tmpurl; + if (!_strnicmp(tmpurl, "uvox:", 5)) allowproxy = 0; + if (!_strnicmp(tmpurl, "order://", 8)) + { + char *p = tmpurl + 8; + // serialized mctp + m_serialized = 1; + m_numcons--; + m_mstimeout = atoi(p); + if ( m_mstimeout < 1 ) + { + m_serialized = 0; + m_mstimeout = 0; + } + } + } + + if (!next) break; + + tmpurl = next + 2; + } + + memset(proxybuf, 0, sizeof(proxybuf)); + proxy = NULL; + if (allowproxy && getProxyInfo(url, proxybuf)) + { + proxy = strstr(proxybuf, "http="); + if (!proxy) proxy = proxybuf; + else + { + proxy += 5; + char *tp = strstr(proxy, ";"); + if (tp) *tp = 0; + } + } + m_is_uvox = 0; + + if ( m_serialized && m_numcons > 1 ) // sanity check + { + int rval = 0, i; + m_newcons = 1; + // walk the list, set the url such that m_cons[0].url points to each item. try to connect + // serialconnect returns error codes -1 on error, 0 on timeout, 1 on successfull connect + for ( i = 0; i < m_numcons; i++ ) + { + if ( i ) + { + m_cons[0].url = m_cons[i].url; + } + rval = serialconnect(0, m_mstimeout); + if ( rval == 1 ) break; + } + if ( rval < 1 ) + { + // we didnt get a connection so... + m_serialfailed = 1; + } + + } + else + doConnect(0); +} + + +void HTTPReader::doConnect(int seekto) +{ + ReleaseGet(m_get); + m_uvox_readpos = 0; + + m_eof = 0; + + + int i; + for (i = 0; i < m_numcons; i++ ) + { + free(m_cons[i].error); + m_cons[i].error = NULL; + ReleaseGet(m_cons[i].get); + m_cons[i].get = CreateGet(); + if (!m_cons[i].get) + break; + m_cons[i].get->open(API_DNS_AUTODNS, 65536, (proxy && proxy[0]) ? proxy : NULL); +#ifdef WINAMP_PLUGIN + m_cons[i].get->addheader("User-Agent:Winamp NSV Player/5.12 (ultravox/2.0)"); +#else + # ifdef WINAMPX + m_cons[i].get->addheader("User-Agent:" UNAGI_USER_AGENT " (ultravox/2.0)"); +# else + m_cons[i].get->addheader("User-Agent:NSV Player/0.0 (ultravox/2.0)"); +# endif +#endif + m_cons[i].get->addheader("Accept:*/*"); + m_cons[i].get->addheader("Connection:close"); + m_cons[i].get->addheader("Ultravox-transport-type: TCP"); + + if (seekto) + { + char buf[64] = {0}; + wsprintfA(buf, "Range:bytes=%d-", seekto); + m_cons[i].get->addheader(buf); + } + else + m_cons[i].get->addheader("icy-metadata:1"); + + m_cons[i].get->connect(m_cons[i].url, !!seekto); + } + m_uvox_enough_bytes = 1; +} + +HTTPReader::~HTTPReader() +{ + ReleaseGet(m_get); + free(m_title); + free(m_err); + free(m_url); + + int i; + for (i = 0; i < m_numcons; i++) + { + ReleaseGet(m_cons[i].get); + free(m_cons[i].error); + } +} + +int HTTPReader::serialconnect(int seekto , int timeout) +{ + + ReleaseGet(m_get); + m_uvox_readpos = 0; + + m_eof = 0; + + int64_t mythen, mynow , myref; + LARGE_INTEGER then, now, ref; + + + QueryPerformanceFrequency( &ref); + myref = ref.QuadPart; + + + QueryPerformanceCounter( &then ); + mythen = then.QuadPart; + + + int timer = 0; + + int i = 0; + + { + ReleaseGet(m_cons[i].get); + m_cons[i].get = CreateGet(); + if (m_cons[i].get == NULL) + return 0; + m_cons[i].get->open(API_DNS_AUTODNS, 65536, (proxy && proxy[0]) ? proxy : NULL); +#ifdef WINAMP_PLUGIN + m_cons[i].get->addheader("User-Agent:Winamp NSV Player/5.12 (ultravox/2.0)"); +#else +# ifdef WINAMPX + m_cons[i].get->addheader("User-Agent:" UNAGI_USER_AGENT " (ultravox/2.0)"); +# else + m_cons[i].get->addheader("User-Agent:NSV Player/0.0 (ultravox/2.0)"); +# endif +#endif + m_cons[i].get->addheader("Accept:*/*"); + m_cons[i].get->addheader("Connection:close"); + m_cons[i].get->addheader("Ultravox-transport-type: TCP"); + + if (seekto) + { + char buf[64] = {0}; + wsprintfA(buf, "Range:bytes=%d-", seekto); + m_cons[i].get->addheader(buf); + } + else m_cons[i].get->addheader("icy-metadata:1"); + + m_cons[i].get->connect(m_cons[i].url, !!seekto); + } + m_uvox_enough_bytes = 1; + + int ret, status; + + if (!m_get) + { + if (m_err) return 0; + int i; + int found = 0; + i = 0; + while ( timer < timeout ) + { + if (!m_cons[i].get) return 0; + found = 1; + + QueryPerformanceCounter( &now ); + mynow = now.QuadPart; + + float profiletime = (float)(mynow - mythen); + profiletime /= myref; + profiletime *= 1000.0; + timer = (int) profiletime; + + ret = m_cons[i].get->run(); + status = m_cons[i].get->get_status(); + + if (ret < 0 || status < 0) + { + const char *t = m_cons[i].get->geterrorstr(); + if (t) + {} + ReleaseGet(m_cons[i].get); + break; + } + + if ( status > 0 ) + { + int code = m_cons[i].get->getreplycode(); + if ( code < 200 || code > 299 ) + { + ReleaseGet(m_cons[i].get); + //wsprintf( m_cons[i].error, "Error: Server returned %d", code ); + break; + } + else + { + // we're in good shape, make our getter current, and delete all the gay ones + ReleaseGet(m_get); // just in case, probably zero anyway + m_get = m_cons[i].get; + m_cons[i].get = NULL; + + // trash i here, but we are breaking anyway :) + /* for (i = 0; i < m_numcons; i++) + { + delete m_cons[i].get; + m_cons[i].get = NULL; + free( m_cons[i].error ); + m_cons[i].error = NULL; + }*/ + break; + } + } +#ifdef _WIN32 + Sleep(1); +#else + usleep(1000); +#endif + } // while + if ( timer > timeout ) + { + ReleaseGet(m_cons[i].get); + ReleaseGet(m_get); + return 0; + } + + + if (!m_get) + { + return 0; + } + } + if ( m_get ) return 1; + else return 0; +} + + +size_t HTTPReader::read(char *buffer, size_t len) +{ + int ret, status; + + if (!m_get) + { + if (m_err) return 0; + int i; + int found = 0; + for (i = 0; !m_get && i < m_numcons; i++) + { + if (!m_cons[i].get) continue; + found = 1; + + ret = m_cons[i].get->run(); + status = m_cons[i].get->get_status(); + + if (ret < 0 || status < 0) + { + const char *t = m_cons[i].get->geterrorstr(); + if (t) + { + free(m_cons[i].error); + m_cons[i].error = _strdup( t ); + } + ReleaseGet(m_cons[i].get); + + } + + if ( status > 0 ) + { + int code = m_cons[i].get->getreplycode(); + if ( code < 200 || code > 299 ) + { + ReleaseGet(m_cons[i].get); + m_cons[i].get = NULL; + + free(m_cons[i].error); + m_cons[i].error = (char *)malloc( 100 ); + wsprintfA( m_cons[i].error, "Error: Server returned %d", code ); + } + else + { + // we're in good shape, make our getter current, and delete all the gay ones + ReleaseGet(m_get); // just in case, probably zero anyway + m_get = m_cons[i].get; + m_cons[i].get = NULL; + + // trash i here, but we are breaking anyway :) + for (i = 0; i < m_numcons; i++) + { + ReleaseGet(m_cons[i].get); + free( m_cons[i].error ); + m_cons[i].error = NULL; + } + break; // exit loop of connections + } + } + } // loop of connections + + if (!found) // out of attempted connections heh + { + free( m_err ); + if (m_numcons > 1) + { + size_t size = 0; + for (i = 0; i < m_numcons; i++) + if ( m_cons[i].error ) size += strlen( m_cons[i].error ) + 1; + + m_err = (char *)malloc(size + 100); + wsprintfA( m_err, "No Valid Multiconnect URLs (%d);", m_numcons ); + for (i = 0; i < m_numcons; i++) + { + strcat( m_err, m_cons[i].error ); + strcat( m_err, ";" ); + free(m_cons[i].error); + m_cons[i].error = NULL; + + } + } + else + { + m_err = m_cons[0].error; + m_cons[0].error = NULL; + if (!m_err) m_err = _strdup("Connection error (Invalid URL?)"); + } + } + if (!m_get) return 0; + } + + ret = m_get->run(); + status = m_get->get_status(); + + if (ret > 0 && (!m_get->bytes_available() || !m_uvox_enough_bytes) && status > 1) + { + m_eof = 1; + } + + if (ret < 0 || status < 0) + { + const char *t = m_get->geterrorstr(); + if (t) + { + free( m_err ); + m_err = (char *)malloc( strlen( t) + 16 ); + wsprintfA( m_err, "Error: %s", t ); + return 0; + } + } + + if (status > 0) + { + if (!m_read_headers) + { + if (status > 1) + { + const char *v = m_get->getheader("Content-Length"); + if (v) m_content_length = atoi(v); + v = m_get->getheader("Accept-Ranges"); + if (v) while (v && *v) + { + if (!_strnicmp(v, "bytes", 5)) + { + m_accept_ranges = 1; + break; + } + v++; + } + v = m_get->getheader("icy-metaint"); + if (v) + { + m_meta_interval = atoi(v); + } + if (!m_title) + { + v = m_get->getheader("icy-name"); + if (v) + m_title = _strdup(v); + } +#ifdef WINAMP_PLUGIN + extern void process_url(char *url); + v = m_get->getheader("icy-url"); + if (v && !strstr(v, "shoutcast.com")) + { + char *p = (char *)v; while (p && *p && *p == ' ') p++; + process_url(p); + } +#endif + + v = m_get->getheader("content-type"); + if (v && !_stricmp(v, "misc/ultravox")) + { + v = m_get->getheader("ultravox-max-msg"); + if (v) m_is_uvox = atoi(v); + if (!m_is_uvox) m_is_uvox = 16000; + } + if (!m_title) + { + v = m_get->getheader("content-disposition"); + if (v) v = strstr(v, "filename="); + if (v) + { + m_title = _strdup(v + 9); + } + } + m_read_headers = 1; + } + } + + size_t l = m_get->bytes_available(); + if (m_is_uvox) + { + again: + if (l >= 6) + { + unsigned char buf[32768*2] = {0}; + m_get->peek_bytes((char *)buf, 6); + if (buf[0] != 0x5A) + { + l--; + m_get->get_bytes((char *)buf, 1); + goto again; + } + int resqos = buf[1]; + int classtype = (buf[2] << 8) | buf[3]; + int msglen = (buf[4] << 8) | buf[5]; + if (msglen > m_is_uvox) // length is too long + { + m_get->get_bytes((char *)buf, 1); + l--; + goto again; + } + if (msglen + 7 <= (int)l) + { + m_uvox_enough_bytes = 1; + + m_get->peek_bytes((char *)buf, msglen + 7); + if (buf[msglen + 6]) + { + m_get->get_bytes((char *)buf, 1); + l--; + goto again; + } + if (classtype == 0x7777) // take any data for now, ignore all other frames + { + l = msglen - m_uvox_readpos; + if (l > len) l = len; + memcpy(buffer, buf + 6 + m_uvox_readpos, l); + m_uvox_readpos += (int)l; + + if (m_uvox_readpos >= msglen) + { + m_uvox_readpos = 0; + m_get->get_bytes((char *)buf, msglen + 7); + + } + return l; + +#ifdef WINAMP_PLUGIN + + } + else if ( classtype == 0x3001 ) + { + extern void process_metadata(char *buf, int size); + m_get->get_bytes((char *)buf, msglen + 7); + process_metadata((char*)buf + 6, msglen + 1); +#endif + + } + else + { + m_get->get_bytes((char *)buf, msglen + 7); + } + } + else + { + m_uvox_enough_bytes = 0; + } + } + return 0; + } + else + { + if (l > len) l = len; + m_get->get_bytes(buffer, (int)l); + + if (m_meta_interval) + { + int x = (int)l; + unsigned char *buf = (unsigned char *)buffer; + if (m_meta_size) // already in meta block + { + int len = min(x, m_meta_size - m_meta_buf_pos); + + memcpy(m_meta_buf + m_meta_buf_pos, buf, len); + m_meta_buf_pos += len; + + if (m_meta_buf_pos == m_meta_size) + { + // if(metacb) metacb->metaDataReader_onData(m_meta_buf,m_meta_size); + m_meta_buf_pos = 0; + m_meta_size = 0; + m_meta_pos = 0; + } + + x -= len; + if (x) memcpy(buf, buf + len, x); + } + else if (m_meta_pos + x > m_meta_interval) // block contains meta data somewhere in it, and we're not alreayd reading a block + { + int start_offs = m_meta_interval - m_meta_pos; + int len; + m_meta_size = ((unsigned char *)buf)[start_offs] * 16; + + len = min(x - start_offs - 1, m_meta_size); + + if (len) memcpy(m_meta_buf, buf + start_offs + 1, len); + m_meta_buf_pos = len; + + if (m_meta_buf_pos == m_meta_size) // full read of metadata successful + { + x -= m_meta_size + 1; + if (x > start_offs) memcpy(buf + start_offs, buf + start_offs + 1 + m_meta_size, x - start_offs); +#ifdef WINAMP_PLUGIN + extern void process_metadata(char *buf, int size); + process_metadata(m_meta_buf, m_meta_size); +#endif + //if(metacb) metacb->metaDataReader_onData(m_meta_buf,m_meta_size); + m_meta_buf_pos = 0; + m_meta_pos = -start_offs; + m_meta_size = 0; + } + else + { + x = start_offs; // otherwise, there's only the first block of data + } + } + if (x > 0) + { + m_meta_pos += x; + } + l = x; + } // end of poopie metadata + } // !uvox + +#if 0 + { + FILE *fh = fopen("c:\\dump.nsv", "ab"); + fwrite(buffer, 1, l, fh); + fclose(fh); + } +#endif + + return l; + } + return 0; +} + +static void parseURL(char *url, char *lp, char *host, int *port, char *req) +{ + char *p, *np; + /* if (_strnicmp(url,"http://",4) && + _strnicmp(url,"icy://",6) && + _strnicmp(url,"sc://",6) && + _strnicmp(url,"shoutcast://",12)) return; + */ + np = p = strstr(url, "://"); + if (!np) np = (char*)url; + else np += 3; + if (!p) p = (char*)url; + else p += 3; + + while (np && *np != '/' && *np) *np++; + if (np && *np) + { + lstrcpynA(req, np, 2048); + *np++ = 0; + } + else strcpy(req, "/"); + np = p; + while (np && *np != '@' && *np) np++; + if (np && *np) + { + *np++ = 0; + lstrcpynA(lp, p, 256); + p = np; + } + else lp[0] = 0; + np = p; + while (np && *np != ':' && *np) np++; + if (*np) + { + *np++ = 0; + *port = atoi(np); + } + else *port = 80; + lstrcpynA(host, p, 256); +} + +int HTTPReader::getProxyInfo(const char *url, char *out) +{ +#ifndef WINAMPX + char INI_FILE[MAX_PATH] = {0}; + char *p; + GetModuleFileNameA(NULL, INI_FILE, sizeof(INI_FILE)); + p = INI_FILE + strlen(INI_FILE); + while (p >= INI_FILE && *p != '.') p--; + strcpy(++p, "ini"); + GetPrivateProfileStringA("Winamp", "proxy", "", out, 8192, INI_FILE); + return !!out[0]; +#else + DWORD v = 0; + HKEY hKey; + + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\AOL\\Unagi", 0, KEY_READ, &hKey) == ERROR_SUCCESS) + { + DWORD l = 4; + DWORD t; + if (RegQueryValueEx(hKey, "ProxyEnable", NULL, &t, (unsigned char *)&v, &l) == ERROR_SUCCESS && t == REG_DWORD) + { + if ( v != 2 ) + { + l = 8192; + if (RegQueryValueEx(hKey, "ProxyServer", NULL, &t, (unsigned char *)out, &l ) != ERROR_SUCCESS || t != REG_SZ) + { + v = 0; + *out = 0; + } + } + else return 0; + } + else v = 0; + out[512 - 1] = 0; + RegCloseKey(hKey); + } + if ( !v && m_useaproxy ) + { + char blah[8192] = ""; + lstrcpyn(blah, url, 8192); + char plp[512] = {0}; + char phost[512] = {0}; + int pport = 80; + char pthereq[1024] = {0}; + parseURL(blah, plp, phost, &pport, pthereq); + v = ResolvProxyFromURL(url, phost, out); + if ( v < 0 ) v = 0; // error getting proxy + } + if ( v > 0) + { + char prox[1024] = {0}; + wsprintf(prox, "PROXY: %s", out); + SendMetadata(prox, 1); + } + return v; +#endif +} + + + +class Win32FileReader : public IDataReader +{ +public: + Win32FileReader(HANDLE file) { m_hFile = file; m_eof = 0; m_err = NULL; } + ~Win32FileReader() { CloseHandle(m_hFile); } + size_t read(char *buf, size_t len) + { + DWORD ob = 0; + if (!len) return 0; + if (!ReadFile(m_hFile, buf, (DWORD)len, &ob, NULL)) + { + m_err = "Error calling ReadFile()!"; + return 0; + } + else if (!ob) m_eof = true; + return ob; + } +bool iseof() { return m_eof; } + bool canseek() { return 1; } + int seek(uint64_t newpos) + { + LARGE_INTEGER li; + li.QuadPart = newpos; + li.LowPart = SetFilePointer (m_hFile, li.LowPart, &li.HighPart, SEEK_SET); + + if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) + { + li.QuadPart = -1; + } + + return li.QuadPart== ~0; + } + + uint64_t getsize() + { + LARGE_INTEGER position; + position.QuadPart=0; + position.LowPart = GetFileSize(m_hFile, (LPDWORD)&position.HighPart); + + if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) + return INVALID_FILE_SIZE; + else + return position.QuadPart; + } + + char *geterror() { return m_err; } +private: + HANDLE m_hFile; + bool m_eof; + char *m_err; +}; + + +#define VAR_TO_FPOS(fpos, var) (fpos) = (var) +#define FPOS_TO_VAR(fpos, typed, var) (var) = (typed)(fpos) +class FileReader : public IDataReader +{ +public: + FileReader(FILE *file) { fp = file; m_err = NULL; } + ~FileReader() { fclose(fp); } + size_t read(char *buf, size_t len) + { + size_t ob; + if (!len) return 0; + ob = fread(buf, 1, len, fp); + if (!ob && ferror(fp)) + { + m_err = "Error calling fread()!"; + return 0; + } + return ob; + } + bool iseof() { return !!feof(fp); } + bool canseek() { return 1; } + int seek(uint64_t newpos) + { + fpos_t pos= newpos; + VAR_TO_FPOS(pos, newpos); + return fsetpos(fp, &pos); + } + + unsigned __int64 getsize() + { + struct stat s; + if (fstat(fileno(fp), &s) < 0) + { + m_err = "Error calling fread()!"; + return 0; + } + return s.st_size; + } + + char *geterror() { return m_err; } +private: + FILE *fp; + char *m_err; +}; + + +IDataReader *CreateReader(const char *url) +{ + if (strstr(url, "://")) return new HTTPReader(url); +#ifdef _WIN32 + HANDLE hFile = CreateFileA(url, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); + if (hFile != INVALID_HANDLE_VALUE) + return new Win32FileReader(hFile); +#else + FILE *fp = fopen(url, "r"); + if (fp) + return new FileReader(fp); +#endif + return NULL; +} + |