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/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/readme.md b/readme.md
index 6e44530c6..fde249315 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.**

@@ -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/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/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.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..bc91a0ed3
--- /dev/null
+++ b/src/Platform_Web.c
@@ -0,0 +1,471 @@
+#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"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#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 = _EAGAIN;
+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;
+}
+
+extern void interop_Log(const char* msg, int len);
+void Platform_Log(const char* msg, int len) {
+ interop_Log(msg, len);
+}
+
+#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----------------------------------------------------------*
+*#########################################################################################################################*/
+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 = interop_SocketCreate();
+ return 0;
+}
+
+cc_result Socket_Available(cc_socket s, int* 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) {
+ 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) {
+ char addr[NATIVE_STR_LEN];
+ int res;
+ Platform_EncodeUtf8(addr, ip);
+ /* returned result is negative for error */
+ 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) {
+ /* recv only reads one WebSocket frame at most, hence call it multiple times */
+ int res; *modified = 0;
+
+ while (count) {
+ /* returned result is negative for error */
+ res = interop_SocketRecv(s, data, count);
+
+ 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) {
+ /* returned result is negative for error */
+ int res = interop_SocketSend(s, data, count);
+
+ if (res >= 0) {
+ *modified = res; return 0;
+ } else {
+ *modified = 0; return -res;
+ }
+}
+
+cc_result Socket_Close(cc_socket s) {
+ /* returned result is negative for error */
+ return -interop_SocketClose(s);
+}
+
+cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) {
+ /* returned result is negative for error */
+ int res = interop_SocketPoll(s), flags;
+
+ if (res >= 0) {
+ flags = mode == SOCKET_POLL_READ ? 0x01 : 0x02;
+ *success = (res & flags) != 0;
+ return 0;
+ } else {
+ *success = false; return -res;
+ }
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------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---------------------------------------------------------*
+*#########################################################################################################################*/
+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* str;
+ int len;
+
+ /* For unrecognised error codes, strerror might return messages */
+ /* such as 'No error information', which is not very useful */
+ if (res >= 1000) return false;
+
+ str = strerror(res);
+ if (!str) return false;
+
+ len = String_CalcLen(str, NATIVE_STR_LEN);
+ String_AppendUtf8(dst, str, len);
+ return true;
+}
+
+EMSCRIPTEN_KEEPALIVE void Platform_LogError(const char* msg) {
+ /* no point 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_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);
+ 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 */
+}
+void Platform_Free(void) { }
+
+
+/*########################################################################################################################*
+*-------------------------------------------------------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;
+}
diff --git a/src/interop_web.c b/src/interop_web.c
deleted file mode 100644
index 8a1420fc2..000000000
--- a/src/interop_web.c
+++ /dev/null
@@ -1,388 +0,0 @@
-#include "Core.h"
-
-#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;
-}
-
-
-/*########################################################################################################################*
-*----------------------------------------------------------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..7a39af8c7
--- /dev/null
+++ b/src/interop_web.js
@@ -0,0 +1,512 @@
+// 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,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.sockets.push(sock);
+
+ return (SOCKETS.sockets.length - 1) | 0;
+ },
+ connect:function(fd, addr, port) {
+ 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 SOCKETS.EALREADY;
+ return SOCKETS.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 SOCKETS.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 = -SOCKETS.ECONNREFUSED; // Used in getsockopt for SOL_SOCKET/SO_ERROR test.
+ };
+ // always "fail" in non-blocking mode
+ return SOCKETS.EINPROGRESS;
+ },
+ poll:function(fd) {
+ var sock = SOCKETS.sockets[fd];
+ if (!sock) return SOCKETS.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.sockets[fd];
+ if (!sock) return SOCKETS.EBADF;
+
+ return sock.recv_queue.length;
+ },
+ getError:function(fd) {
+ var sock = SOCKETS.sockets[fd];
+ if (!sock) return SOCKETS.EBADF;
+
+ return sock.error || 0;
+ },
+ close:function(fd) {
+ var sock = SOCKETS.sockets[fd];
+ if (!sock) return SOCKETS.EBADF;
+
+ try {
+ sock.socket.close();
+ } catch (e) {
+ }
+ delete sock.socket;
+ return 0;
+ },
+ send:function(fd, src, length) {
+ 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 SOCKETS.ENOTCONN;
+ } else if (ws.readyState === ws.CONNECTING) {
+ return SOCKETS.EAGAIN;
+ }
+
+ // 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);
+ return length;
+ } catch (e) {
+ return SOCKETS.EINVAL;
+ }
+ },
+ recv:function(fd, dst, length) {
+ 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 SOCKETS.ENOTCONN;
+ } else {
+ // socket is in a valid state but truly has nothing available
+ return SOCKETS.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, dst);
+ return msg.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