From ef6203801101fd794562cda7e57c717d21074c9d Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sun, 23 May 2021 10:04:34 +1000 Subject: [PATCH 1/9] Split up Platform.c into Platform_WinApi.c, Platform_Web.c, and Platform_Posix.c --- readme.md | 2 +- src/ClassiCube.vcxproj | 5 +- src/ClassiCube.vcxproj.filters | 15 +- src/{Platform.c => Platform_Posix.c} | 993 ++------------------------- src/Platform_Web.c | 409 +++++++++++ src/Platform_WinApi.c | 809 ++++++++++++++++++++++ src/_PlatformBase.h | 110 +++ 7 files changed, 1385 insertions(+), 958 deletions(-) rename src/{Platform.c => Platform_Posix.c} (57%) create mode 100644 src/Platform_Web.c create mode 100644 src/Platform_WinApi.c create mode 100644 src/_PlatformBase.h diff --git a/readme.md b/readme.md index 6e44530c6..80b54b8dd 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -ClassiCube is a custom Minecraft Classic and ClassiCube client written in C that works on Windows, macOS, Linux, Android, BSD, Solaris, Haiku, and in a browser. +ClassiCube is a custom Minecraft Classic and ClassiCube client written in C that works on Windows, macOS, Linux, Android, FreeBSD, NetBSD, OpenBSD, Solaris, Haiku, and in a browser. **It is not affiliated with (or supported by) Mojang AB, Minecraft, or Microsoft in any way.** ![screenshot_n](http://i.imgur.com/FCiwl27.png) diff --git a/src/ClassiCube.vcxproj b/src/ClassiCube.vcxproj index 867315aca..cf6d0a7ee 100644 --- a/src/ClassiCube.vcxproj +++ b/src/ClassiCube.vcxproj @@ -249,6 +249,7 @@ + @@ -280,6 +281,9 @@ + + + @@ -293,7 +297,6 @@ - diff --git a/src/ClassiCube.vcxproj.filters b/src/ClassiCube.vcxproj.filters index 7a8c44c58..d1b1ea021 100644 --- a/src/ClassiCube.vcxproj.filters +++ b/src/ClassiCube.vcxproj.filters @@ -312,6 +312,9 @@ Header Files\TexturePack + + Header Files\Platform + @@ -431,9 +434,6 @@ Source Files\Audio - - Source Files\Platform - Source Files\Entities @@ -536,5 +536,14 @@ Source Files\TexturePack + + Source Files\Platform + + + Source Files\Platform + + + Source Files\Platform + \ No newline at end of file diff --git a/src/Platform.c b/src/Platform_Posix.c similarity index 57% rename from src/Platform.c rename to src/Platform_Posix.c index bd789c1fc..fcfc25517 100644 --- a/src/Platform.c +++ b/src/Platform_Posix.c @@ -1,6 +1,7 @@ -#include "Platform.h" -#include "String.h" -#include "Logger.h" +#include "Core.h" +#if defined CC_BUILD_POSIX + +#include "_PlatformBase.h" #include "Stream.h" #include "ExtMath.h" #include "Drawer2D.h" @@ -9,30 +10,6 @@ #include "Utils.h" #include "Errors.h" -#if defined CC_BUILD_WIN -#define WIN32_LEAN_AND_MEAN -#define NOSERVICE -#define NOMCX -#define NOIME -#ifndef UNICODE -#define UNICODE -#define _UNICODE -#endif - -#include -#include -#include -#include -#include - -#define Socket__Error() WSAGetLastError() -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; -#elif defined CC_BUILD_POSIX /* POSIX can be shared between Linux/BSD/macOS */ #include #include @@ -60,7 +37,7 @@ const cc_result ReturnCode_FileNotFound = ENOENT; const cc_result ReturnCode_SocketInProgess = EINPROGRESS; const cc_result ReturnCode_SocketWouldBlock = EWOULDBLOCK; const cc_result ReturnCode_DirectoryExists = EEXIST; -#endif + /* Platform specific include files (Try to share for UNIX-ish) */ #if defined CC_BUILD_DARWIN #include @@ -76,9 +53,6 @@ const cc_result ReturnCode_DirectoryExists = EEXIST; /* TODO: Use load_image/resume_thread instead of fork */ /* Otherwise opening browser never works because fork fails */ #include -#elif defined CC_BUILD_WEB -#include -#include "Chat.h" #endif @@ -88,70 +62,6 @@ const cc_result ReturnCode_DirectoryExists = EEXIST; void Mem_Set(void* dst, cc_uint8 value, cc_uint32 numBytes) { memset(dst, value, numBytes); } void Mem_Copy(void* dst, const void* src, cc_uint32 numBytes) { memcpy(dst, src, numBytes); } -int Mem_Equal(const void* a, const void* b, cc_uint32 numBytes) { - const cc_uint8* src = (const cc_uint8*)a; - const cc_uint8* dst = (const cc_uint8*)b; - - while (numBytes--) { - if (*src++ != *dst++) return false; - } - return true; -} - -CC_NOINLINE static void AbortOnAllocFailed(const char* place) { - cc_string log; char logBuffer[STRING_SIZE+20 + 1]; - String_InitArray_NT(log, logBuffer); - - String_Format1(&log, "Out of memory! (when allocating %c)", place); - log.buffer[log.length] = '\0'; - Logger_Abort(log.buffer); -} - -void* Mem_Alloc(cc_uint32 numElems, cc_uint32 elemsSize, const char* place) { - void* ptr = Mem_TryAlloc(numElems, elemsSize); - if (!ptr) AbortOnAllocFailed(place); - return ptr; -} - -void* Mem_AllocCleared(cc_uint32 numElems, cc_uint32 elemsSize, const char* place) { - void* ptr = Mem_TryAllocCleared(numElems, elemsSize); - if (!ptr) AbortOnAllocFailed(place); - return ptr; -} - -void* Mem_Realloc(void* mem, cc_uint32 numElems, cc_uint32 elemsSize, const char* place) { - void* ptr = Mem_TryRealloc(mem, numElems, elemsSize); - if (!ptr) AbortOnAllocFailed(place); - return ptr; -} - -static CC_NOINLINE cc_uint32 CalcMemSize(cc_uint32 numElems, cc_uint32 elemsSize) { - if (!numElems) return 1; /* treat 0 size as 1 byte */ - cc_uint32 numBytes = numElems * elemsSize; /* TODO: avoid overflow here */ - if (numBytes < numElems) return 0; /* TODO: Use proper overflow checking */ - return numBytes; -} - -#if defined CC_BUILD_WIN -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); -} -#elif defined CC_BUILD_POSIX void* Mem_TryAlloc(cc_uint32 numElems, cc_uint32 elemsSize) { cc_uint32 size = CalcMemSize(numElems, elemsSize); return size ? malloc(size) : NULL; @@ -169,34 +79,11 @@ void* Mem_TryRealloc(void* mem, cc_uint32 numElems, cc_uint32 elemsSize) { void Mem_Free(void* mem) { if (mem) free(mem); } -#endif /*########################################################################################################################* *------------------------------------------------------Logging/Time-------------------------------------------------------* *#########################################################################################################################*/ -void Platform_Log1(const char* format, const void* a1) { - Platform_Log4(format, a1, NULL, NULL, NULL); -} -void Platform_Log2(const char* format, const void* a1, const void* a2) { - Platform_Log4(format, a1, a2, NULL, NULL); -} -void Platform_Log3(const char* format, const void* a1, const void* a2, const void* a3) { - Platform_Log4(format, a1, a2, a3, NULL); -} - -void Platform_Log4(const char* format, const void* a1, const void* a2, const void* a3, const void* a4) { - cc_string msg; char msgBuffer[512]; - String_InitArray(msg, msgBuffer); - - String_Format4(&msg, format, a1, a2, a3, a4); - Platform_Log(msg.buffer, msg.length); -} - -void Platform_LogConst(const char* message) { - Platform_Log(message, String_Length(message)); -} - /* 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) { @@ -204,73 +91,6 @@ cc_uint64 Stopwatch_ElapsedMicroseconds(cc_uint64 beg, cc_uint64 end) { return ((end - beg) * sw_freqMul) / sw_freqDiv; } -int Stopwatch_ElapsedMS(cc_uint64 beg, cc_uint64 end) { - cc_uint64 raw = Stopwatch_ElapsedMicroseconds(beg, end); - if (raw > Int32_MaxValue) return Int32_MaxValue / 1000; - return (int)raw / 1000; -} - -#if defined CC_BUILD_WIN -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 50491123200000ULL -#define FILETIME_UNIX_EPOCH 11644473600LL -#define FileTime_TotalMS(time) ((time / 10000) + FILETIME_EPOCH) -#define FileTime_UnixTime(time) ((time / 10000000) - FILETIME_UNIX_EPOCH) -TimeMS DateTime_CurrentUTC_MS(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_TotalMS(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); - } -} -#elif defined CC_BUILD_POSIX /* log to android logcat */ #ifdef CC_BUILD_ANDROID #include @@ -310,19 +130,13 @@ void DateTime_CurrentLocal(struct DateTime* t) { } #define NS_PER_SEC 1000000000ULL -#endif /* clock_gettime is optional, see http://pubs.opengroup.org/onlinepubs/009696899/functions/clock_getres.html */ /* "... These functions are part of the Timers option and need not be available on all implementations..." */ -#if defined CC_BUILD_WEB -cc_uint64 Stopwatch_Measure(void) { - /* time is a milliseconds double */ - return (cc_uint64)(emscripten_get_now() * 1000); -} -#elif defined CC_BUILD_DARWIN +#if defined CC_BUILD_DARWIN cc_uint64 Stopwatch_Measure(void) { return mach_absolute_time(); } #elif defined CC_BUILD_SOLARIS cc_uint64 Stopwatch_Measure(void) { return gethrtime(); } -#elif defined CC_BUILD_POSIX +#else cc_uint64 Stopwatch_Measure(void) { struct timespec t; /* TODO: CLOCK_MONOTONIC_RAW ?? */ @@ -335,147 +149,6 @@ cc_uint64 Stopwatch_Measure(void) { /*########################################################################################################################* *-----------------------------------------------------Directory/File------------------------------------------------------* *#########################################################################################################################*/ -#if defined CC_BUILD_WIN -cc_result Directory_Create(const cc_string* path) { - WCHAR str[NATIVE_STR_LEN]; - cc_result res; - - Platform_EncodeUtf16(str, path); - if (CreateDirectoryW(str, NULL)) return 0; - if ((res = GetLastError()) != ERROR_CALL_NOT_IMPLEMENTED) return res; - - Platform_Utf16ToAnsi(str); - return CreateDirectoryA((LPCSTR)str, NULL) ? 0 : GetLastError(); -} - -int File_Exists(const cc_string* path) { - WCHAR str[NATIVE_STR_LEN]; - DWORD attribs; - - Platform_EncodeUtf16(str, path); - attribs = GetFileAttributesW(str); - 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]; - WCHAR str[NATIVE_STR_LEN]; - WIN32_FIND_DATAW eW; - WIN32_FIND_DATAA eA; - int i, ansi = false; - HANDLE find; - cc_result res; - - /* Need to append \* to search for files in directory */ - String_InitArray(path, pathBuffer); - String_Format1(&path, "%s\\*", dirPath); - Platform_EncodeUtf16(str, &path); - - find = FindFirstFileW(str, &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 */ - Platform_Utf16ToAnsi(str); - find = FindFirstFileA((LPCSTR)str, &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 DoFile(cc_file* file, const cc_string* path, DWORD access, DWORD createMode) { - WCHAR str[NATIVE_STR_LEN]; - cc_result res; - Platform_EncodeUtf16(str, path); - - *file = CreateFileW(str, 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 */ - Platform_Utf16ToAnsi(str); - *file = CreateFileA((LPCSTR)str, access, FILE_SHARE_READ, NULL, createMode, 0, NULL); - return *file != INVALID_HANDLE_VALUE ? 0 : GetLastError(); -} - -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[3] = { 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(); -} -#elif defined CC_BUILD_POSIX cc_result Directory_Create(const cc_string* path) { char str[NATIVE_STR_LEN]; Platform_EncodeUtf8(str, path); @@ -563,14 +236,7 @@ cc_result File_Write(cc_file file, const void* data, cc_uint32 count, cc_uint32* } cc_result File_Close(cc_file file) { -#ifndef CC_BUILD_WEB return close(file) == -1 ? errno : 0; -#else - extern void interop_SyncFS(void); - int res = close(file) == -1 ? errno : 0; - interop_SyncFS(); - return res; -#endif } cc_result File_Seek(cc_file file, int offset, int seekType) { @@ -588,93 +254,11 @@ cc_result File_Length(cc_file file, cc_uint32* len) { if (fstat(file, &st) == -1) { *len = -1; return errno; } *len = st.st_size; return 0; } -#endif /*########################################################################################################################* *--------------------------------------------------------Threading--------------------------------------------------------* *#########################################################################################################################*/ -#if defined CC_BUILD_WIN -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_Start(Thread_StartFunc func) { - DWORD threadID; - void* handle = CreateThread(NULL, 0, ExecThread, (void*)func, 0, &threadID); - if (!handle) { - Logger_Abort2(GetLastError(), "Creating thread"); - } - return handle; -} - -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(void) { - 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(void) { - 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); -} -#elif defined CC_BUILD_WEB -/* No real threading support with emscripten backend */ -void Thread_Sleep(cc_uint32 milliseconds) { } -void* Thread_Start(Thread_StartFunc func) { func(); return NULL; } -void Thread_Detach(void* handle) { } -void Thread_Join(void* handle) { } - -void* Mutex_Create(void) { return NULL; } -void Mutex_Free(void* handle) { } -void Mutex_Lock(void* handle) { } -void Mutex_Unlock(void* handle) { } - -void* Waitable_Create(void) { return NULL; } -void Waitable_Free(void* handle) { } -void Waitable_Signal(void* handle) { } -void Waitable_Wait(void* handle) { } -void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) { } -#elif defined CC_BUILD_POSIX void Thread_Sleep(cc_uint32 milliseconds) { usleep(milliseconds * 1000); } #ifdef CC_BUILD_ANDROID @@ -819,41 +403,18 @@ void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) { ptr->signalled = false; Mutex_Unlock(&ptr->mutex); } -#endif /*########################################################################################################################* *--------------------------------------------------------Font/Text--------------------------------------------------------* *#########################################################################################################################*/ -#ifdef CC_BUILD_WEB -void Platform_LoadSysFonts(void) { } -#else 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); } void Platform_LoadSysFonts(void) { int i; -#if defined CC_BUILD_WIN - char winFolder[FILENAME_SIZE]; - WCHAR winTmp[FILENAME_SIZE]; - UINT winLen; - /* System folder path may not be C:/Windows */ - cc_string dirs[1]; - String_InitArray(dirs[0], winFolder); - - 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"); - -#elif defined CC_BUILD_ANDROID +#if defined CC_BUILD_ANDROID static const cc_string dirs[3] = { String_FromConst("/system/fonts"), String_FromConst("/system/font"), @@ -874,7 +435,7 @@ void Platform_LoadSysFonts(void) { String_FromConst("/System/Library/Fonts"), String_FromConst("/Library/Fonts") }; -#elif defined CC_BUILD_POSIX +#else static const cc_string dirs[2] = { String_FromConst("/usr/share/fonts"), String_FromConst("/usr/local/share/fonts") @@ -884,7 +445,6 @@ void Platform_LoadSysFonts(void) { Directory_Enum(&dirs[i], NULL, FontDirCallback); } } -#endif /*########################################################################################################################* @@ -892,27 +452,19 @@ void Platform_LoadSysFonts(void) { *#########################################################################################################################*/ cc_result Socket_Create(cc_socket* s) { *s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - return *s == -1 ? Socket__Error() : 0; + return *s == -1 ? errno : 0; } static cc_result Socket_ioctl(cc_socket s, cc_uint32 cmd, int* data) { -#if defined CC_BUILD_WIN - return ioctlsocket(s, cmd, data); -#else return ioctl(s, cmd, data); -#endif } cc_result Socket_Available(cc_socket s, int* available) { - return Socket_ioctl(s, FIONREAD, available); + return ioctl(s, FIONREAD, available); } cc_result Socket_SetBlocking(cc_socket s, cc_bool blocking) { -#if defined CC_BUILD_WEB - return ERR_NOT_SUPPORTED; /* sockets always async */ -#else int blocking_raw = blocking ? 0 : -1; - return Socket_ioctl(s, FIONBIO, &blocking_raw); -#endif + return ioctl(s, FIONBIO, &blocking_raw); } cc_result Socket_GetError(cc_socket s, cc_result* result) { @@ -930,79 +482,34 @@ cc_result Socket_Connect(cc_socket s, const cc_string* ip, int port) { return ERR_INVALID_ARGUMENT; res = connect(s, &addr, sizeof(addr)); - return res == -1 ? Socket__Error() : 0; + return res == -1 ? errno : 0; } cc_result Socket_Read(cc_socket s, cc_uint8* data, cc_uint32 count, cc_uint32* modified) { -#ifdef CC_BUILD_WEB - /* recv only reads one WebSocket frame at most, hence call it multiple times */ - int recvCount = 0, pending; - *modified = 0; - - while (count && !Socket_Available(s, &pending) && pending) { - recvCount = recv(s, data, count, 0); - if (recvCount == -1) return Socket__Error(); - - *modified += recvCount; - data += recvCount; count -= recvCount; - } - return 0; -#else int recvCount = recv(s, data, count, 0); if (recvCount != -1) { *modified = recvCount; return 0; } - *modified = 0; return Socket__Error(); -#endif + *modified = 0; return errno; } cc_result Socket_Write(cc_socket s, const cc_uint8* data, cc_uint32 count, cc_uint32* modified) { int sentCount = send(s, data, count, 0); if (sentCount != -1) { *modified = sentCount; return 0; } - *modified = 0; return Socket__Error(); + *modified = 0; return errno; } cc_result Socket_Close(cc_socket s) { cc_result res = 0; cc_result res1, res2; -#if defined CC_BUILD_WEB - res1 = 0; -#elif defined CC_BUILD_WIN - res1 = shutdown(s, SD_BOTH); -#else res1 = shutdown(s, SHUT_RDWR); -#endif - if (res1 == -1) res = Socket__Error(); + if (res1 == -1) res = errno; -#if defined CC_BUILD_WIN - res2 = closesocket(s); -#else res2 = close(s); -#endif - if (res2 == -1) res = Socket__Error(); + if (res2 == -1) res = errno; return res; } -/* Alas, a simple cross-platform select() is not good enough */ -#if defined CC_BUILD_WIN -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 Socket__Error(); } - - *success = set.fd_count != 0; return 0; -} -#elif defined CC_BUILD_DARWIN +#if defined CC_BUILD_DARWIN /* poll is broken on old OSX apparently https://daniel.haxx.se/docs/poll-vs-select.html */ cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) { fd_set set; @@ -1018,7 +525,7 @@ cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) { selectCount = select(s + 1, NULL, &set, NULL, &time); } - if (selectCount == -1) { *success = false; return Socket__Error(); } + if (selectCount == -1) { *success = false; return errno; } *success = FD_ISSET(s, &set) != 0; return 0; } #else @@ -1029,7 +536,7 @@ cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) { pfd.fd = s; pfd.events = mode == SOCKET_POLL_READ ? POLLIN : POLLOUT; - if (poll(&pfd, 1, 0) == -1) { *success = false; return Socket__Error(); } + if (poll(&pfd, 1, 0) == -1) { *success = false; return errno; } /* to match select, closed socket still counts as readable */ flags = mode == SOCKET_POLL_READ ? (POLLIN | POLLHUP) : POLLOUT; @@ -1042,72 +549,7 @@ cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) { /*########################################################################################################################* *-----------------------------------------------------Process/Module------------------------------------------------------* *#########################################################################################################################*/ -#if defined CC_BUILD_WIN -static cc_result Process_RawGetExePath(WCHAR* path, int* len) { - *len = GetModuleFileNameW(NULL, path, NATIVE_STR_LEN); - return *len ? 0 : GetLastError(); -} - -cc_result Process_StartGame(const cc_string* args) { - WCHAR path[NATIVE_STR_LEN + 1], raw[NATIVE_STR_LEN]; - cc_string argv; char argvBuffer[NATIVE_STR_LEN]; - STARTUPINFOW si = { 0 }; - PROCESS_INFORMATION pi = { 0 }; - cc_result res; - int len; - - Process_RawGetExePath(path, &len); - path[len] = '\0'; - si.cb = sizeof(STARTUPINFOW); - - String_InitArray(argv, argvBuffer); - /* Game doesn't actually care about argv[0] */ - String_Format1(&argv, "cc %s", args); - String_UNSAFE_TrimEnd(&argv); - Platform_EncodeUtf16(raw, &argv); - - if (CreateProcessW(path, raw, NULL, NULL, - false, 0, NULL, NULL, &si, &pi)) goto success; - if ((res = GetLastError()) != ERROR_CALL_NOT_IMPLEMENTED) return res; - - /* Windows 9x does not support W API functions */ - len = GetModuleFileNameA(NULL, (LPSTR)path, NATIVE_STR_LEN); - ((char*)path)[len] = '\0'; - Platform_Utf16ToAnsi(raw); - - if (CreateProcessA((LPCSTR)path, (LPSTR)raw, NULL, NULL, - false, 0, NULL, NULL, &si, &pi)) goto success; - return GetLastError(); - -success: - /* 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) { - WCHAR str[NATIVE_STR_LEN]; - cc_uintptr res; - Platform_EncodeUtf16(str, args); - - res = (cc_uintptr)ShellExecuteW(NULL, NULL, str, 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; -} -#elif defined CC_BUILD_WEB -cc_result Process_StartGame(const cc_string* args) { return ERR_NOT_SUPPORTED; } -void Process_Exit(cc_result code) { exit(code); } - -extern int interop_OpenTab(const char* url); -cc_result Process_StartOpen(const cc_string* args) { - char str[NATIVE_STR_LEN]; - Platform_EncodeUtf8(str, args); - return interop_OpenTab(str); -} -#elif defined CC_BUILD_ANDROID +#if defined CC_BUILD_ANDROID static char gameArgsBuffer[512]; static cc_string gameArgs = String_FromArray(gameArgsBuffer); @@ -1115,13 +557,7 @@ cc_result Process_StartGame(const cc_string* args) { String_Copy(&gameArgs, args); return 0; /* TODO: Is there a clean way of handling an error */ } -void Process_Exit(cc_result code) { exit(code); } - -cc_result Process_StartOpen(const cc_string* args) { - JavaCall_String_Void("startOpen", args); - return 0; -} -#elif defined CC_BUILD_POSIX +#else static cc_result Process_RawStart(const char* path, char** argv) { pid_t pid = fork(); if (pid == -1) return errno; @@ -1164,11 +600,16 @@ cc_result Process_StartGame(const cc_string* args) { argv[j] = NULL; return Process_RawStart(path, argv); } - +#endif void Process_Exit(cc_result code) { exit(code); } /* Opening browser/starting shell is not really standardised */ -#if defined CC_BUILD_MACOS +#if defined CC_BUILD_ANDROID +cc_result Process_StartOpen(const cc_string* args) { + JavaCall_String_Void("startOpen", args); + return 0; +} +#elif defined CC_BUILD_MACOS cc_result Process_StartOpen(const cc_string* args) { UInt8 str[NATIVE_STR_LEN]; CFURLRef urlCF; @@ -1281,113 +722,29 @@ static cc_result Process_RawGetExePath(char* path, int* len) { return 0; } #endif -#endif /* CC_BUILD_POSIX */ /*########################################################################################################################* *--------------------------------------------------------Updater----------------------------------------------------------* *#########################################################################################################################*/ -#if defined CC_BUILD_WIN -#define UPDATE_TMP TEXT("CC_prev.exe") -#define UPDATE_SRC TEXT(UPDATE_FILE) - -#if _WIN64 -const char* const Updater_D3D9 = "ClassiCube.64.exe"; -const char* const Updater_OGL = "ClassiCube.64-opengl.exe"; -#else -const char* const Updater_D3D9 = "ClassiCube.exe"; -const char* const Updater_OGL = "ClassiCube.opengl.exe"; -#endif - -cc_bool Updater_Clean(void) { - return DeleteFile(UPDATE_TMP) || GetLastError() == ERROR_FILE_NOT_FOUND; -} - -cc_result Updater_Start(const char** action) { - WCHAR path[NATIVE_STR_LEN + 1]; - cc_result res; - int len = 0; - - *action = "Getting executable path"; - if ((res = Process_RawGetExePath(path, &len))) return res; - path[len] = '\0'; - - *action = "Moving executable to CC_prev.exe"; - if (!MoveFileExW(path, UPDATE_TMP, MOVEFILE_REPLACE_EXISTING)) return GetLastError(); - *action = "Replacing executable"; - if (!MoveFileExW(UPDATE_SRC, path, MOVEFILE_REPLACE_EXISTING)) return GetLastError(); - - *action = "Restarting game"; - return Process_StartGame(&String_Empty); -} - -cc_result Updater_GetBuildTime(cc_uint64* timestamp) { - WCHAR path[NATIVE_STR_LEN + 1]; - cc_file file; - FILETIME ft; - cc_uint64 raw; - int len = 0; - - cc_result res = Process_RawGetExePath(path, &len); - if (res) return res; - path[len] = '\0'; - - file = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - if (file == INVALID_HANDLE_VALUE) return GetLastError(); - - 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; -} - -#elif defined CC_BUILD_WEB || defined CC_BUILD_ANDROID const char* const Updater_D3D9 = NULL; -const char* const Updater_OGL = NULL; +cc_bool Updater_Clean(void) { return true; } + +#if defined CC_BUILD_ANDROID +const char* const Updater_OGL = NULL; -#if defined CC_BUILD_WEB -cc_result Updater_GetBuildTime(cc_uint64* t) { return ERR_NOT_SUPPORTED; } -#else cc_result Updater_GetBuildTime(cc_uint64* t) { JNIEnv* env; JavaGetCurrentEnv(env); *t = JavaCallLong(env, "getApkUpdateTime", "()J", NULL); return 0; } -#endif -cc_bool Updater_Clean(void) { return true; } -cc_result Updater_Start(const char** action) { *action = "Updating game"; return ERR_NOT_SUPPORTED; } +cc_result Updater_Start(const char** action) { *action = "Updating game"; return ERR_NOT_SUPPORTED; } cc_result Updater_MarkExecutable(void) { return 0; } cc_result Updater_SetNewBuildTime(cc_uint64 t) { return ERR_NOT_SUPPORTED; } -#elif defined CC_BUILD_POSIX -cc_bool Updater_Clean(void) { return true; } +#else -const char* const Updater_D3D9 = NULL; #if defined CC_BUILD_LINUX #if __x86_64__ const char* const Updater_OGL = "ClassiCube"; @@ -1465,30 +822,7 @@ cc_result Updater_SetNewBuildTime(cc_uint64 timestamp) { /*########################################################################################################################* *-------------------------------------------------------Dynamic lib-------------------------------------------------------* *#########################################################################################################################*/ -#if defined CC_BUILD_WIN -const cc_string DynamicLib_Ext = String_FromConst(".dll"); - -void* DynamicLib_Load2(const cc_string* path) { - WCHAR str[NATIVE_STR_LEN]; - Platform_EncodeUtf16(str, path); - return LoadLibraryW(str); -} - -void* DynamicLib_Get2(void* lib, const char* name) { - return GetProcAddress((HMODULE)lib, name); -} - -cc_bool DynamicLib_DescribeError(cc_string* dst) { - cc_result res = GetLastError(); - Platform_DescribeError(res, dst); - String_Format1(dst, " (error %i)", &res); - return true; -} -#elif defined CC_BUILD_WEB -void* DynamicLib_Load2(const cc_string* path) { return NULL; } -void* DynamicLib_Get2(void* lib, const char* name) { return NULL; } -cc_bool DynamicLib_DescribeError(cc_string* dst) { return false; } -#elif defined MAC_OS_X_VERSION_MIN_REQUIRED && (MAC_OS_X_VERSION_MIN_REQUIRED < 1040) +#if defined MAC_OS_X_VERSION_MIN_REQUIRED && (MAC_OS_X_VERSION_MIN_REQUIRED < 1040) /* Really old mac OS versions don't have the dlopen/dlsym API */ const cc_string DynamicLib_Ext = String_FromConst(".dylib"); @@ -1524,7 +858,7 @@ cc_bool DynamicLib_DescribeError(cc_string* dst) { String_Format4(dst, "%c in %c (%i, sys %i)", msg, name, &err, &errNum); return true; } -#elif defined CC_BUILD_POSIX +#else #include /* TODO: Should we use .bundle instead of .dylib? */ @@ -1551,118 +885,10 @@ cc_bool DynamicLib_DescribeError(cc_string* dst) { } #endif -cc_result DynamicLib_Load(const cc_string* path, void** lib) { - *lib = DynamicLib_Load2(path); - return *lib == NULL; -} -cc_result DynamicLib_Get(void* lib, const char* name, void** symbol) { - *symbol = DynamicLib_Get2(lib, name); - return *symbol == NULL; -} - - -cc_bool DynamicLib_GetAll(void* lib, const struct DynamicLibSym* syms, int count) { - int i, loaded = 0; - void* addr; - - for (i = 0; i < count; i++) { - addr = DynamicLib_Get2(lib, syms[i].name); - if (addr) loaded++; - *syms[i].symAddr = addr; - } - return loaded == count; -} - /*########################################################################################################################* *--------------------------------------------------------Platform---------------------------------------------------------* *#########################################################################################################################*/ -#if defined CC_BUILD_WIN -int Platform_EncodeUtf16(void* data, const cc_string* src) { - WCHAR* dst = (WCHAR*)data; - int i; - if (src->length > FILENAME_SIZE) Logger_Abort("String too long to expand"); - - for (i = 0; i < src->length; i++) { - *dst++ = Convert_CP437ToUnicode(src->buffer[i]); - } - *dst = '\0'; - return src->length * 2; -} - -void Platform_Utf16ToAnsi(void* data) { - WCHAR* src = (WCHAR*)data; - char* dst = (char*)data; - - while (*src) { *dst++ = (char)(*src++); } - *dst = '\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[2] = { - DynamicLib_Sym(AttachConsole), DynamicLib_Sym(IsDebuggerPresent) - }; - static const cc_string kernel32 = String_FromConst("KERNEL32.DLL"); - - void* lib = DynamicLib_Load2(&kernel32); - if (!lib) { Logger_DynamicLibWarn("loading", &kernel32); return; } - DynamicLib_GetAll(lib, funcs, Array_Elems(funcs)); -} - -void Platform_Init(void) { - WSADATA wsaData; - cc_result res; - - Platform_InitStopwatch(); - heap = GetProcessHeap(); - - 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 (_AttachConsole) _AttachConsole(-1); /* ATTACH_PARENT_PROCESS */ - - 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); -} -#elif defined CC_BUILD_POSIX int Platform_EncodeUtf8(void* data, const cc_string* src) { cc_uint8* dst = (cc_uint8*)data; cc_uint8* cur; @@ -1732,85 +958,14 @@ void Platform_Init(void) { Platform_InitStopwatch(); Platform_InitSpecific(); } -#elif defined CC_BUILD_WEB -EMSCRIPTEN_KEEPALIVE void Platform_LogError(const char* msg) { - /* no pointer showing more than 128 characters in chat */ - cc_string str = String_FromRaw(msg, 128); - Logger_WarnFunc(&str); -} - -extern void interop_InitModule(void); -extern void interop_GetIndexedDBError(char* buffer); -void Platform_Init(void) { - char tmp[64+1] = { 0 }; - interop_InitModule(); - - /* Check if an error occurred when pre-loading IndexedDB */ - interop_GetIndexedDBError(tmp); - if (!tmp[0]) return; - - Chat_Add1("&cError preloading IndexedDB: %c", tmp); - Chat_AddRaw("&cPreviously saved settings/maps will be lost"); - /* NOTE: You must pre-load IndexedDB before main() */ - /* (because pre-loading only works asynchronously) */ - /* If you don't, you'll get errors later trying to sync local to remote */ - /* See doc/hosting-webclient.md for example preloading IndexedDB code */ -} #else void Platform_Init(void) { Platform_InitPosix(); } #endif -#endif /* CC_BUILD_POSIX */ /*########################################################################################################################* *-------------------------------------------------------Encryption--------------------------------------------------------* *#########################################################################################################################*/ -#if defined CC_BUILD_WIN -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); - -static void LoadCryptFuncs(void) { - static const struct DynamicLibSym funcs[2] = { - DynamicLib_Sym(CryptProtectData), DynamicLib_Sym(CryptUnprotectData) - }; - static const cc_string crypt32 = String_FromConst("CRYPT32.DLL"); - - void* lib = DynamicLib_Load2(&crypt32); - if (!lib) { Logger_DynamicLibWarn("loading", &crypt32); return; } - DynamicLib_GetAll(lib, funcs, Array_Elems(funcs)); -} - -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; -} -#elif defined CC_BUILD_POSIX /* Encrypts data using XTEA block cipher, with OS specific method to get machine-specific key */ static void EncipherBlock(cc_uint32* v, const cc_uint32* key, cc_string* dst) { @@ -1999,81 +1154,12 @@ cc_result Platform_Decrypt(const void* data, int len, cc_string* dst) { } return 0; } -#endif /*########################################################################################################################* *-----------------------------------------------------Configuration-------------------------------------------------------* *#########################################################################################################################*/ -#if defined CC_BUILD_WIN -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 */ - - for (i = 0; i < GAME_MAX_CMDARGS; i++) { - args[i] = Platform_NextArg(&cmdArgs); - - if (!args[i].length) break; - } - return i; -} - -cc_result Platform_SetDefaultCurrentDirectory(int argc, char **argv) { - WCHAR path[NATIVE_STR_LEN + 1]; - int i, len; - cc_result res = Process_RawGetExePath(path, &len); - if (res) return res; - - /* Get rid of filename at end of directory */ - for (i = len - 1; i >= 0; i--, len--) { - if (path[i] == '/' || path[i] == '\\') break; - } - - path[len] = '\0'; - return SetCurrentDirectoryW(path) ? 0 : GetLastError(); -} -#elif defined CC_BUILD_WEB -int Platform_GetCommandLineArgs(int argc, STRING_REF char** argv, cc_string* args) { - int i, count; - argc--; argv++; /* skip executable path argument */ - - count = min(argc, GAME_MAX_CMDARGS); - for (i = 0; i < count; i++) { args[i] = String_FromReadonly(argv[i]); } - return count; -} - -cc_result Platform_SetDefaultCurrentDirectory(int argc, char **argv) { - return chdir("/classicube") == -1 ? errno : 0; -} -#elif defined CC_BUILD_ANDROID +#if defined CC_BUILD_ANDROID int Platform_GetCommandLineArgs(int argc, STRING_REF char** argv, cc_string* args) { if (!gameArgs.length) return 0; return String_UNSAFE_Split(&gameArgs, ' ', args, GAME_MAX_CMDARGS); @@ -2088,7 +1174,7 @@ cc_result Platform_SetDefaultCurrentDirectory(int argc, char **argv) { Platform_Log1("EXTERNAL DIR: %s|", &dir); return chdir(dir.buffer) == -1 ? errno : 0; } -#elif defined CC_BUILD_POSIX +#else int Platform_GetCommandLineArgs(int argc, STRING_REF char** argv, cc_string* args) { int i, count; argc--; argv++; /* skip executable path argument */ @@ -2265,3 +1351,4 @@ void JavaCall_String_String(const char* name, const cc_string* arg, cc_string* d (*env)->DeleteLocalRef(env, args[0].l); } #endif +#endif diff --git a/src/Platform_Web.c b/src/Platform_Web.c new file mode 100644 index 000000000..fe4e350bc --- /dev/null +++ b/src/Platform_Web.c @@ -0,0 +1,409 @@ +#include "Core.h" +#if defined CC_BUILD_WEB + +#include "_PlatformBase.h" +#include "Stream.h" +#include "ExtMath.h" +#include "Drawer2D.h" +#include "Funcs.h" +#include "Window.h" +#include "Utils.h" +#include "Errors.h" + +/* POSIX can be shared between Linux/BSD/macOS */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const cc_result ReturnCode_FileShareViolation = 1000000000; /* TODO: not used apparently */ +const cc_result ReturnCode_FileNotFound = ENOENT; +const cc_result ReturnCode_SocketInProgess = EINPROGRESS; +const cc_result ReturnCode_SocketWouldBlock = EWOULDBLOCK; +const cc_result ReturnCode_DirectoryExists = EEXIST; +#include +#include "Chat.h" + + +/*########################################################################################################################* +*---------------------------------------------------------Memory----------------------------------------------------------* +*#########################################################################################################################*/ +void Mem_Set(void* dst, cc_uint8 value, cc_uint32 numBytes) { memset(dst, value, numBytes); } +void Mem_Copy(void* dst, const void* src, cc_uint32 numBytes) { memcpy(dst, src, numBytes); } + +void* Mem_TryAlloc(cc_uint32 numElems, cc_uint32 elemsSize) { + cc_uint32 size = CalcMemSize(numElems, elemsSize); + return size ? malloc(size) : NULL; +} + +void* Mem_TryAllocCleared(cc_uint32 numElems, cc_uint32 elemsSize) { + return calloc(numElems, elemsSize); +} + +void* Mem_TryRealloc(void* mem, cc_uint32 numElems, cc_uint32 elemsSize) { + cc_uint32 size = CalcMemSize(numElems, elemsSize); + return size ? realloc(mem, size) : NULL; +} + +void Mem_Free(void* mem) { + if (mem) free(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; +} + +void Platform_Log(const char* msg, int len) { + write(STDOUT_FILENO, msg, len); + write(STDOUT_FILENO, "\n", 1); +} + +#define UnixTime_TotalMS(time) ((cc_uint64)time.tv_sec * 1000 + UNIX_EPOCH + (time.tv_usec / 1000)) +TimeMS DateTime_CurrentUTC_MS(void) { + struct timeval cur; + gettimeofday(&cur, NULL); + return UnixTime_TotalMS(cur); +} + +void DateTime_CurrentLocal(struct DateTime* t) { + struct timeval cur; + struct tm loc_time; + gettimeofday(&cur, NULL); + localtime_r(&cur.tv_sec, &loc_time); + + t->year = loc_time.tm_year + 1900; + t->month = loc_time.tm_mon + 1; + t->day = loc_time.tm_mday; + t->hour = loc_time.tm_hour; + t->minute = loc_time.tm_min; + t->second = loc_time.tm_sec; +} + +cc_uint64 Stopwatch_Measure(void) { + /* time is a milliseconds double */ + return (cc_uint64)(emscripten_get_now() * 1000); +} + + +/*########################################################################################################################* +*-----------------------------------------------------Directory/File------------------------------------------------------* +*#########################################################################################################################*/ +cc_result Directory_Create(const cc_string* path) { + char str[NATIVE_STR_LEN]; + Platform_EncodeUtf8(str, path); + /* read/write/search permissions for owner and group, and with read/search permissions for others. */ + /* TODO: Is the default mode in all cases */ + return mkdir(str, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1 ? errno : 0; +} + +int File_Exists(const cc_string* path) { + char str[NATIVE_STR_LEN]; + struct stat sb; + Platform_EncodeUtf8(str, path); + return stat(str, &sb) == 0 && S_ISREG(sb.st_mode); +} + +cc_result Directory_Enum(const cc_string* dirPath, void* obj, Directory_EnumCallback callback) { + cc_string path; char pathBuffer[FILENAME_SIZE]; + char str[NATIVE_STR_LEN]; + DIR* dirPtr; + struct dirent* entry; + char* src; + int len, res; + + Platform_EncodeUtf8(str, dirPath); + dirPtr = opendir(str); + if (!dirPtr) return errno; + + /* POSIX docs: "When the end of the directory is encountered, a null pointer is returned and errno is not changed." */ + /* errno is sometimes leftover from previous calls, so always reset it before readdir gets called */ + errno = 0; + String_InitArray(path, pathBuffer); + + while ((entry = readdir(dirPtr))) { + path.length = 0; + String_Format1(&path, "%s/", dirPath); + + /* ignore . and .. entry */ + src = entry->d_name; + if (src[0] == '.' && src[1] == '\0') continue; + if (src[0] == '.' && src[1] == '.' && src[2] == '\0') continue; + + len = String_Length(src); + String_AppendUtf8(&path, src, len); + + /* TODO: fallback to stat when this fails */ + if (entry->d_type == DT_DIR) { + res = Directory_Enum(&path, obj, callback); + if (res) { closedir(dirPtr); return res; } + } else { + callback(&path, obj); + } + errno = 0; + } + + res = errno; /* return code from readdir */ + closedir(dirPtr); + return res; +} + +static cc_result File_Do(cc_file* file, const cc_string* path, int mode) { + char str[NATIVE_STR_LEN]; + Platform_EncodeUtf8(str, path); + *file = open(str, mode, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + return *file == -1 ? errno : 0; +} + +cc_result File_Open(cc_file* file, const cc_string* path) { + return File_Do(file, path, O_RDONLY); +} +cc_result File_Create(cc_file* file, const cc_string* path) { + return File_Do(file, path, O_RDWR | O_CREAT | O_TRUNC); +} +cc_result File_OpenOrCreate(cc_file* file, const cc_string* path) { + return File_Do(file, path, O_RDWR | O_CREAT); +} + +cc_result File_Read(cc_file file, void* data, cc_uint32 count, cc_uint32* bytesRead) { + *bytesRead = read(file, data, count); + return *bytesRead == -1 ? errno : 0; +} + +cc_result File_Write(cc_file file, const void* data, cc_uint32 count, cc_uint32* bytesWrote) { + *bytesWrote = write(file, data, count); + return *bytesWrote == -1 ? errno : 0; +} + +extern void interop_SyncFS(void); +cc_result File_Close(cc_file file) { + int res = close(file) == -1 ? errno : 0; + interop_SyncFS(); + return res; +} + +cc_result File_Seek(cc_file file, int offset, int seekType) { + static cc_uint8 modes[3] = { SEEK_SET, SEEK_CUR, SEEK_END }; + return lseek(file, offset, modes[seekType]) == -1 ? errno : 0; +} + +cc_result File_Position(cc_file file, cc_uint32* pos) { + *pos = lseek(file, 0, SEEK_CUR); + return *pos == -1 ? errno : 0; +} + +cc_result File_Length(cc_file file, cc_uint32* len) { + struct stat st; + if (fstat(file, &st) == -1) { *len = -1; return errno; } + *len = st.st_size; return 0; +} + + +/*########################################################################################################################* +*--------------------------------------------------------Threading--------------------------------------------------------* +*#########################################################################################################################*/ +/* No real threading support with emscripten backend */ +void Thread_Sleep(cc_uint32 milliseconds) { } +void* Thread_Start(Thread_StartFunc func) { func(); return NULL; } +void Thread_Detach(void* handle) { } +void Thread_Join(void* handle) { } + +void* Mutex_Create(void) { return NULL; } +void Mutex_Free(void* handle) { } +void Mutex_Lock(void* handle) { } +void Mutex_Unlock(void* handle) { } + +void* Waitable_Create(void) { return NULL; } +void Waitable_Free(void* handle) { } +void Waitable_Signal(void* handle) { } +void Waitable_Wait(void* handle) { } +void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) { } + + +/*########################################################################################################################* +*--------------------------------------------------------Font/Text--------------------------------------------------------* +*#########################################################################################################################*/ +void Platform_LoadSysFonts(void) { } + + +/*########################################################################################################################* +*---------------------------------------------------------Socket----------------------------------------------------------* +*#########################################################################################################################*/ +cc_result Socket_Create(cc_socket* s) { + *s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + return *s == -1 ? errno : 0; +} + +cc_result Socket_Available(cc_socket s, int* available) { + return ioctl(s, FIONREAD, available); +} +cc_result Socket_SetBlocking(cc_socket s, cc_bool blocking) { + return ERR_NOT_SUPPORTED; /* sockets always async */ +} + +cc_result Socket_GetError(cc_socket s, cc_result* result) { + socklen_t resultSize = sizeof(cc_result); + return getsockopt(s, SOL_SOCKET, SO_ERROR, result, &resultSize); +} + +cc_result Socket_Connect(cc_socket s, const cc_string* ip, int port) { + struct sockaddr addr; + cc_result res; + addr.sa_family = AF_INET; + + Stream_SetU16_BE( (cc_uint8*)&addr.sa_data[0], port); + if (!Utils_ParseIP(ip, (cc_uint8*)&addr.sa_data[2])) + return ERR_INVALID_ARGUMENT; + + res = connect(s, &addr, sizeof(addr)); + return res == -1 ? errno : 0; +} + +cc_result Socket_Read(cc_socket s, cc_uint8* data, cc_uint32 count, cc_uint32* modified) { + /* recv only reads one WebSocket frame at most, hence call it multiple times */ + int recvCount = 0, pending; + *modified = 0; + + while (count && !Socket_Available(s, &pending) && pending) { + recvCount = recv(s, data, count, 0); + if (recvCount == -1) return errno; + + *modified += recvCount; + data += recvCount; count -= recvCount; + } + return 0; +} + +cc_result Socket_Write(cc_socket s, const cc_uint8* data, cc_uint32 count, cc_uint32* modified) { + int sentCount = send(s, data, count, 0); + if (sentCount != -1) { *modified = sentCount; return 0; } + *modified = 0; return errno; +} + +cc_result Socket_Close(cc_socket s) { + cc_result res = close(s); + if (res == -1) res = errno; + return res; +} + +#include +cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) { + struct pollfd pfd; + int flags; + + pfd.fd = s; + pfd.events = mode == SOCKET_POLL_READ ? POLLIN : POLLOUT; + if (poll(&pfd, 1, 0) == -1) { *success = false; return errno; } + + /* to match select, closed socket still counts as readable */ + flags = mode == SOCKET_POLL_READ ? (POLLIN | POLLHUP) : POLLOUT; + *success = (pfd.revents & flags) != 0; + return 0; +} + + +/*########################################################################################################################* +*-----------------------------------------------------Process/Module------------------------------------------------------* +*#########################################################################################################################*/ +cc_result Process_StartGame(const cc_string* args) { return ERR_NOT_SUPPORTED; } +void Process_Exit(cc_result code) { exit(code); } + +extern int interop_OpenTab(const char* url); +cc_result Process_StartOpen(const cc_string* args) { + char str[NATIVE_STR_LEN]; + Platform_EncodeUtf8(str, args); + return interop_OpenTab(str); +} + + +/*########################################################################################################################* +*--------------------------------------------------------Updater----------------------------------------------------------* +*#########################################################################################################################*/ +const char* const Updater_D3D9 = NULL; +const char* const Updater_OGL = NULL; + +cc_result Updater_GetBuildTime(cc_uint64* t) { return ERR_NOT_SUPPORTED; } +cc_bool Updater_Clean(void) { return true; } +cc_result Updater_Start(const char** action) { return ERR_NOT_SUPPORTED; } +cc_result Updater_MarkExecutable(void) { return 0; } +cc_result Updater_SetNewBuildTime(cc_uint64 t) { return ERR_NOT_SUPPORTED; } + + +/*########################################################################################################################* +*-------------------------------------------------------Dynamic lib-------------------------------------------------------* +*#########################################################################################################################*/ +void* DynamicLib_Load2(const cc_string* path) { return NULL; } +void* DynamicLib_Get2(void* lib, const char* name) { return NULL; } +cc_bool DynamicLib_DescribeError(cc_string* dst) { return false; } + + +/*########################################################################################################################* +*--------------------------------------------------------Platform---------------------------------------------------------* +*#########################################################################################################################*/ +EMSCRIPTEN_KEEPALIVE void Platform_LogError(const char* msg) { + /* no pointer showing more than 128 characters in chat */ + cc_string str = String_FromRaw(msg, 128); + Logger_WarnFunc(&str); +} + +extern void interop_InitModule(void); +extern void interop_GetIndexedDBError(char* buffer); +void Platform_Init(void) { + char tmp[64+1] = { 0 }; + interop_InitModule(); + + /* Check if an error occurred when pre-loading IndexedDB */ + interop_GetIndexedDBError(tmp); + if (!tmp[0]) return; + + Chat_Add1("&cError preloading IndexedDB: %c", tmp); + Chat_AddRaw("&cPreviously saved settings/maps will be lost"); + /* NOTE: You must pre-load IndexedDB before main() */ + /* (because pre-loading only works asynchronously) */ + /* If you don't, you'll get errors later trying to sync local to remote */ + /* See doc/hosting-webclient.md for example preloading IndexedDB code */ +} + + +/*########################################################################################################################* +*-------------------------------------------------------Encryption--------------------------------------------------------* +*#########################################################################################################################*/ +cc_result Platform_Encrypt(const void* data, int len, cc_string* dst) { return ERR_NOT_SUPPORTED; } +cc_result Platform_Decrypt(const void* data, int len, cc_string* dst) { return ERR_NOT_SUPPORTED; } + + +/*########################################################################################################################* +*-----------------------------------------------------Configuration-------------------------------------------------------* +*#########################################################################################################################*/ +int Platform_GetCommandLineArgs(int argc, STRING_REF char** argv, cc_string* args) { + int i, count; + argc--; argv++; /* skip executable path argument */ + + count = min(argc, GAME_MAX_CMDARGS); + for (i = 0; i < count; i++) { args[i] = String_FromReadonly(argv[i]); } + return count; +} + +cc_result Platform_SetDefaultCurrentDirectory(int argc, char **argv) { + return chdir("/classicube") == -1 ? errno : 0; +} +#endif diff --git a/src/Platform_WinApi.c b/src/Platform_WinApi.c new file mode 100644 index 000000000..a9684b0df --- /dev/null +++ b/src/Platform_WinApi.c @@ -0,0 +1,809 @@ +#include "Core.h" +#if defined CC_BUILD_WIN + +#include "_PlatformBase.h" +#include "Stream.h" +#include "Drawer2D.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 +#include +#include +#include +#include + +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; + +/*########################################################################################################################* +*---------------------------------------------------------Memory----------------------------------------------------------* +*#########################################################################################################################*/ +void Mem_Set(void* dst, cc_uint8 value, cc_uint32 numBytes) { memset(dst, value, numBytes); } +void Mem_Copy(void* dst, const void* src, cc_uint32 numBytes) { memcpy(dst, src, numBytes); } + +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 50491123200000ULL +#define FILETIME_UNIX_EPOCH 11644473600LL +#define FileTime_TotalMS(time) ((time / 10000) + FILETIME_EPOCH) +#define FileTime_UnixTime(time) ((time / 10000000) - FILETIME_UNIX_EPOCH) +TimeMS DateTime_CurrentUTC_MS(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_TotalMS(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------------------------------------------------------* +*#########################################################################################################################*/ +cc_result Directory_Create(const cc_string* path) { + WCHAR str[NATIVE_STR_LEN]; + cc_result res; + + Platform_EncodeUtf16(str, path); + if (CreateDirectoryW(str, NULL)) return 0; + if ((res = GetLastError()) != ERROR_CALL_NOT_IMPLEMENTED) return res; + + Platform_Utf16ToAnsi(str); + return CreateDirectoryA((LPCSTR)str, NULL) ? 0 : GetLastError(); +} + +int File_Exists(const cc_string* path) { + WCHAR str[NATIVE_STR_LEN]; + DWORD attribs; + + Platform_EncodeUtf16(str, path); + attribs = GetFileAttributesW(str); + 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]; + WCHAR str[NATIVE_STR_LEN]; + WIN32_FIND_DATAW eW; + WIN32_FIND_DATAA eA; + int i, ansi = false; + HANDLE find; + cc_result res; + + /* Need to append \* to search for files in directory */ + String_InitArray(path, pathBuffer); + String_Format1(&path, "%s\\*", dirPath); + Platform_EncodeUtf16(str, &path); + + find = FindFirstFileW(str, &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 */ + Platform_Utf16ToAnsi(str); + find = FindFirstFileA((LPCSTR)str, &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 DoFile(cc_file* file, const cc_string* path, DWORD access, DWORD createMode) { + WCHAR str[NATIVE_STR_LEN]; + cc_result res; + Platform_EncodeUtf16(str, path); + + *file = CreateFileW(str, 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 */ + Platform_Utf16ToAnsi(str); + *file = CreateFileA((LPCSTR)str, access, FILE_SHARE_READ, NULL, createMode, 0, NULL); + return *file != INVALID_HANDLE_VALUE ? 0 : GetLastError(); +} + +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[3] = { 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--------------------------------------------------------* +*#########################################################################################################################*/ +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_Start(Thread_StartFunc func) { + DWORD threadID; + void* handle = CreateThread(NULL, 0, ExecThread, (void*)func, 0, &threadID); + if (!handle) { + Logger_Abort2(GetLastError(), "Creating thread"); + } + return handle; +} + +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(void) { + 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(void) { + 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); +} + +void Platform_LoadSysFonts(void) { + int i; + char winFolder[FILENAME_SIZE]; + WCHAR winTmp[FILENAME_SIZE]; + UINT winLen; + /* System folder path may not be C:/Windows */ + cc_string dirs[1]; + String_InitArray(dirs[0], winFolder); + + 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----------------------------------------------------------* +*#########################################################################################################################*/ +cc_result Socket_Create(cc_socket* s) { + *s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + return *s == -1 ? WSAGetLastError() : 0; +} + +cc_result Socket_Available(cc_socket s, int* available) { + return ioctlsocket(s, FIONREAD, available); +} +cc_result Socket_SetBlocking(cc_socket s, cc_bool blocking) { + int blocking_raw = blocking ? 0 : -1; + return ioctlsocket(s, FIONBIO, &blocking_raw); +} + +cc_result Socket_GetError(cc_socket s, cc_result* result) { + socklen_t resultSize = sizeof(cc_result); + return getsockopt(s, SOL_SOCKET, SO_ERROR, result, &resultSize); +} + +cc_result Socket_Connect(cc_socket s, const cc_string* ip, int port) { + struct sockaddr addr; + cc_result res; + addr.sa_family = AF_INET; + + Stream_SetU16_BE( (cc_uint8*)&addr.sa_data[0], port); + if (!Utils_ParseIP(ip, (cc_uint8*)&addr.sa_data[2])) + return ERR_INVALID_ARGUMENT; + + res = connect(s, &addr, sizeof(addr)); + 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, 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, data, count, 0); + if (sentCount != -1) { *modified = sentCount; return 0; } + *modified = 0; return WSAGetLastError(); +} + +cc_result Socket_Close(cc_socket s) { + cc_result res = 0; + cc_result res1, res2; + + res1 = shutdown(s, SD_BOTH); + if (res1 == -1) res = WSAGetLastError(); + + res2 = closesocket(s); + if (res2 == -1) res = WSAGetLastError(); + return res; +} + +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; +} + + +/*########################################################################################################################* +*-----------------------------------------------------Process/Module------------------------------------------------------* +*#########################################################################################################################*/ +static cc_result Process_RawGetExePath(WCHAR* path, int* len) { + *len = GetModuleFileNameW(NULL, path, NATIVE_STR_LEN); + return *len ? 0 : GetLastError(); +} + +cc_result Process_StartGame(const cc_string* args) { + WCHAR path[NATIVE_STR_LEN + 1], raw[NATIVE_STR_LEN]; + cc_string argv; char argvBuffer[NATIVE_STR_LEN]; + STARTUPINFOW si = { 0 }; + PROCESS_INFORMATION pi = { 0 }; + cc_result res; + int len; + + Process_RawGetExePath(path, &len); + path[len] = '\0'; + si.cb = sizeof(STARTUPINFOW); + + String_InitArray(argv, argvBuffer); + /* Game doesn't actually care about argv[0] */ + String_Format1(&argv, "cc %s", args); + String_UNSAFE_TrimEnd(&argv); + Platform_EncodeUtf16(raw, &argv); + + if (CreateProcessW(path, raw, NULL, NULL, + false, 0, NULL, NULL, &si, &pi)) goto success; + if ((res = GetLastError()) != ERROR_CALL_NOT_IMPLEMENTED) return res; + + /* Windows 9x does not support W API functions */ + len = GetModuleFileNameA(NULL, (LPSTR)path, NATIVE_STR_LEN); + ((char*)path)[len] = '\0'; + Platform_Utf16ToAnsi(raw); + + if (CreateProcessA((LPCSTR)path, (LPSTR)raw, NULL, NULL, + false, 0, NULL, NULL, &si, &pi)) goto success; + return GetLastError(); + +success: + /* 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) { + WCHAR str[NATIVE_STR_LEN]; + cc_uintptr res; + Platform_EncodeUtf16(str, args); + + res = (cc_uintptr)ShellExecuteW(NULL, NULL, str, 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) + +#if _WIN64 +const char* const Updater_D3D9 = "ClassiCube.64.exe"; +const char* const Updater_OGL = "ClassiCube.64-opengl.exe"; +#else +const char* const Updater_D3D9 = "ClassiCube.exe"; +const char* const Updater_OGL = "ClassiCube.opengl.exe"; +#endif + +cc_bool Updater_Clean(void) { + return DeleteFile(UPDATE_TMP) || GetLastError() == ERROR_FILE_NOT_FOUND; +} + +cc_result Updater_Start(const char** action) { + WCHAR path[NATIVE_STR_LEN + 1]; + cc_result res; + int len = 0; + + *action = "Getting executable path"; + if ((res = Process_RawGetExePath(path, &len))) return res; + path[len] = '\0'; + + *action = "Moving executable to CC_prev.exe"; + if (!MoveFileExW(path, UPDATE_TMP, MOVEFILE_REPLACE_EXISTING)) return GetLastError(); + *action = "Replacing executable"; + if (!MoveFileExW(UPDATE_SRC, path, MOVEFILE_REPLACE_EXISTING)) return GetLastError(); + + *action = "Restarting game"; + return Process_StartGame(&String_Empty); +} + +cc_result Updater_GetBuildTime(cc_uint64* timestamp) { + WCHAR path[NATIVE_STR_LEN + 1]; + cc_file file; + FILETIME ft; + cc_uint64 raw; + int len = 0; + + cc_result res = Process_RawGetExePath(path, &len); + if (res) return res; + path[len] = '\0'; + + file = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (file == INVALID_HANDLE_VALUE) return GetLastError(); + + 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"); + +void* DynamicLib_Load2(const cc_string* path) { + WCHAR str[NATIVE_STR_LEN]; + Platform_EncodeUtf16(str, path); + return LoadLibraryW(str); +} + +void* DynamicLib_Get2(void* lib, const char* name) { + return GetProcAddress((HMODULE)lib, name); +} + +cc_bool DynamicLib_DescribeError(cc_string* dst) { + cc_result res = GetLastError(); + Platform_DescribeError(res, dst); + String_Format1(dst, " (error %i)", &res); + return true; +} + + +/*########################################################################################################################* +*--------------------------------------------------------Platform---------------------------------------------------------* +*#########################################################################################################################*/ +int Platform_EncodeUtf16(void* data, const cc_string* src) { + WCHAR* dst = (WCHAR*)data; + int i; + if (src->length > FILENAME_SIZE) Logger_Abort("String too long to expand"); + + for (i = 0; i < src->length; i++) { + *dst++ = Convert_CP437ToUnicode(src->buffer[i]); + } + *dst = '\0'; + return src->length * 2; +} + +void Platform_Utf16ToAnsi(void* data) { + WCHAR* src = (WCHAR*)data; + char* dst = (char*)data; + + while (*src) { *dst++ = (char)(*src++); } + *dst = '\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[2] = { + DynamicLib_Sym(AttachConsole), DynamicLib_Sym(IsDebuggerPresent) + }; + static const cc_string kernel32 = String_FromConst("KERNEL32.DLL"); + + void* lib = DynamicLib_Load2(&kernel32); + if (!lib) { Logger_DynamicLibWarn("loading", &kernel32); return; } + DynamicLib_GetAll(lib, funcs, Array_Elems(funcs)); +} + +void Platform_Init(void) { + WSADATA wsaData; + cc_result res; + + Platform_InitStopwatch(); + heap = GetProcessHeap(); + + 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 (_AttachConsole) _AttachConsole(-1); /* ATTACH_PARENT_PROCESS */ + + 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 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); + +static void LoadCryptFuncs(void) { + static const struct DynamicLibSym funcs[2] = { + DynamicLib_Sym(CryptProtectData), DynamicLib_Sym(CryptUnprotectData) + }; + static const cc_string crypt32 = String_FromConst("CRYPT32.DLL"); + + void* lib = DynamicLib_Load2(&crypt32); + if (!lib) { Logger_DynamicLibWarn("loading", &crypt32); return; } + DynamicLib_GetAll(lib, funcs, Array_Elems(funcs)); +} + +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 */ + + for (i = 0; i < GAME_MAX_CMDARGS; i++) { + args[i] = Platform_NextArg(&cmdArgs); + + if (!args[i].length) break; + } + return i; +} + +cc_result Platform_SetDefaultCurrentDirectory(int argc, char **argv) { + WCHAR path[NATIVE_STR_LEN + 1]; + int i, len; + cc_result res = Process_RawGetExePath(path, &len); + if (res) return res; + + /* Get rid of filename at end of directory */ + for (i = len - 1; i >= 0; i--, len--) { + if (path[i] == '/' || path[i] == '\\') break; + } + + path[len] = '\0'; + return SetCurrentDirectoryW(path) ? 0 : GetLastError(); +} +#endif diff --git a/src/_PlatformBase.h b/src/_PlatformBase.h new file mode 100644 index 000000000..19eba1fb8 --- /dev/null +++ b/src/_PlatformBase.h @@ -0,0 +1,110 @@ +#include "Platform.h" +#include "String.h" +#include "Logger.h" +#include "Constants.h" + + +/*########################################################################################################################* +*---------------------------------------------------------Memory----------------------------------------------------------* +*#########################################################################################################################*/ +int Mem_Equal(const void* a, const void* b, cc_uint32 numBytes) { + const cc_uint8* src = (const cc_uint8*)a; + const cc_uint8* dst = (const cc_uint8*)b; + + while (numBytes--) { + if (*src++ != *dst++) return false; + } + return true; +} + +CC_NOINLINE static void AbortOnAllocFailed(const char* place) { + cc_string log; char logBuffer[STRING_SIZE+20 + 1]; + String_InitArray_NT(log, logBuffer); + + String_Format1(&log, "Out of memory! (when allocating %c)", place); + log.buffer[log.length] = '\0'; + Logger_Abort(log.buffer); +} + +void* Mem_Alloc(cc_uint32 numElems, cc_uint32 elemsSize, const char* place) { + void* ptr = Mem_TryAlloc(numElems, elemsSize); + if (!ptr) AbortOnAllocFailed(place); + return ptr; +} + +void* Mem_AllocCleared(cc_uint32 numElems, cc_uint32 elemsSize, const char* place) { + void* ptr = Mem_TryAllocCleared(numElems, elemsSize); + if (!ptr) AbortOnAllocFailed(place); + return ptr; +} + +void* Mem_Realloc(void* mem, cc_uint32 numElems, cc_uint32 elemsSize, const char* place) { + void* ptr = Mem_TryRealloc(mem, numElems, elemsSize); + if (!ptr) AbortOnAllocFailed(place); + return ptr; +} + +static CC_NOINLINE cc_uint32 CalcMemSize(cc_uint32 numElems, cc_uint32 elemsSize) { + if (!numElems) return 1; /* treat 0 size as 1 byte */ + cc_uint32 numBytes = numElems * elemsSize; /* TODO: avoid overflow here */ + if (numBytes < numElems) return 0; /* TODO: Use proper overflow checking */ + return numBytes; +} + + +/*########################################################################################################################* +*------------------------------------------------------Logging/Time-------------------------------------------------------* +*#########################################################################################################################*/ +void Platform_Log1(const char* format, const void* a1) { + Platform_Log4(format, a1, NULL, NULL, NULL); +} +void Platform_Log2(const char* format, const void* a1, const void* a2) { + Platform_Log4(format, a1, a2, NULL, NULL); +} +void Platform_Log3(const char* format, const void* a1, const void* a2, const void* a3) { + Platform_Log4(format, a1, a2, a3, NULL); +} + +void Platform_Log4(const char* format, const void* a1, const void* a2, const void* a3, const void* a4) { + cc_string msg; char msgBuffer[512]; + String_InitArray(msg, msgBuffer); + + String_Format4(&msg, format, a1, a2, a3, a4); + Platform_Log(msg.buffer, msg.length); +} + +void Platform_LogConst(const char* message) { + Platform_Log(message, String_Length(message)); +} + +int Stopwatch_ElapsedMS(cc_uint64 beg, cc_uint64 end) { + cc_uint64 raw = Stopwatch_ElapsedMicroseconds(beg, end); + if (raw > Int32_MaxValue) return Int32_MaxValue / 1000; + return (int)raw / 1000; +} + + +/*########################################################################################################################* +*-------------------------------------------------------Dynamic lib-------------------------------------------------------* +*#########################################################################################################################*/ +cc_result DynamicLib_Load(const cc_string* path, void** lib) { + *lib = DynamicLib_Load2(path); + return *lib == NULL; +} +cc_result DynamicLib_Get(void* lib, const char* name, void** symbol) { + *symbol = DynamicLib_Get2(lib, name); + return *symbol == NULL; +} + + +cc_bool DynamicLib_GetAll(void* lib, const struct DynamicLibSym* syms, int count) { + int i, loaded = 0; + void* addr; + + for (i = 0; i < count; i++) { + addr = DynamicLib_Get2(lib, syms[i].name); + if (addr) loaded++; + *syms[i].symAddr = addr; + } + return loaded == count; +} From bb68bfd0cfd8d55534e70955278f1299bb5e6b06 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sun, 23 May 2021 10:20:42 +1000 Subject: [PATCH 2/9] Fix web backend --- src/Core.h | 1 - src/Logger.c | 1 - src/Platform_Web.c | 34 +++++++++++++++++++++++++++++++++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/Core.h b/src/Core.h index c7f5ed0aa..cc1618a18 100644 --- a/src/Core.h +++ b/src/Core.h @@ -235,7 +235,6 @@ Thus it is **NOT SAFE** to allocate a string on the stack. */ #define CC_BUILD_SDL #elif defined __EMSCRIPTEN__ #define CC_BUILD_WEB -#define CC_BUILD_POSIX #define CC_BUILD_GL #define CC_BUILD_GLMODERN #define CC_BUILD_GLES diff --git a/src/Logger.c b/src/Logger.c index 40a29cc6b..c746659f4 100644 --- a/src/Logger.c +++ b/src/Logger.c @@ -9,7 +9,6 @@ #if defined CC_BUILD_WEB /* Can't see native CPU state with javascript */ -#undef CC_BUILD_POSIX #elif defined CC_BUILD_WIN #define WIN32_LEAN_AND_MEAN #define NOSERVICE diff --git a/src/Platform_Web.c b/src/Platform_Web.c index fe4e350bc..8d3a89f15 100644 --- a/src/Platform_Web.c +++ b/src/Platform_Web.c @@ -359,8 +359,39 @@ cc_bool DynamicLib_DescribeError(cc_string* dst) { return false; } /*########################################################################################################################* *--------------------------------------------------------Platform---------------------------------------------------------* *#########################################################################################################################*/ +int Platform_EncodeUtf8(void* data, const cc_string* src) { + cc_uint8* dst = (cc_uint8*)data; + cc_uint8* cur; + int i, len = 0; + if (src->length > FILENAME_SIZE) Logger_Abort("String too long to expand"); + + for (i = 0; i < src->length; i++) { + cur = dst + len; + len += Convert_CP437ToUtf8(src->buffer[i], cur); + } + dst[len] = '\0'; + return len; +} + +cc_bool Platform_DescribeError(cc_result res, cc_string* dst) { + char chars[NATIVE_STR_LEN]; + int len; + + /* For unrecognised error codes, strerror_r might return messages */ + /* such as 'No error information', which is not very useful */ + /* (could check errno here but quicker just to skip entirely) */ + if (res >= 1000) return false; + + len = strerror_r(res, chars, NATIVE_STR_LEN); + if (len == -1) return false; + + len = String_CalcLen(chars, NATIVE_STR_LEN); + String_AppendUtf8(dst, chars, len); + return true; +} + EMSCRIPTEN_KEEPALIVE void Platform_LogError(const char* msg) { - /* no pointer showing more than 128 characters in chat */ + /* no point showing more than 128 characters in chat */ cc_string str = String_FromRaw(msg, 128); Logger_WarnFunc(&str); } @@ -382,6 +413,7 @@ void Platform_Init(void) { /* If you don't, you'll get errors later trying to sync local to remote */ /* See doc/hosting-webclient.md for example preloading IndexedDB code */ } +void Platform_Free(void) { } /*########################################################################################################################* From e43c4ea690f112d05c5f7a48cb236eecbfc0b947 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sun, 23 May 2021 13:47:51 +1000 Subject: [PATCH 3/9] Web: Rewrite sockets API to avoid standard library --- license.txt | 60 ++++++++++ src/ClassiCube.vcxproj | 1 + src/ClassiCube.vcxproj.filters | 3 + src/Platform_Web.c | 114 ++++++++++-------- src/interop_web.c | 205 +++++++++++++++++++++++++++++++++ 5 files changed, 335 insertions(+), 48 deletions(-) diff --git a/license.txt b/license.txt index 529bb354b..b27d9e3b8 100644 --- a/license.txt +++ b/license.txt @@ -90,6 +90,66 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Emscripten license +============================================================================== + +Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +============================================================================== + +Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file. +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal with the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimers. + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimers + in the documentation and/or other materials provided with the + distribution. + + Neither the names of Mozilla, + nor the names of its contributors may be used to endorse + or promote products derived from this Software without specific prior + written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE. + +============================================================================== + + FreeType license ================== The FreeType Project LICENSE diff --git a/src/ClassiCube.vcxproj b/src/ClassiCube.vcxproj index cf6d0a7ee..29ec97bd4 100644 --- a/src/ClassiCube.vcxproj +++ b/src/ClassiCube.vcxproj @@ -272,6 +272,7 @@ + diff --git a/src/ClassiCube.vcxproj.filters b/src/ClassiCube.vcxproj.filters index d1b1ea021..1841b3969 100644 --- a/src/ClassiCube.vcxproj.filters +++ b/src/ClassiCube.vcxproj.filters @@ -545,5 +545,8 @@ Source Files\Platform + + Source Files\Platform + \ No newline at end of file diff --git a/src/Platform_Web.c b/src/Platform_Web.c index 8d3a89f15..30b65906b 100644 --- a/src/Platform_Web.c +++ b/src/Platform_Web.c @@ -10,7 +10,6 @@ #include "Utils.h" #include "Errors.h" -/* POSIX can be shared between Linux/BSD/macOS */ #include #include #include @@ -18,15 +17,9 @@ #include #include #include -#include -#include -#include -#include #include #include #include -#include -#include #include const cc_result ReturnCode_FileShareViolation = 1000000000; /* TODO: not used apparently */ @@ -73,9 +66,9 @@ cc_uint64 Stopwatch_ElapsedMicroseconds(cc_uint64 beg, cc_uint64 end) { return ((end - beg) * sw_freqMul) / sw_freqDiv; } +extern void interop_Log(const char* msg, int len); void Platform_Log(const char* msg, int len) { - write(STDOUT_FILENO, msg, len); - write(STDOUT_FILENO, "\n", 1); + interop_Log(msg, len); } #define UnixTime_TotalMS(time) ((cc_uint64)time.tv_sec * 1000 + UNIX_EPOCH + (time.tv_usec / 1000)) @@ -248,76 +241,98 @@ void Platform_LoadSysFonts(void) { } /*########################################################################################################################* *---------------------------------------------------------Socket----------------------------------------------------------* *#########################################################################################################################*/ +extern int interop_SocketCreate(void); +extern int interop_SocketConnect(int sock, const char* addr, int port); +extern int interop_SocketClose(int sock); +extern int interop_SocketSend(int sock, const void* data, int len); +extern int interop_SocketRecv(int sock, void* data, int len); +extern int interop_SocketGetPending(int sock); +extern int interop_SocketGetError(int sock); +extern int interop_SocketPoll(int sock); + cc_result Socket_Create(cc_socket* s) { - *s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - return *s == -1 ? errno : 0; + *s = interop_SocketCreate(); + return 0; } cc_result Socket_Available(cc_socket s, int* available) { - return ioctl(s, FIONREAD, available); + int res = interop_SocketGetPending(s); + /* returned result is negative for error */ + + if (res >= 0) { + *available = res; return 0; + } else { + *available = 0; return -res; + } } cc_result Socket_SetBlocking(cc_socket s, cc_bool blocking) { return ERR_NOT_SUPPORTED; /* sockets always async */ } cc_result Socket_GetError(cc_socket s, cc_result* result) { - socklen_t resultSize = sizeof(cc_result); - return getsockopt(s, SOL_SOCKET, SO_ERROR, result, &resultSize); + int res = interop_SocketGetError(s); + /* returned result is negative for error */ + + if (res >= 0) { + *result = res; return 0; + } else { + *result = 0; return -res; + } } cc_result Socket_Connect(cc_socket s, const cc_string* ip, int port) { - struct sockaddr addr; - cc_result res; - addr.sa_family = AF_INET; - - Stream_SetU16_BE( (cc_uint8*)&addr.sa_data[0], port); - if (!Utils_ParseIP(ip, (cc_uint8*)&addr.sa_data[2])) - return ERR_INVALID_ARGUMENT; - - res = connect(s, &addr, sizeof(addr)); - return res == -1 ? errno : 0; + char addr[NATIVE_STR_LEN]; + Platform_EncodeUtf8(addr, ip); + return interop_SocketConnect(s, addr, port); } cc_result Socket_Read(cc_socket s, cc_uint8* data, cc_uint32 count, cc_uint32* modified) { /* recv only reads one WebSocket frame at most, hence call it multiple times */ - int recvCount = 0, pending; - *modified = 0; + int res; *modified = 0; - while (count && !Socket_Available(s, &pending) && pending) { - recvCount = recv(s, data, count, 0); - if (recvCount == -1) return errno; + while (count) { + res = interop_SocketRecv(s, data, count); + /* returned result is negative for error */ - *modified += recvCount; - data += recvCount; count -= recvCount; + if (res >= 0) { + *modified += res; + data += res; count -= res; + } else { + /* EAGAIN when no data available */ + if (res == -EAGAIN) break; + return -res; + } } return 0; } cc_result Socket_Write(cc_socket s, const cc_uint8* data, cc_uint32 count, cc_uint32* modified) { - int sentCount = send(s, data, count, 0); - if (sentCount != -1) { *modified = sentCount; return 0; } - *modified = 0; return errno; + int res = interop_SocketSend(s, data, count); + /* returned result is negative for error */ + + if (res >= 0) { + *modified = res; return 0; + } else { + *modified = 0; return -res; + } } cc_result Socket_Close(cc_socket s) { - cc_result res = close(s); - if (res == -1) res = errno; - return res; + /* returned result is negative for error */ + return -interop_SocketClose(s); } -#include cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) { - struct pollfd pfd; - int flags; + int res = interop_SocketPoll(s), flags; + /* returned result is negative for error */ - pfd.fd = s; - pfd.events = mode == SOCKET_POLL_READ ? POLLIN : POLLOUT; - if (poll(&pfd, 1, 0) == -1) { *success = false; return errno; } - - /* to match select, closed socket still counts as readable */ - flags = mode == SOCKET_POLL_READ ? (POLLIN | POLLHUP) : POLLOUT; - *success = (pfd.revents & flags) != 0; - return 0; + if (res >= 0) { + flags = mode == SOCKET_POLL_READ ? 0x01 : 0x02; + *success = (res & flags) != 0; + return 0; + } else { + *success = false; return -res; + } } @@ -397,10 +412,13 @@ EMSCRIPTEN_KEEPALIVE void Platform_LogError(const char* msg) { } extern void interop_InitModule(void); +extern void interop_InitSockets(void); extern void interop_GetIndexedDBError(char* buffer); + void Platform_Init(void) { char tmp[64+1] = { 0 }; interop_InitModule(); + interop_InitSockets(); /* Check if an error occurred when pre-loading IndexedDB */ interop_GetIndexedDBError(tmp); diff --git a/src/interop_web.c b/src/interop_web.c index 8a1420fc2..e30868666 100644 --- a/src/interop_web.c +++ b/src/interop_web.c @@ -1,4 +1,8 @@ #include "Core.h" +/* Copyright 2010 The Emscripten Authors. All rights reserved. */ +/* Emscripten is available under two separate licenses, the MIT license and the */ +/* University of Illinois/NCSA Open Source License. Both these licenses can be */ +/* found in the LICENSE file. */ #ifdef CC_BUILD_WEB #include @@ -142,6 +146,207 @@ int interop_OpenTab(const char* url) { return 0; } +void interop_Log(const char* msg, int len) { + EM_ASM_({ Module.print(UTF8ArrayToString(HEAPU8, $0, $1)); }, msg, len); +} + +void interop_InitSockets(void) { + EM_ASM({ + window.SOCKETS = { + EBADF:-8,EISCONN:-30,ENOTCONN:-53,EAGAIN:-6,EWOULDBLOCK:-6,EHOSTUNREACH:-23,EINPROGRESS:-26,EALREADY:-7,ECONNRESET:-15,EINVAL:-28,ECONNREFUSED:-14, + sockets = [], + + createSocket:function() { + var sock = { + error: null, // Used in getsockopt for SOL_SOCKET/SO_ERROR test + recv_queue: [], + socket: null, + }; + sockets.push(sock); + + return (sockets.length - 1) | 0; + }, + connect:function(fd, addr, port) { + var sock = sockets[fd]; + if (!sock) return EBADF; + + // early out if we're already connected / in the middle of connecting + var ws = sock.socket; + if (ws) { + if (ws.readyState === ws.CONNECTING) return EALREADY; + return EISCONN; + } + + // create the actual websocket object and connect + try { + var parts = addr.split('/'); + var url = 'ws://' + parts[0] + ":" + port + "/" + parts.slice(1).join('/'); + ws = new WebSocket(url, 'ClassiCube'); + ws.binaryType = 'arraybuffer'; + } catch (e) { + return EHOSTUNREACH; + } + sock.socket = ws; + + ws.onopen = function() {}; + ws.onclose = function() {}; + ws.onmessage = function(event) { + var data = event.data; + if (typeof data === 'string') { + var encoder = new TextEncoder(); // should be utf-8 + data = encoder.encode(data); // make a typed array from the string + } else { + assert(data.byteLength !== undefined); // must receive an ArrayBuffer + if (data.byteLength == 0) { + // An empty ArrayBuffer will emit a pseudo disconnect event + // as recv/recvmsg will return zero which indicates that a socket + // has performed a shutdown although the connection has not been disconnected yet. + return; + } else { + data = new Uint8Array(data); // make a typed array view on the array buffer + } + } + sock.recv_queue.push(data); + }; + ws.onerror = function(error) { + // The WebSocket spec only allows a 'simple event' to be thrown on error, + // so we only really know as much as ECONNREFUSED. + sock.error = -ECONNREFUSED; // Used in getsockopt for SOL_SOCKET/SO_ERROR test. + }; + // always "fail" in non-blocking mode + return EINPROGRESS; + }, + poll:function(fd) { + var sock = sockets[fd]; + if (!sock) return EBADF; + + var ws = sock.socket; + if (!ws) return 0; + var mask = 0; + + if (sock.recv_queue.length || (ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED)) mask |= 1; + if (ws.readyState === ws.OPEN) mask |= 2; + return mask; + }, + getPending:function(fd) { + var sock = sockets[fd]; + if (!sock) return EBADF; + + var bytes = 0; + if (sock.recv_queue.length) { + bytes = sock.recv_queue[0].data.length; + } + return bytes; + }, + getError:function(fd) { + var sock = sockets[fd]; + if (!sock) return EBADF; + + return sock.error || 0; + }, + close:function(fd) { + var sock = sockets[fd]; + if (!sock) return EBADF; + + try { + sock.socket.close(); + } catch (e) { + } + delete sock.socket; + return 0; + }, + send:function(fd, src, length) { + var sock = sockets[fd]; + if (!sock) return EBADF; + + var ws = sock.socket; + if (!ws || ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED) { + return ENOTCONN; + } else if (ws.readyState === ws.CONNECTING) { + return EAGAIN; + } + var data = HEAP8.slice(src, src + length); + + try { + ws.send(data); + return length; + } catch (e) { + return EINVAL; + } + }, + recv:function(fd, dst, length) { + var sock = sockets[fd]; + if (!sock) return EBADF; + + var packet = sock.recv_queue.shift(); + if (!packet) { + var ws = sock.socket; + + if (!ws || ws.readyState == = ws.CLOSING || ws.readyState == = ws.CLOSED) { + return ENOTCONN; + } else { + // socket is in a valid state but truly has nothing available + return EAGAIN; + } + } + + // packet will be an ArrayBuffer if it's unadulterated, but if it's + // requeued TCP data it'll be an ArrayBufferView + var packetLength = packet.byteLength || packet.length; + var packetOffset = packet.byteOffset || 0; + var packetBuffer = packet.buffer || packet; + var bytesRead = Math.min(length, packetLength); + var msg = new Uint8Array(packetBuffer, packetOffset, bytesRead); + + // push back any unread data for TCP connections + if (bytesRead < packetLength) { + var bytesRemaining = packetLength - bytesRead; + packet = new Uint8Array(packetBuffer, packetOffset + bytesRead, bytesRemaining); + sock.recv_queue.unshift(packet); + } + + HEAPU8.set(msg.buffer, dst); + return msg.buffer.byteLength; + } + }; + }); +} + +int interop_SocketCreate(void) { + return EM_ASM_INT_V({ return SOCKETS.createSocket(); }); +} + +int interop_SocketConnect(int sock, const char* addr, int port) { + return EM_ASM_INT({ + var str = UTF8ToString($1); + return SOCKETS.connect($0, str, $2); + }, sock, addr, port); +} + +int interop_SocketClose(int sock) { + return EM_ASM_INT({ return SOCKETS.close($0); }, sock); +} + +int interop_SocketSend(int sock, const void* data, int len) { + return EM_ASM_INT({ return SOCKETS.send($0, $1, $2); }, sock, data, len); +} + +int interop_SocketRecv(int sock, void* data, int len) { + return EM_ASM_INT({ return SOCKETS.recv($0, $1, $2); }, sock, data, len); +} + +int interop_SocketGetPending(int sock) { + return EM_ASM_INT({ return SOCKETS.getPending($0); }, sock); +} + +int interop_SocketGetError(int sock) { + return EM_ASM_INT({ return SOCKETS.getError($0); }, sock); +} + +int interop_SocketPoll(int sock) { + return EM_ASM_INT({ return SOCKETS.poll($0); }, sock); +} + /*########################################################################################################################* *----------------------------------------------------------Window---------------------------------------------------------* From 3e828d09af7fa4ef0e3a897eea6c609cf85628fd Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sun, 23 May 2021 14:29:54 +1000 Subject: [PATCH 4/9] Web: move interop_web to .js file instead of continuously trying to make emcc (and myself) despair by embedding large amounts of JavaScript in EM_ASM --- src/interop_web.c | 593 --------------------------------------------- src/interop_web.js | 511 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 511 insertions(+), 593 deletions(-) delete mode 100644 src/interop_web.c create mode 100644 src/interop_web.js diff --git a/src/interop_web.c b/src/interop_web.c deleted file mode 100644 index e30868666..000000000 --- a/src/interop_web.c +++ /dev/null @@ -1,593 +0,0 @@ -#include "Core.h" -/* Copyright 2010 The Emscripten Authors. All rights reserved. */ -/* Emscripten is available under two separate licenses, the MIT license and the */ -/* University of Illinois/NCSA Open Source License. Both these licenses can be */ -/* found in the LICENSE file. */ - -#ifdef CC_BUILD_WEB -#include - -void interop_InitModule(void) { - EM_ASM({ - Module['websocket']['subprotocol'] = 'ClassiCube'; - - Module.saveBlob = function(blob, name) { - if (window.navigator.msSaveBlob) { - window.navigator.msSaveBlob(blob, name); return; - } - var url = window.URL.createObjectURL(blob); - var elem = document.createElement('a'); - - elem.href = url; - elem.download = name; - elem.style.display = 'none'; - - document.body.appendChild(elem); - elem.click(); - document.body.removeChild(elem); - window.URL.revokeObjectURL(url); - } - }); -} - - -/*########################################################################################################################* -*-----------------------------------------------------------Game----------------------------------------------------------* -*#########################################################################################################################*/ -void interop_TakeScreenshot(const char* path) { - EM_ASM_({ - var name = UTF8ToString($0); - var canvas = Module['canvas']; - if (canvas.toBlob) { - canvas.toBlob(function(blob) { Module.saveBlob(blob, name); }); - } else if (canvas.msToBlob) { - Module.saveBlob(canvas.msToBlob(), name); - } - }, path); -} - - -/*########################################################################################################################* -*-----------------------------------------------------------Http----------------------------------------------------------* -*#########################################################################################################################*/ -void interop_DownloadAsync(const char* urlStr, int method) { - /* onFinished = FUNC(data, len, status) */ - /* onProgress = FUNC(read, total) */ - EM_ASM_({ - var url = UTF8ToString($0); - var reqMethod = $1 == 1 ? 'HEAD' : 'GET'; - var onFinished = Module["_Http_OnFinishedAsync"]; - var onProgress = Module["_Http_OnUpdateProgress"]; - - var xhr = new XMLHttpRequest(); - xhr.open(reqMethod, url); - xhr.responseType = 'arraybuffer'; - - var getContentLength = function(e) { - if (e.total) return e.total; - - try { - var len = xhr.getResponseHeader('Content-Length'); - return parseInt(len, 10); - } catch (ex) { return 0; } - }; - - xhr.onload = function(e) { - var src = new Uint8Array(xhr.response); - var len = src.byteLength; - var data = _malloc(len); - HEAPU8.set(src, data); - onFinished(data, len || getContentLength(e), xhr.status); - }; - xhr.onerror = function(e) { onFinished(0, 0, xhr.status); }; - xhr.ontimeout = function(e) { onFinished(0, 0, xhr.status); }; - xhr.onprogress = function(e) { onProgress(e.loaded, e.total); }; - - try { xhr.send(); } catch (e) { onFinished(0, 0, 0); } - }, urlStr, method); -} - -int interop_IsHttpsOnly(void) { - /* If this webpage is https://, browsers deny any http:// downloading */ - return EM_ASM_INT_V({ return location.protocol === 'https:'; }); -} - - -/*########################################################################################################################* -*-----------------------------------------------------------Menu----------------------------------------------------------* -*#########################################################################################################################*/ -int interop_DownloadMap(const char* path, const char* filename) { - return EM_ASM_({ - try { - var name = UTF8ToString($0); - var data = FS.readFile(name); - var blob = new Blob([data], { type: 'application/octet-stream' }); - Module.saveBlob(blob, UTF8ToString($1)); - FS.unlink(name); - return 0; - } catch (e) { - if (!(e instanceof FS.ErrnoError)) abort(e); - return -e.errno; - } - }, path, filename); -} - -void interop_UploadTexPack(const char* path) { - /* Move from temp into texpacks folder */ - /* TODO: This is pretty awful and should be rewritten */ - EM_ASM_({ - var name = UTF8ToString($0); - var data = FS.readFile(name); - FS.writeFile('/texpacks/' + name.substring(1), data); - }, path); -} - - -/*########################################################################################################################* -*---------------------------------------------------------Platform--------------------------------------------------------* -*#########################################################################################################################*/ -void interop_GetIndexedDBError(char* buffer) { - EM_ASM_({ if (window.cc_idbErr) stringToUTF8(window.cc_idbErr, $0, 64); }, buffer); -} - -void interop_SyncFS(void) { - EM_ASM({ - FS.syncfs(false, function(err) { - if (!err) return; - console.log(err); - ccall('Platform_LogError', 'void', ['string'], ['&cError saving files to IndexedDB:']); - ccall('Platform_LogError', 'void', ['string'], [' &c' + err]); - }); - }); -} - -int interop_OpenTab(const char* url) { - EM_ASM_({ window.open(UTF8ToString($0)); }, url); - return 0; -} - -void interop_Log(const char* msg, int len) { - EM_ASM_({ Module.print(UTF8ArrayToString(HEAPU8, $0, $1)); }, msg, len); -} - -void interop_InitSockets(void) { - EM_ASM({ - window.SOCKETS = { - EBADF:-8,EISCONN:-30,ENOTCONN:-53,EAGAIN:-6,EWOULDBLOCK:-6,EHOSTUNREACH:-23,EINPROGRESS:-26,EALREADY:-7,ECONNRESET:-15,EINVAL:-28,ECONNREFUSED:-14, - sockets = [], - - createSocket:function() { - var sock = { - error: null, // Used in getsockopt for SOL_SOCKET/SO_ERROR test - recv_queue: [], - socket: null, - }; - sockets.push(sock); - - return (sockets.length - 1) | 0; - }, - connect:function(fd, addr, port) { - var sock = sockets[fd]; - if (!sock) return EBADF; - - // early out if we're already connected / in the middle of connecting - var ws = sock.socket; - if (ws) { - if (ws.readyState === ws.CONNECTING) return EALREADY; - return EISCONN; - } - - // create the actual websocket object and connect - try { - var parts = addr.split('/'); - var url = 'ws://' + parts[0] + ":" + port + "/" + parts.slice(1).join('/'); - ws = new WebSocket(url, 'ClassiCube'); - ws.binaryType = 'arraybuffer'; - } catch (e) { - return EHOSTUNREACH; - } - sock.socket = ws; - - ws.onopen = function() {}; - ws.onclose = function() {}; - ws.onmessage = function(event) { - var data = event.data; - if (typeof data === 'string') { - var encoder = new TextEncoder(); // should be utf-8 - data = encoder.encode(data); // make a typed array from the string - } else { - assert(data.byteLength !== undefined); // must receive an ArrayBuffer - if (data.byteLength == 0) { - // An empty ArrayBuffer will emit a pseudo disconnect event - // as recv/recvmsg will return zero which indicates that a socket - // has performed a shutdown although the connection has not been disconnected yet. - return; - } else { - data = new Uint8Array(data); // make a typed array view on the array buffer - } - } - sock.recv_queue.push(data); - }; - ws.onerror = function(error) { - // The WebSocket spec only allows a 'simple event' to be thrown on error, - // so we only really know as much as ECONNREFUSED. - sock.error = -ECONNREFUSED; // Used in getsockopt for SOL_SOCKET/SO_ERROR test. - }; - // always "fail" in non-blocking mode - return EINPROGRESS; - }, - poll:function(fd) { - var sock = sockets[fd]; - if (!sock) return EBADF; - - var ws = sock.socket; - if (!ws) return 0; - var mask = 0; - - if (sock.recv_queue.length || (ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED)) mask |= 1; - if (ws.readyState === ws.OPEN) mask |= 2; - return mask; - }, - getPending:function(fd) { - var sock = sockets[fd]; - if (!sock) return EBADF; - - var bytes = 0; - if (sock.recv_queue.length) { - bytes = sock.recv_queue[0].data.length; - } - return bytes; - }, - getError:function(fd) { - var sock = sockets[fd]; - if (!sock) return EBADF; - - return sock.error || 0; - }, - close:function(fd) { - var sock = sockets[fd]; - if (!sock) return EBADF; - - try { - sock.socket.close(); - } catch (e) { - } - delete sock.socket; - return 0; - }, - send:function(fd, src, length) { - var sock = sockets[fd]; - if (!sock) return EBADF; - - var ws = sock.socket; - if (!ws || ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED) { - return ENOTCONN; - } else if (ws.readyState === ws.CONNECTING) { - return EAGAIN; - } - var data = HEAP8.slice(src, src + length); - - try { - ws.send(data); - return length; - } catch (e) { - return EINVAL; - } - }, - recv:function(fd, dst, length) { - var sock = sockets[fd]; - if (!sock) return EBADF; - - var packet = sock.recv_queue.shift(); - if (!packet) { - var ws = sock.socket; - - if (!ws || ws.readyState == = ws.CLOSING || ws.readyState == = ws.CLOSED) { - return ENOTCONN; - } else { - // socket is in a valid state but truly has nothing available - return EAGAIN; - } - } - - // packet will be an ArrayBuffer if it's unadulterated, but if it's - // requeued TCP data it'll be an ArrayBufferView - var packetLength = packet.byteLength || packet.length; - var packetOffset = packet.byteOffset || 0; - var packetBuffer = packet.buffer || packet; - var bytesRead = Math.min(length, packetLength); - var msg = new Uint8Array(packetBuffer, packetOffset, bytesRead); - - // push back any unread data for TCP connections - if (bytesRead < packetLength) { - var bytesRemaining = packetLength - bytesRead; - packet = new Uint8Array(packetBuffer, packetOffset + bytesRead, bytesRemaining); - sock.recv_queue.unshift(packet); - } - - HEAPU8.set(msg.buffer, dst); - return msg.buffer.byteLength; - } - }; - }); -} - -int interop_SocketCreate(void) { - return EM_ASM_INT_V({ return SOCKETS.createSocket(); }); -} - -int interop_SocketConnect(int sock, const char* addr, int port) { - return EM_ASM_INT({ - var str = UTF8ToString($1); - return SOCKETS.connect($0, str, $2); - }, sock, addr, port); -} - -int interop_SocketClose(int sock) { - return EM_ASM_INT({ return SOCKETS.close($0); }, sock); -} - -int interop_SocketSend(int sock, const void* data, int len) { - return EM_ASM_INT({ return SOCKETS.send($0, $1, $2); }, sock, data, len); -} - -int interop_SocketRecv(int sock, void* data, int len) { - return EM_ASM_INT({ return SOCKETS.recv($0, $1, $2); }, sock, data, len); -} - -int interop_SocketGetPending(int sock) { - return EM_ASM_INT({ return SOCKETS.getPending($0); }, sock); -} - -int interop_SocketGetError(int sock) { - return EM_ASM_INT({ return SOCKETS.getError($0); }, sock); -} - -int interop_SocketPoll(int sock) { - return EM_ASM_INT({ return SOCKETS.poll($0); }, sock); -} - - -/*########################################################################################################################* -*----------------------------------------------------------Window---------------------------------------------------------* -*#########################################################################################################################*/ -int interop_CanvasWidth(void) { return EM_ASM_INT_V({ return Module['canvas'].width }); } -int interop_CanvasHeight(void) { return EM_ASM_INT_V({ return Module['canvas'].height }); } -int interop_ScreenWidth(void) { return EM_ASM_INT_V({ return screen.width; }); } -int interop_ScreenHeight(void) { return EM_ASM_INT_V({ return screen.height; }); } - -int interop_IsAndroid(void) { - return EM_ASM_INT_V({ return /Android/i.test(navigator.userAgent); }); -} -int interop_IsIOS(void) { - /* iOS 13 on iPad doesn't identify itself as iPad by default anymore */ - /* https://stackoverflow.com/questions/57765958/how-to-detect-ipad-and-ipad-os-version-in-ios-13-and-up */ - return EM_ASM_INT_V({ - return /iPhone|iPad|iPod/i.test(navigator.userAgent) || - (navigator.platform === 'MacIntel' && navigator.maxTouchPoints && navigator.maxTouchPoints > 2); - }); -} - -void interop_InitContainer(void) { - /* Create wrapper div if necessary (so input textbox shows in fullscreen on android)*/ - EM_ASM({ - var agent = navigator.userAgent; - var canvas = Module['canvas']; - window.cc_container = document.body; - - if (/Android/i.test(agent)) { - var wrapper = document.createElement("div"); - wrapper.id = 'canvas_wrapper'; - - canvas.parentNode.insertBefore(wrapper, canvas); - wrapper.appendChild(canvas); - window.cc_container = wrapper; - } - }); -} - -int interop_GetContainerID(void) { - /* For chrome on android, need to make container div fullscreen instead */ - return EM_ASM_INT_V({ return document.getElementById('canvas_wrapper') ? 1 : 0; }); -} - -void interop_ForceTouchPageLayout(void) { - EM_ASM( if (typeof(forceTouchLayout) === 'function') forceTouchLayout(); ); -} - -void interop_SetPageTitle(const char* title) { - EM_ASM_({ document.title = UTF8ToString($0); }, title); -} - -void interop_AddClipboardListeners(void) { - /* Copy text, but only if user isn't selecting something else on the webpage */ - /* (don't check window.clipboardData here, that's handled in interop_TrySetClipboardText instead) */ - EM_ASM(window.addEventListener('copy', - function(e) { - if (window.getSelection && window.getSelection().toString()) return; - ccall('Window_RequestClipboardText', 'void'); - if (!window.cc_copyText) return; - - if (e.clipboardData) { - e.clipboardData.setData('text/plain', window.cc_copyText); - e.preventDefault(); - } - window.cc_copyText = null; - }); - ); - - /* Paste text (window.clipboardData is handled in interop_TryGetClipboardText instead) */ - EM_ASM(window.addEventListener('paste', - function(e) { - if (e.clipboardData) { - var contents = e.clipboardData.getData('text/plain'); - ccall('Window_GotClipboardText', 'void', ['string'], [contents]); - } - }); - ); -} - -void interop_TryGetClipboardText(void) { - /* For IE11, use window.clipboardData to get the clipboard */ - EM_ASM_({ - if (window.clipboardData) { - var contents = window.clipboardData.getData('Text'); - ccall('Window_StoreClipboardText', 'void', ['string'], [contents]); - } - }); -} - -void interop_TrySetClipboardText(const char* text) { - /* For IE11, use window.clipboardData to set the clipboard */ - /* For other browsers, instead use the window.copy events */ - EM_ASM_({ - if (window.clipboardData) { - if (window.getSelection && window.getSelection().toString()) return; - window.clipboardData.setData('Text', UTF8ToString($0)); - } else { - window.cc_copyText = UTF8ToString($0); - } - }, text); -} - -void interop_EnterFullscreen(void) { - /* emscripten sets css size to screen's base width/height, */ - /* except that becomes wrong when device rotates. */ - /* Better to just set CSS width/height to always be 100% */ - EM_ASM({ - var canvas = Module['canvas']; - canvas.style.width = '100%'; - canvas.style.height = '100%'; - }); - - /* By default, pressing Escape will immediately exit fullscreen - which is */ - /* quite annoying given that it is also the Menu key. Some browsers allow */ - /* 'locking' the Escape key, so that you have to hold down Escape to exit. */ - /* NOTE: This ONLY works when the webpage is a https:// one */ - EM_ASM({ try { navigator.keyboard.lock(["Escape"]); } catch (ex) { } }); -} - -/* Adjust from document coordinates to element coordinates */ -void interop_AdjustXY(int* x, int* y) { - EM_ASM_({ - var canvasRect = Module['canvas'].getBoundingClientRect(); - HEAP32[$0 >> 2] = HEAP32[$0 >> 2] - canvasRect.left; - HEAP32[$1 >> 2] = HEAP32[$1 >> 2] - canvasRect.top; - }, x, y); -} - -void interop_RequestCanvasResize(void) { - EM_ASM( if (typeof(resizeGameCanvas) === 'function') resizeGameCanvas(); ); -} - -void interop_SetCursorVisible(int visible) { - if (visible) { - EM_ASM(Module['canvas'].style['cursor'] = 'default'; ); - } else { - EM_ASM(Module['canvas'].style['cursor'] = 'none'; ); - } -} - -void interop_ShowDialog(const char* title, const char* msg) { - EM_ASM_({ alert(UTF8ToString($0) + "\n\n" + UTF8ToString($1)); }, title, msg); -} - -void interop_OpenKeyboard(const char* text, int type, const char* placeholder) { - EM_ASM_({ - var elem = window.cc_inputElem; - if (!elem) { - if ($1 == 1) { - elem = document.createElement('input'); - elem.setAttribute('inputmode', 'decimal'); - } else { - elem = document.createElement('textarea'); - } - elem.setAttribute('style', 'position:absolute; left:0; bottom:0; margin: 0px; width: 100%'); - elem.setAttribute('placeholder', UTF8ToString($2)); - elem.value = UTF8ToString($0); - - elem.addEventListener('input', - function(ev) { - ccall('Window_OnTextChanged', 'void', ['string'], [ev.target.value]); - }, false); - window.cc_inputElem = elem; - - window.cc_divElem = document.createElement('div'); - window.cc_divElem.setAttribute('style', 'position:absolute; left:0; top:0; width:100%; height:100%; background-color: black; opacity:0.4; resize:none; pointer-events:none;'); - - window.cc_container.appendChild(window.cc_divElem); - window.cc_container.appendChild(elem); - } - elem.focus(); - elem.click(); - }, text, type, placeholder); -} - -/* NOTE: When pressing 'Go' on the on-screen keyboard, some web browsers add \n to value */ -void interop_SetKeyboardText(const char* text) { - EM_ASM_({ - if (!window.cc_inputElem) return; - var str = UTF8ToString($0); - var cur = window.cc_inputElem.value; - - if (cur.length && cur[cur.length - 1] == '\n') { cur = cur.substring(0, cur.length - 1); } - if (str != cur) window.cc_inputElem.value = str; - }, text); -} - -void interop_CloseKeyboard(void) { - EM_ASM({ - if (!window.cc_inputElem) return; - window.cc_container.removeChild(window.cc_divElem); - window.cc_container.removeChild(window.cc_inputElem); - window.cc_divElem = null; - window.cc_inputElem = null; - }); -} - -void interop_OpenFileDialog(const char* filter) { - EM_ASM_({ - var elem = window.cc_uploadElem; - if (!elem) { - elem = document.createElement('input'); - elem.setAttribute('type', 'file'); - elem.setAttribute('style', 'display: none'); - elem.accept = UTF8ToString($0); - - elem.addEventListener('change', - function(ev) { - var files = ev.target.files; - for (var i = 0; i < files.length; i++) { - var reader = new FileReader(); - var name = files[i].name; - - reader.onload = function(e) { - var data = new Uint8Array(e.target.result); - FS.createDataFile('/', name, data, true, true, true); - ccall('Window_OnFileUploaded', 'void', ['string'], ['/' + name]); - FS.unlink('/' + name); - }; - reader.readAsArrayBuffer(files[i]); - } - window.cc_container.removeChild(window.cc_uploadElem); - window.cc_uploadElem = null; - }, false); - window.cc_uploadElem = elem; - window.cc_container.appendChild(elem); - } - elem.click(); - }, filter); -} - - -/*########################################################################################################################* -*--------------------------------------------------------GLContext--------------------------------------------------------* -*#########################################################################################################################*/ -void interop_GetGpuRenderer(char* buffer, int len) { - EM_ASM_({ - var dbg = GLctx.getExtension('WEBGL_debug_renderer_info'); - var str = dbg ? GLctx.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : ""; - stringToUTF8(str, $0, $1); - }, buffer, len); -} -#endif diff --git a/src/interop_web.js b/src/interop_web.js new file mode 100644 index 000000000..141f2f82c --- /dev/null +++ b/src/interop_web.js @@ -0,0 +1,511 @@ +// Copyright 2010 The Emscripten Authors. All rights reserved. +// Emscripten is available under two separate licenses, +// the MIT license and the University of Illinois/NCSA Open Source License. +// Both these licenses can be found in the LICENSE file. + +mergeInto(LibraryManager.library, { + + interop_InitModule: function() { + Module.saveBlob = function(blob, name) { + if (window.navigator.msSaveBlob) { + window.navigator.msSaveBlob(blob, name); return; + } + var url = window.URL.createObjectURL(blob); + var elem = document.createElement('a'); + + elem.href = url; + elem.download = name; + elem.style.display = 'none'; + + document.body.appendChild(elem); + elem.click(); + document.body.removeChild(elem); + window.URL.revokeObjectURL(url); + } + }, + interop_TakeScreenshot: function(path) { + var name = UTF8ToString(path); + var canvas = Module['canvas']; + if (canvas.toBlob) { + canvas.toBlob(function(blob) { Module.saveBlob(blob, name); }); + } else if (canvas.msToBlob) { + Module.saveBlob(canvas.msToBlob(), name); + } + }, + + +//######################################################################################################################## +//-----------------------------------------------------------Http--------------------------------------------------------- +//######################################################################################################################## + interop_DownloadAsync: function(urlStr, method) { + // onFinished = FUNC(data, len, status) + // onProgress = FUNC(read, total) + var url = UTF8ToString(urlStr); + var reqMethod = method == 1 ? 'HEAD' : 'GET'; + var onFinished = Module["_Http_OnFinishedAsync"]; + var onProgress = Module["_Http_OnUpdateProgress"]; + + var xhr = new XMLHttpRequest(); + xhr.open(reqMethod, url); + xhr.responseType = 'arraybuffer'; + + var getContentLength = function(e) { + if (e.total) return e.total; + + try { + var len = xhr.getResponseHeader('Content-Length'); + return parseInt(len, 10); + } catch (ex) { return 0; } + }; + + xhr.onload = function(e) { + var src = new Uint8Array(xhr.response); + var len = src.byteLength; + var data = _malloc(len); + HEAPU8.set(src, data); + onFinished(data, len || getContentLength(e), xhr.status); + }; + xhr.onerror = function(e) { onFinished(0, 0, xhr.status); }; + xhr.ontimeout = function(e) { onFinished(0, 0, xhr.status); }; + xhr.onprogress = function(e) { onProgress(e.loaded, e.total); }; + + try { xhr.send(); } catch (e) { onFinished(0, 0, 0); } + }, + interop_IsHttpsOnly : function() { + // If this webpage is https://, browsers deny any http:// downloading + return location.protocol === 'https:'; + }, + + +//######################################################################################################################## +//-----------------------------------------------------------Menu--------------------------------------------------------- +//######################################################################################################################## + interop_DownloadMap: function(path, filename) { + try { + var name = UTF8ToString(path); + var data = FS.readFile(name); + var blob = new Blob([data], { type: 'application/octet-stream' }); + Module.saveBlob(blob, UTF8ToString(filename)); + FS.unlink(name); + return 0; + } catch (e) { + if (!(e instanceof FS.ErrnoError)) abort(e); + return -e.errno; + } + }, + interop_UploadTexPack: function(path) { + // Move from temp into texpacks folder + // TODO: This is pretty awful and should be rewritten + var name = UTF8ToString(path); + var data = FS.readFile(name); + FS.writeFile('/texpacks/' + name.substring(1), data); + }, + + +//######################################################################################################################## +//---------------------------------------------------------Platform------------------------------------------------------- +//######################################################################################################################## + interop_GetIndexedDBError: function(buffer) { + if (window.cc_idbErr) stringToUTF8(window.cc_idbErr, buffer, 64); + }, + interop_SyncFS: function() { + FS.syncfs(false, function(err) { + if (!err) return; + console.log(err); + ccall('Platform_LogError', 'void', ['string'], ['&cError saving files to IndexedDB:']); + ccall('Platform_LogError', 'void', ['string'], [' &c' + err]); + }); + }, + interop_OpenTab: function(url) { + window.open(UTF8ToString(url)); + return 0; + }, + interop_Log: function(msg, len) { + Module.print(UTF8ArrayToString(HEAPU8, msg, len)); + }, + interop_InitSockets: function() { + window.SOCKETS = { + EBADF:-8,EISCONN:-30,ENOTCONN:-53,EAGAIN:-6,EWOULDBLOCK:-6,EHOSTUNREACH:-23,EINPROGRESS:-26,EALREADY:-7,ECONNRESET:-15,EINVAL:-28,ECONNREFUSED:-14, + sockets: [], + + createSocket:function() { + var sock = { + error: null, // Used in getsockopt for SOL_SOCKET/SO_ERROR test + recv_queue: [], + socket: null, + }; + this.sockets.push(sock); + + return (this.sockets.length - 1) | 0; + }, + connect:function(fd, addr, port) { + var sock = this.sockets[fd]; + if (!sock) return this.EBADF; + + // early out if we're already connected / in the middle of connecting + var ws = sock.socket; + if (ws) { + if (ws.readyState === ws.CONNECTING) return this.EALREADY; + return this.EISCONN; + } + + // create the actual websocket object and connect + try { + var parts = addr.split('/'); + var url = 'ws://' + parts[0] + ":" + port + "/" + parts.slice(1).join('/'); + ws = new WebSocket(url, 'ClassiCube'); + ws.binaryType = 'arraybuffer'; + } catch (e) { + return this.EHOSTUNREACH; + } + sock.socket = ws; + + ws.onopen = function() {}; + ws.onclose = function() {}; + ws.onmessage = function(event) { + var data = event.data; + if (typeof data === 'string') { + var encoder = new TextEncoder(); // should be utf-8 + data = encoder.encode(data); // make a typed array from the string + } else { + assert(data.byteLength !== undefined); // must receive an ArrayBuffer + if (data.byteLength == 0) { + // An empty ArrayBuffer will emit a pseudo disconnect event + // as recv/recvmsg will return zero which indicates that a socket + // has performed a shutdown although the connection has not been disconnected yet. + return; + } else { + data = new Uint8Array(data); // make a typed array view on the array buffer + } + } + sock.recv_queue.push(data); + }; + ws.onerror = function(error) { + // The WebSocket spec only allows a 'simple event' to be thrown on error, + // so we only really know as much as ECONNREFUSED. + sock.error = -this.ECONNREFUSED; // Used in getsockopt for SOL_SOCKET/SO_ERROR test. + }; + // always "fail" in non-blocking mode + return this.EINPROGRESS; + }, + poll:function(fd) { + var sock = this.sockets[fd]; + if (!sock) return this.EBADF; + + var ws = sock.socket; + if (!ws) return 0; + var mask = 0; + + if (sock.recv_queue.length || (ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED)) mask |= 1; + if (ws.readyState === ws.OPEN) mask |= 2; + return mask; + }, + getPending:function(fd) { + var sock = this.sockets[fd]; + if (!sock) return this.EBADF; + + var bytes = 0; + if (sock.recv_queue.length) { + bytes = sock.recv_queue[0].data.length; + } + return bytes; + }, + getError:function(fd) { + var sock = this.sockets[fd]; + if (!sock) return this.EBADF; + + return sock.error || 0; + }, + close:function(fd) { + var sock = this.sockets[fd]; + if (!sock) return this.EBADF; + + try { + sock.socket.close(); + } catch (e) { + } + delete sock.socket; + return 0; + }, + send:function(fd, src, length) { + var sock = this.sockets[fd]; + if (!sock) return this.EBADF; + + var ws = sock.socket; + if (!ws || ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED) { + return this.ENOTCONN; + } else if (ws.readyState === ws.CONNECTING) { + return this.EAGAIN; + } + var data = HEAP8.slice(src, src + length); + + try { + ws.send(data); + return length; + } catch (e) { + return this.EINVAL; + } + }, + recv:function(fd, dst, length) { + var sock = this.sockets[fd]; + if (!sock) return this.EBADF; + + var packet = sock.recv_queue.shift(); + if (!packet) { + var ws = sock.socket; + + if (!ws || ws.readyState == ws.CLOSING || ws.readyState == ws.CLOSED) { + return this.ENOTCONN; + } else { + // socket is in a valid state but truly has nothing available + return this.EAGAIN; + } + } + + // packet will be an ArrayBuffer if it's unadulterated, but if it's + // requeued TCP data it'll be an ArrayBufferView + var packetLength = packet.byteLength || packet.length; + var packetOffset = packet.byteOffset || 0; + var packetBuffer = packet.buffer || packet; + var bytesRead = Math.min(length, packetLength); + var msg = new Uint8Array(packetBuffer, packetOffset, bytesRead); + + // push back any unread data for TCP connections + if (bytesRead < packetLength) { + var bytesRemaining = packetLength - bytesRead; + packet = new Uint8Array(packetBuffer, packetOffset + bytesRead, bytesRemaining); + sock.recv_queue.unshift(packet); + } + + HEAPU8.set(msg.buffer, dst); + return msg.buffer.byteLength; + } + }; + }, + interop_SocketCreate: function() { + return SOCKETS.createSocket(); + }, + interop_SocketConnect: function(sock, addr, port) { + var str = UTF8ToString(addr); + return SOCKETS.connect(sock, str, port); + }, + interop_SocketClose: function(sock) { + return SOCKETS.close(sock); + }, + interop_SocketSend: function(sock, data, len) { + return SOCKETS.send(sock, data, len); + }, + interop_SocketRecv: function(sock, data, len) { + return SOCKETS.recv(sock, data, len); + }, + interop_SocketGetPending: function(sock) { + return SOCKETS.getPending(sock); + }, + interop_SocketGetError: function(sock) { + return SOCKETS.getError(sock); + }, + interop_SocketPoll: function(sock) { + return SOCKETS.poll(sock); + }, + + +//######################################################################################################################## +//----------------------------------------------------------Window-------------------------------------------------------- +//######################################################################################################################## + interop_CanvasWidth: function() { return Module['canvas'].width }, + interop_CanvasHeight: function() { return Module['canvas'].height }, + interop_ScreenWidth: function() { return screen.width; }, + interop_ScreenHeight: function() { return screen.height; }, + + interop_IsAndroid: function() { + return /Android/i.test(navigator.userAgent); + }, + interop_IsIOS: function() { + // iOS 13 on iPad doesn't identify itself as iPad by default anymore + // https://stackoverflow.com/questions/57765958/how-to-detect-ipad-and-ipad-os-version-in-ios-13-and-up + return /iPhone|iPad|iPod/i.test(navigator.userAgent) || + (navigator.platform === 'MacIntel' && navigator.maxTouchPoints && navigator.maxTouchPoints > 2); + }, + interop_InitContainer: function() { + // Create wrapper div if necessary (so input textbox shows in fullscreen on android) + var agent = navigator.userAgent; + var canvas = Module['canvas']; + window.cc_container = document.body; + + if (/Android/i.test(agent)) { + var wrapper = document.createElement("div"); + wrapper.id = 'canvas_wrapper'; + + canvas.parentNode.insertBefore(wrapper, canvas); + wrapper.appendChild(canvas); + window.cc_container = wrapper; + } + }, + interop_GetContainerID: function() { + // For chrome on android, need to make container div fullscreen instead + return document.getElementById('canvas_wrapper') ? 1 : 0; + }, + interop_ForceTouchPageLayout: function() { + if (typeof(forceTouchLayout) === 'function') forceTouchLayout(); + }, + interop_SetPageTitle : function(title) { + document.title = UTF8ToString(title); + }, + interop_AddClipboardListeners: function() { + // Copy text, but only if user isn't selecting something else on the webpage + // (don't check window.clipboardData here, that's handled in interop_TrySetClipboardText instead) + window.addEventListener('copy', + function(e) { + if (window.getSelection && window.getSelection().toString()) return; + ccall('Window_RequestClipboardText', 'void'); + if (!window.cc_copyText) return; + + if (e.clipboardData) { + e.clipboardData.setData('text/plain', window.cc_copyText); + e.preventDefault(); + } + window.cc_copyText = null; + }); + + // Paste text (window.clipboardData is handled in interop_TryGetClipboardText instead) + window.addEventListener('paste', + function(e) { + if (e.clipboardData) { + var contents = e.clipboardData.getData('text/plain'); + ccall('Window_GotClipboardText', 'void', ['string'], [contents]); + } + }); + }, + interop_TryGetClipboardText: function() { + // For IE11, use window.clipboardData to get the clipboard + if (window.clipboardData) { + var contents = window.clipboardData.getData('Text'); + ccall('Window_StoreClipboardText', 'void', ['string'], [contents]); + } + }, + interop_TrySetClipboardText: function(text) { + // For IE11, use window.clipboardData to set the clipboard */ + // For other browsers, instead use the window.copy events */ + if (window.clipboardData) { + if (window.getSelection && window.getSelection().toString()) return; + window.clipboardData.setData('Text', UTF8ToString(text)); + } else { + window.cc_copyText = UTF8ToString(text); + } + }, + interop_EnterFullscreen: function() { + // emscripten sets css size to screen's base width/height, + // except that becomes wrong when device rotates. + // Better to just set CSS width/height to always be 100% + var canvas = Module['canvas']; + canvas.style.width = '100%'; + canvas.style.height = '100%'; + + // By default, pressing Escape will immediately exit fullscreen - which is + // quite annoying given that it is also the Menu key. Some browsers allow + // 'locking' the Escape key, so that you have to hold down Escape to exit. + // NOTE: This ONLY works when the webpage is a https:// one + try { navigator.keyboard.lock(["Escape"]); } catch (ex) { } + }, + + // Adjust from document coordinates to element coordinates + interop_AdjustXY: function(x, y) { + var canvasRect = Module['canvas'].getBoundingClientRect(); + HEAP32[x >> 2] = HEAP32[x >> 2] - canvasRect.left; + HEAP32[y >> 2] = HEAP32[y >> 2] - canvasRect.top; + }, + interop_RequestCanvasResize: function() { + if (typeof(resizeGameCanvas) === 'function') resizeGameCanvas(); + }, + interop_SetCursorVisible: function(visible) { + Module['canvas'].style['cursor'] = visible ? 'default' : 'none'; + }, + interop_ShowDialog: function(title, msg) { + alert(UTF8ToString(title) + "\n\n" + UTF8ToString(msg)); + }, + interop_OpenKeyboard: function(text, type, placeholder) { + var elem = window.cc_inputElem; + if (!elem) { + if (type == 1) { + elem = document.createElement('input'); + elem.setAttribute('inputmode', 'decimal'); + } else { + elem = document.createElement('textarea'); + } + elem.setAttribute('style', 'position:absolute; left:0; bottom:0; margin: 0px; width: 100%'); + elem.setAttribute('placeholder', UTF8ToString(placeholder)); + elem.value = UTF8ToString(text); + + elem.addEventListener('input', + function(ev) { + ccall('Window_OnTextChanged', 'void', ['string'], [ev.target.value]); + }, false); + window.cc_inputElem = elem; + + window.cc_divElem = document.createElement('div'); + window.cc_divElem.setAttribute('style', 'position:absolute; left:0; top:0; width:100%; height:100%; background-color: black; opacity:0.4; resize:none; pointer-events:none;'); + + window.cc_container.appendChild(window.cc_divElem); + window.cc_container.appendChild(elem); + } + elem.focus(); + elem.click(); + }, + // NOTE: When pressing 'Go' on the on-screen keyboard, some web browsers add \n to value + interop_SetKeyboardText: function(text) { + if (!window.cc_inputElem) return; + var str = UTF8ToString(text); + var cur = window.cc_inputElem.value; + + if (cur.length && cur[cur.length - 1] == '\n') { cur = cur.substring(0, cur.length - 1); } + if (str != cur) window.cc_inputElem.value = str; + }, + interop_CloseKeyboard: function() { + if (!window.cc_inputElem) return; + window.cc_container.removeChild(window.cc_divElem); + window.cc_container.removeChild(window.cc_inputElem); + window.cc_divElem = null; + window.cc_inputElem = null; + }, + interop_OpenFileDialog: function(filter) { + var elem = window.cc_uploadElem; + if (!elem) { + elem = document.createElement('input'); + elem.setAttribute('type', 'file'); + elem.setAttribute('style', 'display: none'); + elem.accept = UTF8ToString(filter); + + elem.addEventListener('change', + function(ev) { + var files = ev.target.files; + for (var i = 0; i < files.length; i++) { + var reader = new FileReader(); + var name = files[i].name; + + reader.onload = function(e) { + var data = new Uint8Array(e.target.result); + FS.createDataFile('/', name, data, true, true, true); + ccall('Window_OnFileUploaded', 'void', ['string'], ['/' + name]); + FS.unlink('/' + name); + }; + reader.readAsArrayBuffer(files[i]); + } + window.cc_container.removeChild(window.cc_uploadElem); + window.cc_uploadElem = null; + }, false); + window.cc_uploadElem = elem; + window.cc_container.appendChild(elem); + } + elem.click(); + }, + + +//######################################################################################################################## +//--------------------------------------------------------GLContext------------------------------------------------------- +//######################################################################################################################### + interop_GetGpuRenderer : function(buffer, len) { + var dbg = GLctx.getExtension('WEBGL_debug_renderer_info'); + var str = dbg ? GLctx.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : ""; + stringToUTF8(str, buffer, len); + } +}); \ No newline at end of file From 355f35c7197938c7a2bfadb6befad10c2738120f Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sun, 23 May 2021 15:27:42 +1000 Subject: [PATCH 5/9] Fix multiplayer not working --- src/Platform_Web.c | 4 +++- src/interop_web.js | 14 +++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Platform_Web.c b/src/Platform_Web.c index 30b65906b..7f9dddf77 100644 --- a/src/Platform_Web.c +++ b/src/Platform_Web.c @@ -283,7 +283,9 @@ cc_result Socket_GetError(cc_socket s, cc_result* result) { cc_result Socket_Connect(cc_socket s, const cc_string* ip, int port) { char addr[NATIVE_STR_LEN]; Platform_EncodeUtf8(addr, ip); - return interop_SocketConnect(s, addr, port); + + /* returned result is negative for error */ + return -interop_SocketConnect(s, addr, port); } cc_result Socket_Read(cc_socket s, cc_uint8* data, cc_uint32 count, cc_uint32* modified) { diff --git a/src/interop_web.js b/src/interop_web.js index 141f2f82c..ad0f4e8be 100644 --- a/src/interop_web.js +++ b/src/interop_web.js @@ -203,17 +203,13 @@ mergeInto(LibraryManager.library, { getPending:function(fd) { var sock = this.sockets[fd]; if (!sock) return this.EBADF; - - var bytes = 0; - if (sock.recv_queue.length) { - bytes = sock.recv_queue[0].data.length; - } - return bytes; + + return sock.recv_queue.length; }, getError:function(fd) { var sock = this.sockets[fd]; if (!sock) return this.EBADF; - + return sock.error || 0; }, close:function(fd) { @@ -277,8 +273,8 @@ mergeInto(LibraryManager.library, { sock.recv_queue.unshift(packet); } - HEAPU8.set(msg.buffer, dst); - return msg.buffer.byteLength; + HEAPU8.set(msg, dst); + return msg.byteLength; } }; }, From cef1e5b484d1c524cc4fb2cccf1cc95cd35bbf33 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sun, 23 May 2021 15:38:34 +1000 Subject: [PATCH 6/9] Fix timing out not working properly --- readme.md | 2 +- src/interop_web.js | 52 +++++++++++++++++++++++----------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/readme.md b/readme.md index 80b54b8dd..fde249315 100644 --- a/readme.md +++ b/readme.md @@ -127,7 +127,7 @@ NOTE: You have to change entry->d_type == DT_DIR to Directory_Exists(&path) (TOD #### Web -```emcc *.c -s ALLOW_MEMORY_GROWTH=1 --preload-file texpacks/default.zip``` +```emcc *.c -s ALLOW_MEMORY_GROWTH=1 --js-library interop_web.js --preload-file texpacks/default.zip``` The generated javascript file has some issues. [See here for how to fix](doc/compile-fixes.md#webclient-patches) diff --git a/src/interop_web.js b/src/interop_web.js index ad0f4e8be..61b14f2fe 100644 --- a/src/interop_web.js +++ b/src/interop_web.js @@ -134,19 +134,19 @@ mergeInto(LibraryManager.library, { recv_queue: [], socket: null, }; - this.sockets.push(sock); + SOCKETS.sockets.push(sock); - return (this.sockets.length - 1) | 0; + return (SOCKETS.sockets.length - 1) | 0; }, connect:function(fd, addr, port) { - var sock = this.sockets[fd]; - if (!sock) return this.EBADF; + var sock = SOCKETS.sockets[fd]; + if (!sock) return SOCKETS.EBADF; // early out if we're already connected / in the middle of connecting var ws = sock.socket; if (ws) { - if (ws.readyState === ws.CONNECTING) return this.EALREADY; - return this.EISCONN; + if (ws.readyState === ws.CONNECTING) return SOCKETS.EALREADY; + return SOCKETS.EISCONN; } // create the actual websocket object and connect @@ -156,7 +156,7 @@ mergeInto(LibraryManager.library, { ws = new WebSocket(url, 'ClassiCube'); ws.binaryType = 'arraybuffer'; } catch (e) { - return this.EHOSTUNREACH; + return SOCKETS.EHOSTUNREACH; } sock.socket = ws; @@ -183,14 +183,14 @@ mergeInto(LibraryManager.library, { ws.onerror = function(error) { // The WebSocket spec only allows a 'simple event' to be thrown on error, // so we only really know as much as ECONNREFUSED. - sock.error = -this.ECONNREFUSED; // Used in getsockopt for SOL_SOCKET/SO_ERROR test. + sock.error = -SOCKETS.ECONNREFUSED; // Used in getsockopt for SOL_SOCKET/SO_ERROR test. }; // always "fail" in non-blocking mode - return this.EINPROGRESS; + return SOCKETS.EINPROGRESS; }, poll:function(fd) { - var sock = this.sockets[fd]; - if (!sock) return this.EBADF; + var sock = SOCKETS.sockets[fd]; + if (!sock) return SOCKETS.EBADF; var ws = sock.socket; if (!ws) return 0; @@ -201,20 +201,20 @@ mergeInto(LibraryManager.library, { return mask; }, getPending:function(fd) { - var sock = this.sockets[fd]; - if (!sock) return this.EBADF; + var sock = SOCKETS.sockets[fd]; + if (!sock) return SOCKETS.EBADF; return sock.recv_queue.length; }, getError:function(fd) { - var sock = this.sockets[fd]; - if (!sock) return this.EBADF; + var sock = SOCKETS.sockets[fd]; + if (!sock) return SOCKETS.EBADF; return sock.error || 0; }, close:function(fd) { - var sock = this.sockets[fd]; - if (!sock) return this.EBADF; + var sock = SOCKETS.sockets[fd]; + if (!sock) return SOCKETS.EBADF; try { sock.socket.close(); @@ -224,14 +224,14 @@ mergeInto(LibraryManager.library, { return 0; }, send:function(fd, src, length) { - var sock = this.sockets[fd]; - if (!sock) return this.EBADF; + var sock = SOCKETS.sockets[fd]; + if (!sock) return SOCKETS.EBADF; var ws = sock.socket; if (!ws || ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED) { - return this.ENOTCONN; + return SOCKETS.ENOTCONN; } else if (ws.readyState === ws.CONNECTING) { - return this.EAGAIN; + return SOCKETS.EAGAIN; } var data = HEAP8.slice(src, src + length); @@ -239,22 +239,22 @@ mergeInto(LibraryManager.library, { ws.send(data); return length; } catch (e) { - return this.EINVAL; + return SOCKETS.EINVAL; } }, recv:function(fd, dst, length) { - var sock = this.sockets[fd]; - if (!sock) return this.EBADF; + var sock = SOCKETS.sockets[fd]; + if (!sock) return SOCKETS.EBADF; var packet = sock.recv_queue.shift(); if (!packet) { var ws = sock.socket; if (!ws || ws.readyState == ws.CLOSING || ws.readyState == ws.CLOSED) { - return this.ENOTCONN; + return SOCKETS.ENOTCONN; } else { // socket is in a valid state but truly has nothing available - return this.EAGAIN; + return SOCKETS.EAGAIN; } } From 6e57334f9af29df31199d27583511ca009bd0e2b Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sun, 23 May 2021 18:43:25 +1000 Subject: [PATCH 7/9] Web: Show right message when invalid IP provided --- src/ClassiCube.vcxproj | 1 - src/ClassiCube.vcxproj.filters | 3 --- src/Platform_Web.c | 27 +++++++++++++++------------ 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/ClassiCube.vcxproj b/src/ClassiCube.vcxproj index 29ec97bd4..cf6d0a7ee 100644 --- a/src/ClassiCube.vcxproj +++ b/src/ClassiCube.vcxproj @@ -272,7 +272,6 @@ - diff --git a/src/ClassiCube.vcxproj.filters b/src/ClassiCube.vcxproj.filters index 1841b3969..d1b1ea021 100644 --- a/src/ClassiCube.vcxproj.filters +++ b/src/ClassiCube.vcxproj.filters @@ -545,8 +545,5 @@ Source Files\Platform - - Source Files\Platform - \ No newline at end of file diff --git a/src/Platform_Web.c b/src/Platform_Web.c index 7f9dddf77..8b9edc408 100644 --- a/src/Platform_Web.c +++ b/src/Platform_Web.c @@ -282,10 +282,14 @@ cc_result Socket_GetError(cc_socket s, cc_result* result) { cc_result Socket_Connect(cc_socket s, const cc_string* ip, int port) { char addr[NATIVE_STR_LEN]; + int res; Platform_EncodeUtf8(addr, ip); - /* returned result is negative for error */ - return -interop_SocketConnect(s, addr, port); + res = -interop_SocketConnect(s, addr, port); + + /* error returned when invalid address provided */ + if (res == EHOSTUNREACH) return ERR_INVALID_ARGUMENT; + return res; } cc_result Socket_Read(cc_socket s, cc_uint8* data, cc_uint32 count, cc_uint32* modified) { @@ -293,8 +297,8 @@ cc_result Socket_Read(cc_socket s, cc_uint8* data, cc_uint32 count, cc_uint32* m int res; *modified = 0; while (count) { - res = interop_SocketRecv(s, data, count); /* returned result is negative for error */ + res = interop_SocketRecv(s, data, count); if (res >= 0) { *modified += res; @@ -309,8 +313,8 @@ cc_result Socket_Read(cc_socket s, cc_uint8* data, cc_uint32 count, cc_uint32* m } cc_result Socket_Write(cc_socket s, const cc_uint8* data, cc_uint32 count, cc_uint32* modified) { - int res = interop_SocketSend(s, data, count); /* returned result is negative for error */ + int res = interop_SocketSend(s, data, count); if (res >= 0) { *modified = res; return 0; @@ -325,8 +329,8 @@ cc_result Socket_Close(cc_socket s) { } cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) { - int res = interop_SocketPoll(s), flags; /* returned result is negative for error */ + int res = interop_SocketPoll(s), flags; if (res >= 0) { flags = mode == SOCKET_POLL_READ ? 0x01 : 0x02; @@ -391,19 +395,18 @@ int Platform_EncodeUtf8(void* data, const cc_string* src) { } cc_bool Platform_DescribeError(cc_result res, cc_string* dst) { - char chars[NATIVE_STR_LEN]; + char* str; int len; - /* For unrecognised error codes, strerror_r might return messages */ + /* For unrecognised error codes, strerror might return messages */ /* such as 'No error information', which is not very useful */ - /* (could check errno here but quicker just to skip entirely) */ if (res >= 1000) return false; - len = strerror_r(res, chars, NATIVE_STR_LEN); - if (len == -1) return false; + str = strerror(res); + if (!str) return false; - len = String_CalcLen(chars, NATIVE_STR_LEN); - String_AppendUtf8(dst, chars, len); + len = String_CalcLen(str, NATIVE_STR_LEN); + String_AppendUtf8(dst, str, len); return true; } From 5d93db81d256d495e9e3bc4b54bdf805c072b6dd Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Fri, 28 May 2021 17:54:57 +1000 Subject: [PATCH 8/9] Fix multiplayer not working at all with old emscripten --- misc/buildbot.sh | 2 +- src/Platform_Web.c | 15 +++++++++++---- src/interop_web.js | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/misc/buildbot.sh b/misc/buildbot.sh index a7c11da01..6b716ce5e 100644 --- a/misc/buildbot.sh +++ b/misc/buildbot.sh @@ -90,7 +90,7 @@ WEB_CC="/home/buildbot/emsdk/emscripten/1.38.31/emcc" build_web() { echo "Building web.." rm cc.js - $WEB_CC *.c -O1 -o cc.js -s WASM=0 -s LEGACY_VM_SUPPORT=1 -s ALLOW_MEMORY_GROWTH=1 -s ABORTING_MALLOC=0 --preload-file texpacks/default.zip -w + $WEB_CC *.c -O1 -o cc.js --js-library interop_web.js -s WASM=0 -s LEGACY_VM_SUPPORT=1 -s ALLOW_MEMORY_GROWTH=1 -s ABORTING_MALLOC=0 --preload-file texpacks/default.zip -w # so game loads textures from classicube.net/static/default.zip sed -i 's#cc.data#/static/default.zip#g' cc.js # fix mouse wheel scrolling page not being properly prevented diff --git a/src/Platform_Web.c b/src/Platform_Web.c index 8b9edc408..bc91a0ed3 100644 --- a/src/Platform_Web.c +++ b/src/Platform_Web.c @@ -22,10 +22,17 @@ #include #include +/* Unfortunately, errno constants are different in some older emscripten versions */ +/* (linux errno compared to WASI errno) */ +/* So just use the same numbers as interop_web.js (otherwise connecting always fail) */ +#define _EINPROGRESS 26 +#define _EAGAIN 6 /* same as EWOULDBLOCK */ +#define _EHOSTUNREACH 23 + const cc_result ReturnCode_FileShareViolation = 1000000000; /* TODO: not used apparently */ const cc_result ReturnCode_FileNotFound = ENOENT; -const cc_result ReturnCode_SocketInProgess = EINPROGRESS; -const cc_result ReturnCode_SocketWouldBlock = EWOULDBLOCK; +const cc_result ReturnCode_SocketInProgess = _EINPROGRESS; +const cc_result ReturnCode_SocketWouldBlock = _EAGAIN; const cc_result ReturnCode_DirectoryExists = EEXIST; #include #include "Chat.h" @@ -288,7 +295,7 @@ cc_result Socket_Connect(cc_socket s, const cc_string* ip, int port) { res = -interop_SocketConnect(s, addr, port); /* error returned when invalid address provided */ - if (res == EHOSTUNREACH) return ERR_INVALID_ARGUMENT; + if (res == _EHOSTUNREACH) return ERR_INVALID_ARGUMENT; return res; } @@ -305,7 +312,7 @@ cc_result Socket_Read(cc_socket s, cc_uint8* data, cc_uint32 count, cc_uint32* m data += res; count -= res; } else { /* EAGAIN when no data available */ - if (res == -EAGAIN) break; + if (res == -_EAGAIN) break; return -res; } } diff --git a/src/interop_web.js b/src/interop_web.js index 61b14f2fe..0ab1e0f99 100644 --- a/src/interop_web.js +++ b/src/interop_web.js @@ -125,7 +125,7 @@ mergeInto(LibraryManager.library, { }, interop_InitSockets: function() { window.SOCKETS = { - EBADF:-8,EISCONN:-30,ENOTCONN:-53,EAGAIN:-6,EWOULDBLOCK:-6,EHOSTUNREACH:-23,EINPROGRESS:-26,EALREADY:-7,ECONNRESET:-15,EINVAL:-28,ECONNREFUSED:-14, + EBADF:-8,EISCONN:-30,ENOTCONN:-53,EAGAIN:-6,EHOSTUNREACH:-23,EINPROGRESS:-26,EALREADY:-7,ECONNRESET:-15,EINVAL:-28,ECONNREFUSED:-14, sockets: [], createSocket:function() { From aef0ec7602f4bec8bfa358c205b275af62bcc9d7 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sat, 29 May 2021 08:48:27 +1000 Subject: [PATCH 9/9] Fix multiplayer not working in IE11 --- src/interop_web.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/interop_web.js b/src/interop_web.js index 0ab1e0f99..7a39af8c7 100644 --- a/src/interop_web.js +++ b/src/interop_web.js @@ -233,7 +233,12 @@ mergeInto(LibraryManager.library, { } else if (ws.readyState === ws.CONNECTING) { return SOCKETS.EAGAIN; } - var data = HEAP8.slice(src, src + length); + + // var data = HEAP8.slice(src, src + length); unsupported in IE11 + var data = new Uint8Array(length); + for (var i = 0; i < length; i++) { + data[i] = HEAP8[src + i]; + } try { ws.send(data);