717 lines
16 KiB
C++
717 lines
16 KiB
C++
#include "main.h"
|
|
#include "VirtualIO.h"
|
|
#include "api__in_mp4.h"
|
|
#include "api/service/waservicefactory.h"
|
|
#include "../../..\Components\wac_network\wac_network_http_receiver_api.h"
|
|
#include "../nu/AutoChar.h"
|
|
#include "../nu/ProgressTracker.h"
|
|
|
|
#include <assert.h>
|
|
#include <strsafe.h>
|
|
|
|
#define HTTP_BUFFER_SIZE 65536
|
|
// {C0A565DC-0CFE-405a-A27C-468B0C8A3A5C}
|
|
static const GUID internetConfigGroupGUID =
|
|
{
|
|
0xc0a565dc, 0xcfe, 0x405a, { 0xa2, 0x7c, 0x46, 0x8b, 0xc, 0x8a, 0x3a, 0x5c }
|
|
};
|
|
|
|
static void SetUserAgent(api_httpreceiver *http)
|
|
{
|
|
char agent[256] = {0};
|
|
StringCchPrintfA(agent, 256, "User-Agent: %S/%S", WASABI_API_APP->main_getAppName(), WASABI_API_APP->main_getVersionNumString());
|
|
http->addheader(agent);
|
|
}
|
|
|
|
static api_httpreceiver *SetupConnection(const char *url, uint64_t start_position, uint64_t end_position)
|
|
{
|
|
api_httpreceiver *http = 0;
|
|
waServiceFactory *sf = mod.service->service_getServiceByGuid(httpreceiverGUID);
|
|
if (sf) http = (api_httpreceiver *)sf->getInterface();
|
|
|
|
if (!http)
|
|
return http;
|
|
|
|
int use_proxy = 1;
|
|
bool proxy80 = AGAVE_API_CONFIG->GetBool(internetConfigGroupGUID, L"proxy80", false);
|
|
if (proxy80 && strstr(url, ":") && (!strstr(url, ":80/") && strstr(url, ":80") != (url + strlen(url) - 3)))
|
|
use_proxy = 0;
|
|
|
|
const wchar_t *proxy = use_proxy?AGAVE_API_CONFIG->GetString(internetConfigGroupGUID, L"proxy", 0):0;
|
|
http->open(API_DNS_AUTODNS, HTTP_BUFFER_SIZE, (proxy && proxy[0]) ? (const char *)AutoChar(proxy) : NULL);
|
|
if (start_position && start_position != (uint64_t)-1)
|
|
{
|
|
if (end_position == (uint64_t)-1)
|
|
{
|
|
char temp[128] = {0};
|
|
StringCchPrintfA(temp, 128, "Range: bytes=%I64u-", start_position);
|
|
http->addheader(temp);
|
|
}
|
|
else
|
|
{
|
|
char temp[128] = {0};
|
|
StringCchPrintfA(temp, 128, "Range: bytes=%I64u-%I64u", start_position, end_position);
|
|
http->addheader(temp);
|
|
}
|
|
}
|
|
SetUserAgent(http);
|
|
http->connect(url);
|
|
return http;
|
|
}
|
|
|
|
static DWORD CALLBACK ProgressiveThread(LPVOID param);
|
|
|
|
static __int64 Seek64(HANDLE hf, __int64 distance, DWORD MoveMethod)
|
|
{
|
|
LARGE_INTEGER li;
|
|
|
|
li.QuadPart = distance;
|
|
|
|
li.LowPart = SetFilePointer (hf, li.LowPart, &li.HighPart, MoveMethod);
|
|
|
|
if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
|
|
{
|
|
li.QuadPart = -1;
|
|
}
|
|
|
|
return li.QuadPart;
|
|
}
|
|
|
|
int bufferCount;
|
|
static void Buffering(int bufStatus, const wchar_t *displayString)
|
|
{
|
|
if (bufStatus < 0 || bufStatus > 100)
|
|
return;
|
|
|
|
char tempdata[75*2] = {0, };
|
|
|
|
int csa = mod.SAGetMode();
|
|
if (csa & 1)
|
|
{
|
|
for (int x = 0; x < bufStatus*75 / 100; x ++)
|
|
tempdata[x] = x * 16 / 75;
|
|
}
|
|
else if (csa&2)
|
|
{
|
|
int offs = (csa & 1) ? 75 : 0;
|
|
int x = 0;
|
|
while (x < bufStatus*75 / 100)
|
|
{
|
|
tempdata[offs + x++] = -6 + x * 14 / 75;
|
|
}
|
|
while (x < 75)
|
|
{
|
|
tempdata[offs + x++] = 0;
|
|
}
|
|
}
|
|
else if (csa == 4)
|
|
{
|
|
tempdata[0] = tempdata[1] = (bufStatus * 127 / 100);
|
|
}
|
|
if (csa) mod.SAAdd(tempdata, ++bufferCount, (csa == 3) ? 0x80000003 : csa);
|
|
|
|
/*
|
|
TODO
|
|
wchar_t temp[64] = {0};
|
|
StringCchPrintf(temp, 64, L"%s: %d%%",displayString, bufStatus);
|
|
SetStatus(temp);
|
|
*/
|
|
//SetVideoStatusText(temp); // TODO: find a way to set the old status back
|
|
// videoOutput->notifyBufferState(static_cast<int>(bufStatus*2.55f));
|
|
}
|
|
|
|
class ProgressiveReader
|
|
{
|
|
public:
|
|
ProgressiveReader(const char *url, HANDLE killswitch) : killswitch(killswitch)
|
|
{
|
|
thread_abort = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
download_thread = 0;
|
|
progressive_file_read = 0;
|
|
progressive_file_write = 0;
|
|
|
|
content_length=0;
|
|
current_position=0;
|
|
stream_disconnected=false;
|
|
connected=false;
|
|
end_of_file=false;
|
|
|
|
wchar_t temppath[MAX_PATH-14] = {0}; // MAX_PATH-14 'cause MSDN said so
|
|
GetTempPathW(MAX_PATH-14, temppath);
|
|
GetTempFileNameW(temppath, L"wdl", 0, filename);
|
|
this->url = _strdup(url);
|
|
http = SetupConnection(url, 0, (uint64_t)-1);
|
|
|
|
progressive_file_read = CreateFileW(filename, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0);
|
|
progressive_file_write = CreateFileW(filename, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0);
|
|
download_thread = CreateThread(0, 0, ProgressiveThread, this, 0, 0);
|
|
|
|
while (!connected && !stream_disconnected && WaitForSingleObject(killswitch, 55) == WAIT_TIMEOUT)
|
|
{
|
|
// nop
|
|
}
|
|
Buffer();
|
|
}
|
|
|
|
~ProgressiveReader()
|
|
{
|
|
if (download_thread)
|
|
{
|
|
SetEvent(thread_abort);
|
|
WaitForSingleObject(download_thread, INFINITE);
|
|
CloseHandle(download_thread);
|
|
}
|
|
|
|
if (thread_abort)
|
|
{
|
|
CloseHandle(thread_abort);
|
|
}
|
|
|
|
CloseHandle(progressive_file_read);
|
|
CloseHandle(progressive_file_write);
|
|
DeleteFile(filename);
|
|
|
|
if (http)
|
|
{
|
|
waServiceFactory *sf = mod.service->service_getServiceByGuid(httpreceiverGUID);
|
|
if (sf) http = (api_httpreceiver *)sf->releaseInterface(http);
|
|
http=0;
|
|
}
|
|
}
|
|
|
|
void Buffer()
|
|
{
|
|
bufferCount=0;
|
|
for (int i=0;i<101;i++)
|
|
{
|
|
Buffering(i, L"Buffering: ");
|
|
WaitForSingleObject(killswitch, 55);
|
|
}
|
|
}
|
|
|
|
void OnFinish()
|
|
{
|
|
stream_disconnected=true;
|
|
}
|
|
|
|
|
|
bool WaitForPosition(uint64_t position, uint64_t size)
|
|
{
|
|
do
|
|
{
|
|
bool valid = progress_tracker.Valid(position, position+size);
|
|
if (valid)
|
|
return true;
|
|
else
|
|
{
|
|
if (position < current_position)
|
|
{
|
|
Reconnect(position, position+size);
|
|
}
|
|
else
|
|
{
|
|
Buffer();
|
|
}
|
|
}
|
|
} while (WaitForSingleObject(killswitch, 0) == WAIT_TIMEOUT);
|
|
return false;
|
|
}
|
|
|
|
size_t Read(void *buffer, size_t size)
|
|
{
|
|
if (WaitForPosition(current_position, (uint64_t)size) == false)
|
|
return 0;
|
|
|
|
DWORD bytes_read=0;
|
|
ReadFile(progressive_file_read, buffer, size, &bytes_read, NULL);
|
|
current_position += bytes_read;
|
|
return bytes_read;
|
|
}
|
|
|
|
uint64_t GetFileLength()
|
|
{
|
|
return content_length;
|
|
}
|
|
|
|
void Reconnect(uint64_t position, uint64_t end)
|
|
{
|
|
SetEvent(thread_abort);
|
|
WaitForSingleObject(download_thread, INFINITE);
|
|
ResetEvent(thread_abort);
|
|
|
|
uint64_t new_start, new_end;
|
|
progress_tracker.Seek(position, end, &new_start, &new_end);
|
|
|
|
CloseHandle(download_thread);
|
|
stream_disconnected=false;
|
|
connected=false;
|
|
if (http)
|
|
{
|
|
waServiceFactory *sf = mod.service->service_getServiceByGuid(httpreceiverGUID);
|
|
if (sf) http = (api_httpreceiver *)sf->releaseInterface(http);
|
|
http=0;
|
|
}
|
|
|
|
http = SetupConnection(url, new_start, new_end);
|
|
Seek64(progressive_file_write, new_start, SEEK_SET);
|
|
download_thread = CreateThread(0, 0, ProgressiveThread, this, 0, 0);
|
|
while (!connected && !stream_disconnected && WaitForSingleObject(killswitch, 55) == WAIT_TIMEOUT)
|
|
{
|
|
// nop
|
|
}
|
|
Buffer();
|
|
}
|
|
|
|
int SetPosition(uint64_t position)
|
|
{
|
|
if (position == content_length)
|
|
{
|
|
end_of_file=true;
|
|
}
|
|
else
|
|
{
|
|
if (!progress_tracker.Valid(position, position))
|
|
{
|
|
Reconnect(position, (uint64_t)-1);
|
|
}
|
|
current_position = Seek64(progressive_file_read, position, SEEK_SET);
|
|
end_of_file=false;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int GetPosition(uint64_t *position)
|
|
{
|
|
if (end_of_file)
|
|
*position = content_length;
|
|
else
|
|
*position = current_position;
|
|
return 0;
|
|
}
|
|
|
|
int EndOfFile()
|
|
{
|
|
return !!stream_disconnected;
|
|
}
|
|
|
|
int Close()
|
|
{
|
|
SetEvent(thread_abort);
|
|
while (!stream_disconnected && WaitForSingleObject(killswitch, 55) == WAIT_TIMEOUT)
|
|
{
|
|
// nop
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* API used by download thread */
|
|
void Write(const void *data, size_t data_len)
|
|
{
|
|
DWORD bytes_written = 0;
|
|
WriteFile(progressive_file_write, data, data_len, &bytes_written, 0);
|
|
progress_tracker.Write(data_len);
|
|
}
|
|
|
|
int Wait(int milliseconds)
|
|
{
|
|
HANDLE handles[] = {killswitch, thread_abort};
|
|
int ret = WaitForMultipleObjects(2, handles, FALSE, milliseconds);
|
|
if (ret == WAIT_OBJECT_0+1)
|
|
return 1;
|
|
else if (ret == WAIT_TIMEOUT)
|
|
return 0;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
int DoRead(void *buffer, size_t bufferlen)
|
|
{
|
|
int ret = http->run();
|
|
int bytes_received;
|
|
do
|
|
{
|
|
ret = http->run();
|
|
bytes_received= http->get_bytes(buffer, bufferlen);
|
|
if (bytes_received)
|
|
Write(buffer, bytes_received);
|
|
} while (bytes_received);
|
|
return ret;
|
|
}
|
|
|
|
int Connect()
|
|
{
|
|
do
|
|
{
|
|
int ret = http->run();
|
|
if (ret == -1) // connection failed
|
|
return ret;
|
|
|
|
// ---- check our reply code ----
|
|
int replycode = http->getreplycode();
|
|
switch (replycode)
|
|
{
|
|
case 0:
|
|
case 100:
|
|
break;
|
|
case 200:
|
|
case 206:
|
|
{
|
|
|
|
const char *content_length_header = http->getheader("Content-Length");
|
|
if (content_length_header)
|
|
{
|
|
uint64_t new_content_length = _strtoui64(content_length_header, 0, 10);
|
|
//InterlockedExchange64((volatile LONGLONG *)&content_length, new_content_length);
|
|
content_length = new_content_length; // TODO interlock on win32
|
|
}
|
|
connected=true;
|
|
|
|
return 0;
|
|
}
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
while (Wait(55) == 0);
|
|
return 0;
|
|
}
|
|
|
|
private:
|
|
uint64_t current_position;
|
|
volatile uint64_t content_length;
|
|
bool end_of_file;
|
|
bool stream_disconnected;
|
|
bool connected;
|
|
char *url;
|
|
wchar_t filename[MAX_PATH];
|
|
HANDLE progressive_file_read, progressive_file_write;
|
|
ProgressTracker progress_tracker;
|
|
HANDLE killswitch;
|
|
HANDLE download_thread;
|
|
api_httpreceiver *http;
|
|
HANDLE thread_abort;
|
|
};
|
|
|
|
static DWORD CALLBACK ProgressiveThread(LPVOID param)
|
|
{
|
|
ProgressiveReader *reader = (ProgressiveReader *)param;
|
|
|
|
if (reader->Connect() == 0)
|
|
{
|
|
int ret = 0;
|
|
while (ret == 0)
|
|
{
|
|
ret=reader->Wait(10);
|
|
if (ret >= 0)
|
|
{
|
|
char buffer[HTTP_BUFFER_SIZE] = {0};
|
|
reader->DoRead(buffer, sizeof(buffer));
|
|
}
|
|
}
|
|
}
|
|
reader->OnFinish();
|
|
|
|
return 0;
|
|
}
|
|
|
|
u_int64_t HTTPGetFileLength(void *user)
|
|
{
|
|
ProgressiveReader *reader = (ProgressiveReader *)user;
|
|
return reader->GetFileLength();
|
|
}
|
|
|
|
int HTTPSetPosition(void *user, u_int64_t position)
|
|
{
|
|
ProgressiveReader *reader = (ProgressiveReader *)user;
|
|
return reader->SetPosition(position);
|
|
}
|
|
|
|
int HTTPGetPosition(void *user, u_int64_t *position)
|
|
{
|
|
ProgressiveReader *reader = (ProgressiveReader *)user;
|
|
return reader->GetPosition(position);
|
|
}
|
|
|
|
size_t HTTPRead(void *user, void *buffer, size_t size)
|
|
{
|
|
ProgressiveReader *reader = (ProgressiveReader *)user;
|
|
return reader->Read(buffer, size);
|
|
}
|
|
|
|
size_t HTTPWrite(void *user, void *buffer, size_t size)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
int HTTPEndOfFile(void *user)
|
|
{
|
|
ProgressiveReader *reader = (ProgressiveReader *)user;
|
|
return reader->EndOfFile();
|
|
}
|
|
|
|
int HTTPClose(void *user)
|
|
{
|
|
ProgressiveReader *reader = (ProgressiveReader *)user;
|
|
return reader->Close();
|
|
}
|
|
|
|
Virtual_IO HTTPIO =
|
|
{
|
|
HTTPGetFileLength,
|
|
HTTPSetPosition,
|
|
HTTPGetPosition,
|
|
HTTPRead,
|
|
HTTPWrite,
|
|
HTTPEndOfFile,
|
|
HTTPClose,
|
|
};
|
|
|
|
void *CreateReader(const wchar_t *url, HANDLE killswitch)
|
|
{
|
|
|
|
if ( WAC_API_DOWNLOADMANAGER )
|
|
{
|
|
return new ProgressiveReader(AutoChar(url), killswitch);
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
void DestroyReader(void *user)
|
|
{
|
|
ProgressiveReader *reader = (ProgressiveReader *)user;
|
|
delete reader;
|
|
}
|
|
|
|
void StopReader(void *user)
|
|
{
|
|
ProgressiveReader *reader = (ProgressiveReader *)user;
|
|
reader->Close();
|
|
}
|
|
|
|
/* ----------------------------------- */
|
|
|
|
struct Win32_State
|
|
{
|
|
Win32_State()
|
|
{
|
|
memset(buffer, 0, sizeof(buffer));
|
|
handle=0;
|
|
endOfFile=false;
|
|
position.QuadPart = 0;
|
|
event = CreateEvent(NULL, TRUE, TRUE, NULL);
|
|
read_offset=0;
|
|
io_active=false;
|
|
}
|
|
~Win32_State()
|
|
{
|
|
if (handle && handle != INVALID_HANDLE_VALUE)
|
|
CancelIo(handle);
|
|
CloseHandle(event);
|
|
}
|
|
// void *userData;
|
|
HANDLE handle;
|
|
bool endOfFile;
|
|
LARGE_INTEGER position;
|
|
HANDLE event;
|
|
OVERLAPPED overlapped;
|
|
DWORD read_offset;
|
|
bool io_active;
|
|
char buffer[16384];
|
|
};
|
|
|
|
static __int64 FileSize64(HANDLE file)
|
|
{
|
|
LARGE_INTEGER position;
|
|
position.QuadPart=0;
|
|
position.LowPart = GetFileSize(file, (LPDWORD)&position.HighPart);
|
|
|
|
if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR)
|
|
return INVALID_FILE_SIZE;
|
|
else
|
|
return position.QuadPart;
|
|
}
|
|
|
|
u_int64_t UnicodeGetFileLength(void *user)
|
|
{
|
|
Win32_State *state = static_cast<Win32_State *>(user);
|
|
assert(state->handle);
|
|
return FileSize64(state->handle);
|
|
}
|
|
|
|
|
|
|
|
int UnicodeSetPosition(void *user, u_int64_t position)
|
|
{
|
|
Win32_State *state = static_cast<Win32_State *>(user);
|
|
assert(state->handle);
|
|
__int64 diff = position - state->position.QuadPart;
|
|
|
|
if ((diff+state->read_offset) >= sizeof(state->buffer)
|
|
|| (diff+state->read_offset) < 0)
|
|
{
|
|
CancelIo(state->handle);
|
|
state->io_active = 0;
|
|
state->read_offset = 0;
|
|
}
|
|
else if (diff)
|
|
state->read_offset += (DWORD)diff;
|
|
|
|
state->position.QuadPart = position;
|
|
state->endOfFile = false;
|
|
return 0;
|
|
}
|
|
|
|
int UnicodeGetPosition(void *user, u_int64_t *position)
|
|
{
|
|
Win32_State *state = static_cast<Win32_State *>(user);
|
|
assert(state->handle);
|
|
*position = state->position.QuadPart;
|
|
return 0;
|
|
}
|
|
|
|
static void DoRead(Win32_State *state)
|
|
{
|
|
WaitForSingleObject(state->event, INFINITE);
|
|
state->overlapped.hEvent = state->event;
|
|
state->overlapped.Offset = state->position.LowPart;
|
|
state->overlapped.OffsetHigh = state->position.HighPart;
|
|
state->read_offset = 0;
|
|
ResetEvent(state->event);
|
|
ReadFile(state->handle, state->buffer, sizeof(state->buffer), NULL, &state->overlapped);
|
|
//int error = GetLastError();//ERROR_IO_PENDING = 997
|
|
state->io_active=true;
|
|
}
|
|
|
|
size_t UnicodeRead(void *user, void *buffer, size_t size)
|
|
{
|
|
Win32_State *state = static_cast<Win32_State *>(user);
|
|
assert(state->handle);
|
|
size_t totalRead=0;
|
|
HANDLE file = state->handle;
|
|
if (!state->io_active)
|
|
{
|
|
DoRead(state);
|
|
}
|
|
|
|
if (state->read_offset == sizeof(state->buffer))
|
|
{
|
|
DoRead(state);
|
|
}
|
|
|
|
while (size > (sizeof(state->buffer) - state->read_offset))
|
|
{
|
|
DWORD bytesRead=0;
|
|
BOOL res = GetOverlappedResult(file, &state->overlapped, &bytesRead, TRUE);
|
|
if ((res && bytesRead != sizeof(state->buffer))
|
|
|| (!res && GetLastError() == ERROR_HANDLE_EOF))
|
|
{
|
|
state->endOfFile = true;
|
|
}
|
|
|
|
if (bytesRead > state->read_offset)
|
|
{
|
|
size_t bytesToCopy = bytesRead-state->read_offset;
|
|
memcpy(buffer, state->buffer + state->read_offset, bytesToCopy);
|
|
buffer=(uint8_t *)buffer + bytesToCopy;
|
|
totalRead+=bytesToCopy;
|
|
size-=bytesToCopy;
|
|
|
|
|
|
if (state->endOfFile)
|
|
return totalRead;
|
|
|
|
state->position.QuadPart += bytesToCopy;
|
|
DoRead(state);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
DWORD bytesRead=0;
|
|
BOOL res = GetOverlappedResult(file, &state->overlapped, &bytesRead, FALSE);
|
|
if ((res && bytesRead != sizeof(state->buffer))
|
|
|| (!res && GetLastError() == ERROR_HANDLE_EOF))
|
|
{
|
|
state->endOfFile = true;
|
|
}
|
|
if (bytesRead >= (size + state->read_offset))
|
|
{
|
|
memcpy(buffer, state->buffer + state->read_offset, size);
|
|
state->read_offset += size;
|
|
totalRead+=size;
|
|
state->position.QuadPart += size;
|
|
break;
|
|
}
|
|
|
|
if (state->endOfFile)
|
|
break;
|
|
|
|
WaitForSingleObject(state->event, 10); // wait 10 milliseconds or when buffer is done, whichever is faster
|
|
}
|
|
|
|
return totalRead;
|
|
}
|
|
|
|
size_t UnicodeWrite(void *user, void *buffer, size_t size)
|
|
{
|
|
Win32_State *state = static_cast<Win32_State *>(user);
|
|
DWORD written = 0;
|
|
assert(state->handle);
|
|
WriteFile(state->handle, buffer, size, &written, NULL);
|
|
return 0;
|
|
}
|
|
|
|
int UnicodeEndOfFile(void *user)
|
|
{
|
|
Win32_State *state = static_cast<Win32_State *>(user);
|
|
return state->endOfFile;
|
|
}
|
|
|
|
int UnicodeClose(void *user)
|
|
{
|
|
Win32_State *state = static_cast<Win32_State *>(user);
|
|
if (state->handle)
|
|
CloseHandle(state->handle);
|
|
|
|
state->handle=0;
|
|
return 0;
|
|
}
|
|
|
|
Virtual_IO UnicodeIO =
|
|
{
|
|
UnicodeGetFileLength,
|
|
UnicodeSetPosition,
|
|
UnicodeGetPosition,
|
|
UnicodeRead,
|
|
UnicodeWrite,
|
|
UnicodeEndOfFile,
|
|
UnicodeClose,
|
|
};
|
|
|
|
|
|
void *CreateUnicodeReader(const wchar_t *filename)
|
|
{
|
|
HANDLE fileHandle = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
|
|
if (fileHandle == INVALID_HANDLE_VALUE)
|
|
return 0;
|
|
|
|
Win32_State *state = new Win32_State;
|
|
if (!state)
|
|
{
|
|
CloseHandle(fileHandle);
|
|
return 0;
|
|
}
|
|
state->endOfFile = false;
|
|
state->handle = fileHandle;
|
|
return state;
|
|
}
|
|
|
|
void DestroyUnicodeReader(void *reader)
|
|
{
|
|
if (reader) // need to check because of the cast
|
|
delete (Win32_State *)reader;
|
|
}
|