summary refs log tree commit diff
path: root/src/Platform_Windows.c
diff options
context:
space:
mode:
authorWlodekM <[email protected]>2024-06-16 10:35:45 +0300
committerWlodekM <[email protected]>2024-06-16 10:35:45 +0300
commitabef6da56913f1c55528103e60a50451a39628b1 (patch)
treeb3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Platform_Windows.c
initial commit
Diffstat (limited to 'src/Platform_Windows.c')
-rw-r--r--src/Platform_Windows.c1135
1 files changed, 1135 insertions, 0 deletions
diff --git a/src/Platform_Windows.c b/src/Platform_Windows.c
new file mode 100644
index 0000000..7e66dbf
--- /dev/null
+++ b/src/Platform_Windows.c
@@ -0,0 +1,1135 @@
+#include "Core.h"
+#if defined CC_BUILD_WIN
+
+#include "_PlatformBase.h"
+#include "Stream.h"
+#include "SystemFonts.h"
+#include "Funcs.h"
+#include "Utils.h"
+#include "Errors.h"
+
+#define WIN32_LEAN_AND_MEAN
+#define NOSERVICE
+#define NOMCX
+#define NOIME
+#ifndef UNICODE
+#define UNICODE
+#define _UNICODE
+#endif
+#include <windows.h>
+#include <ws2tcpip.h>
+
+/* === BEGIN shellapi.h === */
+#define SHELLAPI DECLSPEC_IMPORT
+SHELLAPI HINSTANCE WINAPI ShellExecuteW(HWND hwnd, LPCWSTR operation, LPCWSTR file, LPCWSTR parameters, LPCWSTR directory, INT showCmd);
+SHELLAPI HINSTANCE WINAPI ShellExecuteA(HWND hwnd, LPCSTR operation,  LPCSTR file,  LPCSTR  parameters, LPCSTR  directory, INT showCmd);
+/* === END shellapi.h === */
+/* === BEGIN wincrypt.h === */
+typedef struct _CRYPTOAPI_BLOB {
+	DWORD cbData;
+	BYTE* pbData;
+} DATA_BLOB;
+
+static BOOL (WINAPI *_CryptProtectData  )(DATA_BLOB* dataIn, PCWSTR dataDescr, PVOID entropy, PVOID reserved, PVOID promptStruct, DWORD flags, DATA_BLOB* dataOut);
+static BOOL (WINAPI *_CryptUnprotectData)(DATA_BLOB* dataIn, PWSTR* dataDescr, PVOID entropy, PVOID reserved, PVOID promptStruct, DWORD flags, DATA_BLOB* dataOut);
+/* === END wincrypt.h === */
+
+static HANDLE heap;
+const cc_result ReturnCode_FileShareViolation = ERROR_SHARING_VIOLATION;
+const cc_result ReturnCode_FileNotFound     = ERROR_FILE_NOT_FOUND;
+const cc_result ReturnCode_SocketInProgess  = WSAEINPROGRESS;
+const cc_result ReturnCode_SocketWouldBlock = WSAEWOULDBLOCK;
+const cc_result ReturnCode_DirectoryExists  = ERROR_ALREADY_EXISTS;
+const char* Platform_AppNameSuffix = "";
+cc_bool Platform_SingleProcess;
+
+/*########################################################################################################################*
+*---------------------------------------------------------Memory----------------------------------------------------------*
+*#########################################################################################################################*/
+#ifdef CC_BUILD_NOSTDLIB
+void* Mem_Set(void* dst, cc_uint8 value,  unsigned numBytes) {
+	char* dp = (char*)dst;
+
+	while (numBytes--) *dp++ = value; /* TODO optimise */
+	return dst;
+}
+
+void* Mem_Copy(void* dst, const void* src, unsigned numBytes) {
+	char* sp = (char*)src;
+	char* dp = (char*)dst;
+
+	while (numBytes--) *dp++ = *sp++; /* TODO optimise */
+	return dst;
+}
+
+void* Mem_Move(void* dst, const void* src, unsigned numBytes) { 
+	char* sp = (char*)src;
+	char* dp = (char*)dst;
+	
+	/* Check if destination range overlaps source range */
+	/* If this happens, then need to copy backwards */
+	if (dp >= sp && dp < (sp + numBytes)) {
+		sp += numBytes;
+		dp += numBytes;
+
+		while (numBytes--) *--dp = *--sp;
+	} else {
+		while (numBytes--) *dp++ = *sp++;
+	}
+	return dst;
+}
+#else
+void* Mem_Set(void*  dst, cc_uint8 value,  unsigned numBytes) { return memset( dst, value, numBytes); }
+void* Mem_Copy(void* dst, const void* src, unsigned numBytes) { return memcpy( dst, src,   numBytes); }
+void* Mem_Move(void* dst, const void* src, unsigned numBytes) { return memmove(dst, src,   numBytes); }
+#endif
+
+void* Mem_TryAlloc(cc_uint32 numElems, cc_uint32 elemsSize) {
+	cc_uint32 size = CalcMemSize(numElems, elemsSize);
+	return size ? HeapAlloc(heap, 0, size) : NULL;
+}
+
+void* Mem_TryAllocCleared(cc_uint32 numElems, cc_uint32 elemsSize) {
+	cc_uint32 size = CalcMemSize(numElems, elemsSize);
+	return size ? HeapAlloc(heap, HEAP_ZERO_MEMORY, size) : NULL;
+}
+
+void* Mem_TryRealloc(void* mem, cc_uint32 numElems, cc_uint32 elemsSize) {
+	cc_uint32 size = CalcMemSize(numElems, elemsSize);
+	return size ? HeapReAlloc(heap, 0, mem, size) : NULL;
+}
+
+void Mem_Free(void* mem) {
+	if (mem) HeapFree(heap, 0, mem);
+}
+
+
+/*########################################################################################################################*
+*------------------------------------------------------Logging/Time-------------------------------------------------------*
+*#########################################################################################################################*/
+/* TODO: check this is actually accurate */
+static cc_uint64 sw_freqMul = 1, sw_freqDiv = 1;
+cc_uint64 Stopwatch_ElapsedMicroseconds(cc_uint64 beg, cc_uint64 end) {
+	if (end < beg) return 0;
+	return ((end - beg) * sw_freqMul) / sw_freqDiv;
+}
+
+static HANDLE conHandle;
+static BOOL hasDebugger;
+
+void Platform_Log(const char* msg, int len) {
+	char tmp[2048 + 1];
+	DWORD wrote;
+
+	if (conHandle) {
+		WriteFile(conHandle, msg,  len, &wrote, NULL);
+		WriteFile(conHandle, "\n",   1, &wrote, NULL);
+	}
+
+	if (!hasDebugger) return;
+	len = min(len, 2048);
+	Mem_Copy(tmp, msg, len); tmp[len] = '\0';
+
+	OutputDebugStringA(tmp);
+	OutputDebugStringA("\n");
+}
+
+#define FILETIME_EPOCH      50491123200ULL
+#define FILETIME_UNIX_EPOCH 11644473600ULL
+#define FileTime_TotalSecs(time) ((time / 10000000) + FILETIME_EPOCH)
+#define FileTime_UnixTime(time)  ((time / 10000000) - FILETIME_UNIX_EPOCH)
+TimeMS DateTime_CurrentUTC(void) {
+	FILETIME ft; 
+	cc_uint64 raw;
+	
+	GetSystemTimeAsFileTime(&ft);
+	/* in 100 nanosecond units, since Jan 1 1601 */
+	raw = ft.dwLowDateTime | ((cc_uint64)ft.dwHighDateTime << 32);
+	return FileTime_TotalSecs(raw);
+}
+
+void DateTime_CurrentLocal(struct DateTime* t) {
+	SYSTEMTIME localTime;
+	GetLocalTime(&localTime);
+
+	t->year   = localTime.wYear;
+	t->month  = localTime.wMonth;
+	t->day    = localTime.wDay;
+	t->hour   = localTime.wHour;
+	t->minute = localTime.wMinute;
+	t->second = localTime.wSecond;
+}
+
+static cc_bool sw_highRes;
+cc_uint64 Stopwatch_Measure(void) {
+	LARGE_INTEGER t;
+	FILETIME ft;
+
+	if (sw_highRes) {
+		QueryPerformanceCounter(&t);
+		return (cc_uint64)t.QuadPart;
+	} else {		
+		GetSystemTimeAsFileTime(&ft);
+		return (cc_uint64)ft.dwLowDateTime | ((cc_uint64)ft.dwHighDateTime << 32);
+	}
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------Directory/File------------------------------------------------------*
+*#########################################################################################################################*/
+void Directory_GetCachePath(cc_string* path) { }
+
+cc_result Directory_Create(const cc_string* path) {
+	cc_winstring str;
+	cc_result res;
+
+	Platform_EncodeString(&str, path);
+	if (CreateDirectoryW(str.uni, NULL)) return 0;
+	/* Windows 9x does not support W API functions */
+	if ((res = GetLastError()) != ERROR_CALL_NOT_IMPLEMENTED) return res;
+
+	return CreateDirectoryA(str.ansi, NULL) ? 0 : GetLastError();
+}
+
+int File_Exists(const cc_string* path) {
+	cc_winstring str;
+	DWORD attribs;
+
+	Platform_EncodeString(&str, path);
+	attribs = GetFileAttributesW(str.uni);
+
+	return attribs != INVALID_FILE_ATTRIBUTES && !(attribs & FILE_ATTRIBUTE_DIRECTORY);
+}
+
+static cc_result Directory_EnumCore(const cc_string* dirPath, const cc_string* file, DWORD attribs,
+									void* obj, Directory_EnumCallback callback) {
+	cc_string path; char pathBuffer[MAX_PATH + 10];
+	/* ignore . and .. entry */
+	if (file->length == 1 && file->buffer[0] == '.') return 0;
+	if (file->length == 2 && file->buffer[0] == '.' && file->buffer[1] == '.') return 0;
+
+	String_InitArray(path, pathBuffer);
+	String_Format2(&path, "%s/%s", dirPath, file);
+
+	if (attribs & FILE_ATTRIBUTE_DIRECTORY) return Directory_Enum(&path, obj, callback);
+	callback(&path, obj);
+	return 0;
+}
+
+cc_result Directory_Enum(const cc_string* dirPath, void* obj, Directory_EnumCallback callback) {
+	cc_string path; char pathBuffer[MAX_PATH + 10];
+	WIN32_FIND_DATAW eW;
+	WIN32_FIND_DATAA eA;
+	int i, ansi = false;
+	cc_winstring str;
+	HANDLE find;
+	cc_result res;	
+
+	/* Need to append \* to search for files in directory */
+	String_InitArray(path, pathBuffer);
+	String_Format1(&path, "%s\\*", dirPath);
+	Platform_EncodeString(&str, &path);
+	
+	find = FindFirstFileW(str.uni, &eW);
+	if (!find || find == INVALID_HANDLE_VALUE) {
+		if ((res = GetLastError()) != ERROR_CALL_NOT_IMPLEMENTED) return res;
+		ansi = true;
+
+		/* Windows 9x does not support W API functions */
+		find = FindFirstFileA(str.ansi, &eA);
+		if (find == INVALID_HANDLE_VALUE) return GetLastError();
+	}
+
+	if (ansi) {
+		do {
+			path.length = 0;
+			for (i = 0; i < MAX_PATH && eA.cFileName[i]; i++) {
+				String_Append(&path, Convert_CodepointToCP437(eA.cFileName[i]));
+			}
+			if ((res = Directory_EnumCore(dirPath, &path, eA.dwFileAttributes, obj, callback))) return res;
+		} while (FindNextFileA(find, &eA));
+	} else {
+		do {
+			path.length = 0;
+			for (i = 0; i < MAX_PATH && eW.cFileName[i]; i++) {
+				/* TODO: UTF16 to codepoint conversion */
+				String_Append(&path, Convert_CodepointToCP437(eW.cFileName[i]));
+			}
+			if ((res = Directory_EnumCore(dirPath, &path, eW.dwFileAttributes, obj, callback))) return res;
+		} while (FindNextFileW(find, &eW));
+	}
+
+	res = GetLastError(); /* return code from FindNextFile */
+	FindClose(find);
+	return res == ERROR_NO_MORE_FILES ? 0 : res;
+}
+
+static cc_result DoFileRaw(cc_file* file, const cc_winstring* str, DWORD access, DWORD createMode) {
+	cc_result res;
+
+	*file = CreateFileW(str->uni,  access, FILE_SHARE_READ, NULL, createMode, 0, NULL);
+	if (*file && *file != INVALID_HANDLE_VALUE) return 0;
+	if ((res = GetLastError()) != ERROR_CALL_NOT_IMPLEMENTED) return res;
+
+	/* Windows 9x does not support W API functions */
+	*file = CreateFileA(str->ansi, access, FILE_SHARE_READ, NULL, createMode, 0, NULL);
+	return *file != INVALID_HANDLE_VALUE ? 0 : GetLastError();
+}
+
+static cc_result DoFile(cc_file* file, const cc_string* path, DWORD access, DWORD createMode) {
+	cc_winstring str;
+	Platform_EncodeString(&str, path);
+	return DoFileRaw(file, &str, access, createMode);
+}
+
+cc_result File_Open(cc_file* file, const cc_string* path) {
+	return DoFile(file, path, GENERIC_READ, OPEN_EXISTING);
+}
+cc_result File_Create(cc_file* file, const cc_string* path) {
+	return DoFile(file, path, GENERIC_WRITE | GENERIC_READ, CREATE_ALWAYS);
+}
+cc_result File_OpenOrCreate(cc_file* file, const cc_string* path) {
+	return DoFile(file, path, GENERIC_WRITE | GENERIC_READ, OPEN_ALWAYS);
+}
+
+cc_result File_Read(cc_file file, void* data, cc_uint32 count, cc_uint32* bytesRead) {
+	BOOL success = ReadFile(file, data, count, bytesRead, NULL);
+	return success ? 0 : GetLastError();
+}
+
+cc_result File_Write(cc_file file, const void* data, cc_uint32 count, cc_uint32* bytesWrote) {
+	BOOL success = WriteFile(file, data, count, bytesWrote, NULL);
+	return success ? 0 : GetLastError();
+}
+
+cc_result File_Close(cc_file file) {
+	return CloseHandle(file) ? 0 : GetLastError();
+}
+
+cc_result File_Seek(cc_file file, int offset, int seekType) {
+	static cc_uint8 modes[] = { FILE_BEGIN, FILE_CURRENT, FILE_END };
+	DWORD pos = SetFilePointer(file, offset, NULL, modes[seekType]);
+	return pos != INVALID_SET_FILE_POINTER ? 0 : GetLastError();
+}
+
+cc_result File_Position(cc_file file, cc_uint32* pos) {
+	*pos = SetFilePointer(file, 0, NULL, FILE_CURRENT);
+	return *pos != INVALID_SET_FILE_POINTER ? 0 : GetLastError();
+}
+
+cc_result File_Length(cc_file file, cc_uint32* len) {
+	*len = GetFileSize(file, NULL);
+	return *len != INVALID_FILE_SIZE ? 0 : GetLastError();
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------------Threading--------------------------------------------------------*
+*#############################################################################################################p############*/
+void Thread_Sleep(cc_uint32 milliseconds) { Sleep(milliseconds); }
+static DWORD WINAPI ExecThread(void* param) {
+	Thread_StartFunc func = (Thread_StartFunc)param;
+	func();
+	return 0;
+}
+
+void Thread_Run(void** handle, Thread_StartFunc func, int stackSize, const char* name) {
+	DWORD threadID;
+	HANDLE thread = CreateThread(NULL, 0, ExecThread, (void*)func, CREATE_SUSPENDED, &threadID);
+	if (!thread) Logger_Abort2(GetLastError(), "Creating thread");
+	
+	*handle = thread;
+	ResumeThread(thread);
+}
+
+void Thread_Detach(void* handle) {
+	if (!CloseHandle((HANDLE)handle)) {
+		Logger_Abort2(GetLastError(), "Freeing thread handle");
+	}
+}
+
+void Thread_Join(void* handle) {
+	WaitForSingleObject((HANDLE)handle, INFINITE);
+	Thread_Detach(handle);
+}
+
+void* Mutex_Create(const char* name) {
+	CRITICAL_SECTION* ptr = (CRITICAL_SECTION*)Mem_Alloc(1, sizeof(CRITICAL_SECTION), "mutex");
+	InitializeCriticalSection(ptr);
+	return ptr;
+}
+
+void Mutex_Free(void* handle)   { 
+	DeleteCriticalSection((CRITICAL_SECTION*)handle); 
+	Mem_Free(handle);
+}
+void Mutex_Lock(void* handle)   { EnterCriticalSection((CRITICAL_SECTION*)handle); }
+void Mutex_Unlock(void* handle) { LeaveCriticalSection((CRITICAL_SECTION*)handle); }
+
+void* Waitable_Create(const char* name) {
+	void* handle = CreateEventA(NULL, false, false, NULL);
+	if (!handle) {
+		Logger_Abort2(GetLastError(), "Creating waitable");
+	}
+	return handle;
+}
+
+void Waitable_Free(void* handle) {
+	if (!CloseHandle((HANDLE)handle)) {
+		Logger_Abort2(GetLastError(), "Freeing waitable");
+	}
+}
+
+void Waitable_Signal(void* handle) { SetEvent((HANDLE)handle); }
+void Waitable_Wait(void* handle) {
+	WaitForSingleObject((HANDLE)handle, INFINITE);
+}
+
+void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) {
+	WaitForSingleObject((HANDLE)handle, milliseconds);
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------------Font/Text--------------------------------------------------------*
+*#########################################################################################################################*/
+static void FontDirCallback(const cc_string* path, void* obj) {
+	static const cc_string fonExt = String_FromConst(".fon");
+	/* Completely skip windows .FON files */
+	if (String_CaselessEnds(path, &fonExt)) return;
+	SysFonts_Register(path, NULL);
+}
+
+void Platform_LoadSysFonts(void) { 
+	int i;
+	char winFolder[FILENAME_SIZE];
+	WCHAR winTmp[FILENAME_SIZE];
+	UINT winLen;
+	
+	cc_string dirs[2];
+	String_InitArray(dirs[0], winFolder);
+	dirs[1] = String_FromReadonly("C:/WINNT35/system");
+
+	/* System folder path may not be C:/Windows */
+	winLen = GetWindowsDirectoryW(winTmp, FILENAME_SIZE);
+	if (winLen) {
+		String_AppendUtf16(&dirs[0], winTmp, winLen * 2);
+	} else {
+		String_AppendConst(&dirs[0], "C:/Windows");
+	}
+	String_AppendConst(&dirs[0], "/fonts");
+
+	for (i = 0; i < Array_Elems(dirs); i++) {
+		Directory_Enum(&dirs[i], NULL, FontDirCallback);
+	}
+}
+
+
+/*########################################################################################################################*
+*---------------------------------------------------------Socket----------------------------------------------------------*
+*#########################################################################################################################*/
+/* Sanity check to ensure cc_sockaddr struct is large enough to contain all socket addresses supported by this platform */
+static char sockaddr_size_check[sizeof(SOCKADDR_STORAGE) < CC_SOCKETADDR_MAXSIZE ? 1 : -1];
+
+static int (WSAAPI *_WSAStartup)(WORD versionRequested, LPWSADATA wsaData);
+static int (WSAAPI *_WSACleanup)(void);
+static int (WSAAPI *_WSAGetLastError)(void);
+static int (WSAAPI *_WSAStringToAddressW)(LPWSTR addressString, INT addressFamily, LPVOID protocolInfo, LPVOID address, LPINT addressLength);
+
+static int (WSAAPI *_socket)(int af, int type, int protocol);
+static int (WSAAPI *_closesocket)(SOCKET s);
+static int (WSAAPI *_connect)(SOCKET s, const struct sockaddr* name, int namelen);
+static int (WSAAPI *_shutdown)(SOCKET s, int how);
+
+static int (WSAAPI *_ioctlsocket)(SOCKET s, long cmd, u_long* argp);
+static int (WSAAPI *_getsockopt)(SOCKET s, int level, int optname, char* optval, int* optlen);
+static int (WSAAPI *_recv)(SOCKET s, char* buf, int len, int flags);
+static int (WSAAPI *_send)(SOCKET s, const char FAR * buf, int len, int flags);
+static int (WSAAPI *_select)(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, const struct timeval* timeout);
+
+static struct hostent* (WSAAPI *_gethostbyname)(const char* name);
+static unsigned short  (WSAAPI *_htons)(u_short hostshort);
+static int  (WSAAPI *_getaddrinfo )(PCSTR nodeName, PCSTR serviceName, const ADDRINFOA* hints, PADDRINFOA* result);
+static void (WSAAPI* _freeaddrinfo)(PADDRINFOA addrInfo);
+
+
+
+static INT WSAAPI FallbackParseAddress(LPWSTR addressString, INT addressFamily, LPVOID protocolInfo, LPVOID address, LPINT addressLength) {
+	SOCKADDR_IN* addr4 = (SOCKADDR_IN*)address;
+	cc_uint8*    addr  = (cc_uint8*)&addr4->sin_addr;
+	cc_string ip, parts[4 + 1];
+	cc_winstring* addrStr = (cc_winstring*)addressString;
+
+	ip = String_FromReadonly(addrStr->ansi);
+	/* 4+1 in case user tries '1.1.1.1.1' */
+	if (String_UNSAFE_Split(&ip, '.', parts, 4 + 1) != 4)
+		return ERR_INVALID_ARGUMENT;
+
+	if (!Convert_ParseUInt8(&parts[0], &addr[0]) || !Convert_ParseUInt8(&parts[1], &addr[1]) ||
+		!Convert_ParseUInt8(&parts[2], &addr[2]) || !Convert_ParseUInt8(&parts[3], &addr[3]))
+		return ERR_INVALID_ARGUMENT;
+
+	addr4->sin_family = AF_INET;
+	return 0;
+}
+
+static void LoadWinsockFuncs(void) {
+	static const struct DynamicLibSym funcs[] = {
+		DynamicLib_Sym(WSAStartup),      DynamicLib_Sym(WSACleanup),
+		DynamicLib_Sym(WSAGetLastError), DynamicLib_Sym(WSAStringToAddressW),
+		DynamicLib_Sym(socket),          DynamicLib_Sym(closesocket),
+		DynamicLib_Sym(connect),         DynamicLib_Sym(shutdown),
+		DynamicLib_Sym(ioctlsocket),     DynamicLib_Sym(getsockopt),
+		DynamicLib_Sym(gethostbyname),   DynamicLib_Sym(htons),
+		DynamicLib_Sym(getaddrinfo),     DynamicLib_Sym(freeaddrinfo),
+		DynamicLib_Sym(recv), DynamicLib_Sym(send), DynamicLib_Sym(select)
+	};
+	static const cc_string winsock1 = String_FromConst("wsock32.DLL");
+	static const cc_string winsock2 = String_FromConst("WS2_32.DLL");
+	void* lib;
+
+	DynamicLib_LoadAll(&winsock2, funcs, Array_Elems(funcs), &lib);
+	/* Windows 95 is missing WS2_32 dll */
+	if (!_WSAStartup) DynamicLib_LoadAll(&winsock1, funcs, Array_Elems(funcs), &lib);
+
+	/* Fallback for older OS versions which lack WSAStringToAddressW */
+	if (!_WSAStringToAddressW) _WSAStringToAddressW = FallbackParseAddress;
+}
+
+static cc_result ParseHostOld(char* host, int port, cc_sockaddr* addrs, int* numValidAddrs) {
+	struct hostent* res;
+	cc_result wsa_res;
+	SOCKADDR_IN* addr4;
+	char* src_addr;
+	int i;
+
+	res = _gethostbyname(host);
+
+	if (!res) {
+		wsa_res = _WSAGetLastError();
+
+		if (wsa_res == WSAHOST_NOT_FOUND) return SOCK_ERR_UNKNOWN_HOST;
+		return ERR_INVALID_ARGUMENT;
+	}
+		
+	/* per MSDN, should only be getting AF_INET returned from this */
+	if (res->h_addrtype != AF_INET) return ERR_INVALID_ARGUMENT;
+	if (!res->h_addr_list)          return ERR_INVALID_ARGUMENT;
+
+	for (i = 0; i < SOCKET_MAX_ADDRS; i++) 
+	{
+		src_addr = res->h_addr_list[i];
+		if (!src_addr) break;
+		addrs[i].size = sizeof(SOCKADDR_IN);
+
+		addr4 = (SOCKADDR_IN*)addrs[i].data;
+		addr4->sin_family = AF_INET;
+		addr4->sin_port   = _htons(port);
+		addr4->sin_addr   = *(IN_ADDR*)src_addr;
+	}
+
+	*numValidAddrs = i;
+	/* Must have at least one IPv4 address */
+	return i == 0 ? ERR_INVALID_ARGUMENT : 0;
+}
+
+static cc_result ParseHostNew(char* host, int port, cc_sockaddr* addrs, int* numValidAddrs) {
+	char portRaw[32]; cc_string portStr;
+	struct addrinfo hints = { 0 };
+	struct addrinfo* result;
+	struct addrinfo* cur;
+	int res, i = 0;
+
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_protocol = IPPROTO_TCP;
+	
+	String_InitArray(portStr,  portRaw);
+	String_AppendInt(&portStr, port);
+	portRaw[portStr.length] = '\0';
+
+	res = _getaddrinfo(host, portRaw, &hints, &result);
+	if (res == EAI_NONAME) return SOCK_ERR_UNKNOWN_HOST;
+	if (res) return res;
+
+	/* Prefer IPv4 addresses first */
+	for (cur = result; cur && i < SOCKET_MAX_ADDRS; cur = cur->ai_next) 
+	{
+		if (cur->ai_family != AF_INET) continue;
+		SocketAddr_Set(&addrs[i], cur->ai_addr, cur->ai_addrlen); i++;
+	}
+	
+	for (cur = result; cur && i < SOCKET_MAX_ADDRS; cur = cur->ai_next) 
+	{
+		if (cur->ai_family == AF_INET) continue;
+		SocketAddr_Set(&addrs[i], cur->ai_addr, cur->ai_addrlen); i++;
+	}
+
+	_freeaddrinfo(result);
+	*numValidAddrs = i;
+	return i == 0 ? ERR_INVALID_ARGUMENT : 0;
+}
+
+cc_result Socket_ParseAddress(const cc_string* address, int port, cc_sockaddr* addrs, int* numValidAddrs) {
+	SOCKADDR_IN*  addr4 = (SOCKADDR_IN* )addrs[0].data;
+	SOCKADDR_IN6* addr6 = (SOCKADDR_IN6*)addrs[0].data;
+	cc_winstring str;
+	INT size;
+
+	*numValidAddrs = 0;
+	Platform_EncodeString(&str, address);
+
+	size = sizeof(*addr4);
+	if (!_WSAStringToAddressW(str.uni, AF_INET,  NULL, addr4, &size)) {
+		addr4->sin_port  = _htons(port);
+
+		addrs[0].size  = size;
+		*numValidAddrs = 1;
+		return 0;
+	}
+
+	size = sizeof(*addr6);
+	if (!_WSAStringToAddressW(str.uni, AF_INET6, NULL, addr6, &size)) {
+		addr6->sin6_port = _htons(port);
+
+		addrs[0].size  = size;
+		*numValidAddrs = 1;
+		return 0;
+	}
+
+	if (_getaddrinfo) {
+		return ParseHostNew(str.ansi, port, addrs, numValidAddrs);
+	} else {
+		return ParseHostOld(str.ansi, port, addrs, numValidAddrs);
+	}
+}
+
+cc_result Socket_Connect(cc_socket* s, cc_sockaddr* addr, cc_bool nonblocking) {
+	SOCKADDR* raw_addr = (SOCKADDR*)addr->data;
+	cc_result res;
+
+	*s = _socket(raw_addr->sa_family, SOCK_STREAM, IPPROTO_TCP);
+	if (*s == -1) return _WSAGetLastError();
+
+	if (nonblocking) {
+		u_long blockingMode = -1; /* non-blocking mode */
+		_ioctlsocket(*s, FIONBIO, &blockingMode);
+	}
+
+	res = _connect(*s, raw_addr, addr->size);
+	return res == -1 ? _WSAGetLastError() : 0;
+}
+
+cc_result Socket_Read(cc_socket s, cc_uint8* data, cc_uint32 count, cc_uint32* modified) {
+	int recvCount = _recv(s, (char*)data, count, 0);
+	if (recvCount != -1) { *modified = recvCount; return 0; }
+	*modified = 0; return _WSAGetLastError();
+}
+
+cc_result Socket_Write(cc_socket s, const cc_uint8* data, cc_uint32 count, cc_uint32* modified) {
+	int sentCount = _send(s, (const char*)data, count, 0);
+	if (sentCount != -1) { *modified = sentCount; return 0; }
+	*modified = 0; return _WSAGetLastError();
+}
+
+void Socket_Close(cc_socket s) {
+	_shutdown(s, SD_BOTH);
+	_closesocket(s);
+}
+
+static cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) {
+	fd_set set;
+	struct timeval time = { 0 };
+	int selectCount;
+
+	set.fd_count    = 1;
+	set.fd_array[0] = s;
+
+	if (mode == SOCKET_POLL_READ) {
+		selectCount = _select(1, &set, NULL, NULL, &time);
+	} else {
+		selectCount = _select(1, NULL, &set, NULL, &time);
+	}
+
+	if (selectCount == -1) { *success = false; return _WSAGetLastError(); }
+
+	*success = set.fd_count != 0; return 0;
+}
+
+cc_result Socket_CheckReadable(cc_socket s, cc_bool* readable) {
+	return Socket_Poll(s, SOCKET_POLL_READ, readable);
+}
+
+cc_result Socket_CheckWritable(cc_socket s, cc_bool* writable) {
+	int resultSize = sizeof(cc_result);
+	cc_result res  = Socket_Poll(s, SOCKET_POLL_WRITE, writable);
+	if (res || *writable) return res;
+
+	/* https://stackoverflow.com/questions/29479953/so-error-value-after-successful-socket-operation */
+	_getsockopt(s, SOL_SOCKET, SO_ERROR, (char*)&res, &resultSize);
+	return res;
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------Process/Module------------------------------------------------------*
+*#########################################################################################################################*/
+cc_bool Process_OpenSupported = true;
+
+static cc_result Process_RawGetExePath(cc_winstring* path, int* len) {
+	cc_result res;
+
+	/* If GetModuleFileNameA fails.. that's a serious problem */
+	*len = GetModuleFileNameA(NULL, path->ansi, NATIVE_STR_LEN);
+	path->ansi[*len] = '\0';
+	if (!(*len)) return GetLastError();
+	
+	*len = GetModuleFileNameW(NULL, path->uni, NATIVE_STR_LEN);
+	path->uni[*len]  = '\0';
+	if (*len) return 0;
+
+	/* GetModuleFileNameW can fail on Win 9x */
+	res = GetLastError();
+	if (res == ERROR_CALL_NOT_IMPLEMENTED) res = 0;
+	return res;
+}
+
+cc_result Process_StartGame2(const cc_string* args, int numArgs) {
+	union STARTUPINFO_union {
+		STARTUPINFOW wide;
+		STARTUPINFOA ansi;
+	} si = { 0 }; // less compiler warnings this way
+	
+	cc_winstring path;
+	cc_string argv; char argvBuffer[NATIVE_STR_LEN];
+	PROCESS_INFORMATION pi = { 0 };
+	cc_winstring raw;
+	cc_result res;
+	int len, i;
+
+	if (Platform_SingleProcess) return SetGameArgs(args, numArgs);
+	if ((res = Process_RawGetExePath(&path, &len))) return res;
+	si.wide.cb = sizeof(STARTUPINFOW);
+	
+	String_InitArray(argv, argvBuffer);
+	/* Game doesn't actually care about argv[0] */
+	String_AppendConst(&argv, "cc");
+	for (i = 0; i < numArgs; i++)
+	{
+		if (String_IndexOf(&args[i], ' ') >= 0) {
+			String_Format1(&argv, " \"%s\"", &args[i]);
+		} else {
+			String_Format1(&argv, " %s",     &args[i]);
+		}
+	}
+	Platform_EncodeString(&raw, &argv);
+
+	if (path.uni[0]) {
+		if (!CreateProcessW(path.uni, raw.uni, NULL, NULL,
+				false, 0, NULL, NULL, &si.wide, &pi)) return GetLastError();
+	} else {
+		/* Windows 9x does not support W API functions */
+		if (!CreateProcessA(path.ansi, raw.ansi, NULL, NULL,
+				false, 0, NULL, NULL, &si.ansi, &pi)) return GetLastError();
+	}
+
+	/* Don't leak memory for process return code */
+	CloseHandle(pi.hProcess);
+	CloseHandle(pi.hThread);
+	return 0;
+}
+
+void Process_Exit(cc_result code) { ExitProcess(code); }
+cc_result Process_StartOpen(const cc_string* args) {
+	cc_winstring str;
+	cc_uintptr res;
+	Platform_EncodeString(&str, args);
+
+	res = (cc_uintptr)ShellExecuteW(NULL, NULL, str.uni, NULL, NULL, SW_SHOWNORMAL);
+	/* Windows 9x always returns "error 0" for ShellExecuteW */
+	if (res == 0) res = (cc_uintptr)ShellExecuteA(NULL, NULL, str.ansi, NULL, NULL, SW_SHOWNORMAL);
+
+	/* MSDN: "If the function succeeds, it returns a value greater than 32. If the function fails, */
+	/*  it returns an error value that indicates the cause of the failure" */
+	return res > 32 ? 0 : (cc_result)res;
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------------Updater----------------------------------------------------------*
+*#########################################################################################################################*/
+#define UPDATE_TMP TEXT("CC_prev.exe")
+#define UPDATE_SRC TEXT(UPDATE_FILE)
+cc_bool Updater_Supported = true;
+
+#if defined _M_IX86
+const struct UpdaterInfo Updater_Info = {
+	"&eDirect3D 9 is recommended", 2,
+	{
+		{ "Direct3D9", "ClassiCube.exe" },
+		{ "OpenGL",    "ClassiCube.opengl.exe" }
+	}
+};
+#elif defined _M_X64
+const struct UpdaterInfo Updater_Info = {
+	"&eDirect3D 9 is recommended", 2,
+	{
+		{ "Direct3D9", "ClassiCube.64.exe" },
+		{ "OpenGL",    "ClassiCube.64-opengl.exe" }
+	}
+};
+#elif defined _M_ARM64
+const struct UpdaterInfo Updater_Info = { "", 1, { { "Direct3D11", "cc-arm64-d3d11.exe" } } };
+#elif defined _M_ARM
+const struct UpdaterInfo Updater_Info = { "", 1, { { "Direct3D11", "cc-arm32-d3d11.exe" } } };
+#else
+const struct UpdaterInfo Updater_Info = { "&eCompile latest source code to update", 0 };
+#endif
+
+cc_bool Updater_Clean(void) {
+	return DeleteFile(UPDATE_TMP) || GetLastError() == ERROR_FILE_NOT_FOUND;
+}
+
+cc_result Updater_Start(const char** action) {
+	cc_winstring path;
+	cc_result res;
+	int len;
+
+	*action = "Getting executable path";
+	if ((res = Process_RawGetExePath(&path, &len))) return res;
+
+	*action = "Moving executable to CC_prev.exe"; 
+	if (!path.uni[0]) return ERR_NOT_SUPPORTED; /* MoveFileA returns ERROR_ACCESS_DENIED on Win 9x anyways */
+	if (!MoveFileExW(path.uni, UPDATE_TMP, MOVEFILE_REPLACE_EXISTING)) return GetLastError();
+
+	*action = "Replacing executable";
+	if (!MoveFileExW(UPDATE_SRC, path.uni, MOVEFILE_REPLACE_EXISTING)) return GetLastError();
+
+	*action = "Restarting game";
+	return Process_StartGame2(NULL, 0);
+}
+
+cc_result Updater_GetBuildTime(cc_uint64* timestamp) {
+	cc_winstring path;
+	cc_file file;
+	FILETIME ft;
+	cc_uint64 raw;
+	cc_result res;
+	int len;
+
+	if ((res = Process_RawGetExePath(&path, &len))) return res;
+	if ((res = DoFileRaw(&file, &path, GENERIC_READ, OPEN_EXISTING))) return res;
+
+	if (GetFileTime(file, NULL, NULL, &ft)) {
+		raw        = ft.dwLowDateTime | ((cc_uint64)ft.dwHighDateTime << 32);
+		*timestamp = FileTime_UnixTime(raw);
+	} else {
+		res = GetLastError();
+	}
+
+	File_Close(file);
+	return res;
+}
+
+/* Don't need special execute permission on windows */
+cc_result Updater_MarkExecutable(void) { return 0; }
+cc_result Updater_SetNewBuildTime(cc_uint64 timestamp) {
+	static const cc_string path = String_FromConst(UPDATE_FILE);
+	cc_file file;
+	FILETIME ft;
+	cc_uint64 raw;
+	cc_result res = File_OpenOrCreate(&file, &path);
+	if (res) return res;
+
+	raw = 10000000 * (timestamp + FILETIME_UNIX_EPOCH);
+	ft.dwLowDateTime  = (cc_uint32)raw;
+	ft.dwHighDateTime = (cc_uint32)(raw >> 32);
+
+	if (!SetFileTime(file, NULL, NULL, &ft)) res = GetLastError();
+	File_Close(file);
+	return res;
+}
+
+
+/*########################################################################################################################*
+*-------------------------------------------------------Dynamic lib-------------------------------------------------------*
+*#########################################################################################################################*/
+const cc_string DynamicLib_Ext = String_FromConst(".dll");
+static cc_result dynamicErr;
+static cc_bool loadingPlugin;
+
+void* DynamicLib_Load2(const cc_string* path) {
+	static cc_string plugins_dir = String_FromConst("plugins/");
+	cc_winstring str;
+	void* lib;
+
+	Platform_EncodeString(&str, path);
+	loadingPlugin = String_CaselessStarts(path, &plugins_dir);
+
+	if ((lib = LoadLibraryW(str.uni))) return lib;
+	dynamicErr = GetLastError();
+	if (dynamicErr != ERROR_CALL_NOT_IMPLEMENTED) return NULL;
+
+	/* Windows 9x only supports A variants */
+	lib = LoadLibraryA(str.ansi);
+	if (!lib) dynamicErr = GetLastError();
+	return lib;
+}
+
+void* DynamicLib_Get2(void* lib, const char* name) {
+	void* addr = GetProcAddress((HMODULE)lib, name);
+	if (!addr) dynamicErr = GetLastError();
+	return addr;
+}
+
+cc_bool DynamicLib_DescribeError(cc_string* dst) {
+	cc_result res = dynamicErr;
+	dynamicErr = 0; /* Reset error (match posix behaviour) */
+
+	Platform_DescribeError(res, dst);
+	String_Format1(dst, " (error %e)", &res);
+
+	/* Plugin may have been compiled to load symbols from ClassiCube.exe, */
+	/*  but the user might have renamed it to something else */
+	if (res == ERROR_MOD_NOT_FOUND  && loadingPlugin) {
+		String_AppendConst(dst, "\n    Make sure the ClassiCube executable is named ClassiCube.exe");
+	}
+	if (res == ERROR_PROC_NOT_FOUND && loadingPlugin) {
+		String_AppendConst(dst, "\n    The plugin or your game may be outdated");
+	}
+
+	/* User might be trying to use 32 bit plugin with 64 bit executable, or vice versa */
+	if (res == ERROR_BAD_EXE_FORMAT && loadingPlugin) {
+		if (sizeof(cc_uintptr) == 4) {
+			String_AppendConst(dst, "\n    Try using a 32 bit version of the plugin instead");
+		} else {
+			String_AppendConst(dst, "\n    Try using a 64 bit version of the plugin instead");
+		}
+	}
+	return true;
+}
+
+
+/*########################################################################################################################*
+*--------------------------------------------------------Platform---------------------------------------------------------*
+*#########################################################################################################################*/
+void Platform_EncodeString(cc_winstring* dst, const cc_string* src) {
+	cc_unichar* uni;
+	char* ansi;
+	int i;
+	if (src->length > FILENAME_SIZE) Logger_Abort("String too long to expand");
+
+	uni = dst->uni;
+	for (i = 0; i < src->length; i++) {
+		*uni++ = Convert_CP437ToUnicode(src->buffer[i]);
+	}
+	*uni = '\0';
+
+	ansi = dst->ansi;
+	for (i = 0; i < src->length; i++) {
+		*ansi++ = (char)dst->uni[i];
+	}
+	*ansi = '\0';
+}
+
+static void Platform_InitStopwatch(void) {
+	LARGE_INTEGER freq;
+	sw_highRes = QueryPerformanceFrequency(&freq);
+
+	if (sw_highRes) {
+		sw_freqMul = 1000 * 1000;
+		sw_freqDiv = freq.QuadPart;
+	} else { sw_freqDiv = 10; }
+}
+
+static BOOL (WINAPI *_AttachConsole)(DWORD processId);
+static BOOL (WINAPI *_IsDebuggerPresent)(void);
+
+static void LoadKernelFuncs(void) {
+	static const struct DynamicLibSym funcs[] = {
+		DynamicLib_Sym(AttachConsole), DynamicLib_Sym(IsDebuggerPresent)
+	};
+
+	static const cc_string kernel32 = String_FromConst("KERNEL32.DLL");
+	void* lib;
+	DynamicLib_LoadAll(&kernel32, funcs, Array_Elems(funcs), &lib);
+}
+
+void Platform_Init(void) {
+	WSADATA wsaData;
+	cc_result res;
+
+	Platform_InitStopwatch();
+	heap = GetProcessHeap();
+
+	LoadWinsockFuncs();
+	res = _WSAStartup(MAKEWORD(2, 2), &wsaData);
+	if (res) Logger_SysWarn(res, "starting WSA");
+
+	LoadKernelFuncs();
+	if (_IsDebuggerPresent) hasDebugger = _IsDebuggerPresent();
+	/* For when user runs from command prompt */
+#if CC_WIN_BACKEND != CC_WIN_BACKEND_TERMINAL
+	if (_AttachConsole) _AttachConsole(-1); /* ATTACH_PARENT_PROCESS */
+#endif
+
+	conHandle = GetStdHandle(STD_OUTPUT_HANDLE);
+	if (conHandle == INVALID_HANDLE_VALUE) conHandle = NULL;
+}
+
+void Platform_Free(void) {
+	_WSACleanup();
+	HeapDestroy(heap);
+}
+
+cc_bool Platform_DescribeErrorExt(cc_result res, cc_string* dst, void* lib) {
+	WCHAR chars[NATIVE_STR_LEN];
+	DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
+	if (lib) flags |= FORMAT_MESSAGE_FROM_HMODULE;
+
+	res = FormatMessageW(flags, lib, res, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
+						 chars, NATIVE_STR_LEN, NULL);
+	if (!res) return false;
+
+	String_AppendUtf16(dst, chars, res * 2);
+	return true;
+}
+
+cc_bool Platform_DescribeError(cc_result res, cc_string* dst) {
+	return Platform_DescribeErrorExt(res, dst, NULL);
+}
+
+
+/*########################################################################################################################*
+*-------------------------------------------------------Encryption--------------------------------------------------------*
+*#########################################################################################################################*/
+
+static void LoadCryptFuncs(void) {
+	static const struct DynamicLibSym funcs[] = {
+		DynamicLib_Sym(CryptProtectData), DynamicLib_Sym(CryptUnprotectData)
+	};
+
+	static const cc_string crypt32 = String_FromConst("CRYPT32.DLL");
+	void* lib;
+	DynamicLib_LoadAll(&crypt32, funcs, Array_Elems(funcs), &lib);
+}
+
+cc_result Platform_Encrypt(const void* data, int len, cc_string* dst) {
+	DATA_BLOB input, output;
+	int i;
+	input.cbData = len; input.pbData = (BYTE*)data;
+
+	if (!_CryptProtectData) LoadCryptFuncs();
+	if (!_CryptProtectData) return ERR_NOT_SUPPORTED;
+	if (!_CryptProtectData(&input, NULL, NULL, NULL, NULL, 0, &output)) return GetLastError();
+
+	for (i = 0; i < output.cbData; i++) {
+		String_Append(dst, output.pbData[i]);
+	}
+	LocalFree(output.pbData);
+	return 0;
+}
+cc_result Platform_Decrypt(const void* data, int len, cc_string* dst) {
+	DATA_BLOB input, output;
+	int i;
+	input.cbData = len; input.pbData = (BYTE*)data;
+
+	if (!_CryptUnprotectData) LoadCryptFuncs();
+	if (!_CryptUnprotectData) return ERR_NOT_SUPPORTED;
+	if (!_CryptUnprotectData(&input, NULL, NULL, NULL, NULL, 0, &output)) return GetLastError();
+
+	for (i = 0; i < output.cbData; i++) {
+		String_Append(dst, output.pbData[i]);
+	}
+	LocalFree(output.pbData);
+	return 0;
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------Configuration-------------------------------------------------------*
+*#########################################################################################################################*/
+static cc_string Platform_NextArg(STRING_REF cc_string* args) {
+	cc_string arg;
+	int end;
+
+	/* get rid of leading spaces before arg */
+	while (args->length && args->buffer[0] == ' ') {
+		*args = String_UNSAFE_SubstringAt(args, 1);
+	}
+
+	if (args->length && args->buffer[0] == '"') {
+		/* "xy za" is used for arg with spaces */
+		*args = String_UNSAFE_SubstringAt(args, 1);
+		end = String_IndexOf(args, '"');
+	} else {
+		end = String_IndexOf(args, ' ');
+	}
+
+	if (end == -1) {
+		arg   = *args;
+		args->length = 0;
+	} else {
+		arg   = String_UNSAFE_Substring(args, 0, end);
+		*args = String_UNSAFE_SubstringAt(args, end + 1);
+	}
+	return arg;
+}
+
+int Platform_GetCommandLineArgs(int argc, STRING_REF char** argv, cc_string* args) {
+	cc_string cmdArgs = String_FromReadonly(GetCommandLineA());
+	int i;
+	Platform_NextArg(&cmdArgs); /* skip exe path */
+	if (Platform_SingleProcess) return GetGameArgs(args);
+
+	for (i = 0; i < GAME_MAX_CMDARGS; i++) 
+	{
+		args[i] = Platform_NextArg(&cmdArgs);
+
+		if (!args[i].length) break;
+	}
+	return i;
+}
+
+/* Detects if the game is running in Windows directory */
+/* This happens when ClassiCube is launched directly from shell process */
+/*  (e.g. via clicking a search result in Windows 10 start menu) */
+static cc_bool IsProblematicWorkingDirectory(void) {
+	cc_string curDir, winDir;
+	char curPath[2048] = { 0 };
+	char winPath[2048] = { 0 };
+
+	GetCurrentDirectoryA(2048, curPath);
+	GetSystemDirectoryA(winPath, 2048);
+
+	curDir = String_FromReadonly(curPath);
+	winDir = String_FromReadonly(winPath);
+	
+	if (String_Equals(&curDir, &winDir)) {
+		Platform_LogConst("Working directory is System32! Changing to executable directory..");
+		return true;
+	}
+	return false;
+}
+
+cc_result Platform_SetDefaultCurrentDirectory(int argc, char** argv) {
+	cc_winstring path;
+	int i, len;
+	cc_result res;
+	if (!IsProblematicWorkingDirectory()) return 0;
+
+	res = Process_RawGetExePath(&path, &len);
+	if (res) return res;
+	if (!path.uni[0]) return ERR_NOT_SUPPORTED; 
+	/* Not implemented on ANSI only systems due to laziness */
+
+	/* Get rid of filename at end of directory */
+	for (i = len - 1; i >= 0; i--, len--) 
+	{
+		if (path.uni[i] == '/' || path.uni[i] == '\\') break;
+	}
+
+	path.uni[len] = '\0';
+	return SetCurrentDirectoryW(path.uni) ? 0 : GetLastError();
+}
+#endif