Merge pull request #852 from UnknownShadow200/SplitBackend

Splitup Platform.c + rewrite web backend sockets
This commit is contained in:
UnknownShadow200 2021-05-29 10:35:35 +10:00 committed by GitHub
commit 00b7ef80ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 2021 additions and 1350 deletions

View File

@ -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

View File

@ -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

View File

@ -1,4 +1,4 @@
ClassiCube is a custom Minecraft Classic and ClassiCube client written in C that works on Windows, macOS, Linux, Android, BSD, Solaris, Haiku, and in a browser.
ClassiCube is a custom Minecraft Classic and ClassiCube client written in C that works on Windows, macOS, Linux, Android, FreeBSD, NetBSD, OpenBSD, Solaris, Haiku, and in a browser.
**It is not affiliated with (or supported by) Mojang AB, Minecraft, or Microsoft in any way.**
![screenshot_n](http://i.imgur.com/FCiwl27.png)
@ -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)

View File

@ -249,6 +249,7 @@
<ClInclude Include="Widgets.h" />
<ClInclude Include="Window.h" />
<ClInclude Include="World.h" />
<ClInclude Include="_PlatformBase.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="Animations.c" />
@ -280,6 +281,9 @@
<ClCompile Include="Deflate.c" />
<ClCompile Include="Model.c" />
<ClCompile Include="Menus.c" />
<ClCompile Include="Platform_Posix.c" />
<ClCompile Include="Platform_Web.c" />
<ClCompile Include="Platform_WinApi.c" />
<ClCompile Include="Protocol.c" />
<ClCompile Include="Physics.c" />
<ClCompile Include="IsometricDrawer.c" />
@ -293,7 +297,6 @@
<ClCompile Include="BlockPhysics.c" />
<ClCompile Include="PickedPosRenderer.c" />
<ClCompile Include="Picking.c" />
<ClCompile Include="Platform.c" />
<ClCompile Include="Program.c" />
<ClCompile Include="Resources.c" />
<ClCompile Include="Screens.c" />

View File

@ -312,6 +312,9 @@
<ClInclude Include="Animations.h">
<Filter>Header Files\TexturePack</Filter>
</ClInclude>
<ClInclude Include="_PlatformBase.h">
<Filter>Header Files\Platform</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="String.c">
@ -431,9 +434,6 @@
<ClCompile Include="Vorbis.c">
<Filter>Source Files\Audio</Filter>
</ClCompile>
<ClCompile Include="Platform.c">
<Filter>Source Files\Platform</Filter>
</ClCompile>
<ClCompile Include="Model.c">
<Filter>Source Files\Entities</Filter>
</ClCompile>
@ -536,5 +536,14 @@
<ClCompile Include="Animations.c">
<Filter>Source Files\TexturePack</Filter>
</ClCompile>
<ClCompile Include="Platform_WinApi.c">
<Filter>Source Files\Platform</Filter>
</ClCompile>
<ClCompile Include="Platform_Web.c">
<Filter>Source Files\Platform</Filter>
</ClCompile>
<ClCompile Include="Platform_Posix.c">
<Filter>Source Files\Platform</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

471
src/Platform_Web.c Normal file
View File

@ -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 <errno.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <stdio.h>
/* 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 <emscripten.h>
#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

809
src/Platform_WinApi.c Normal file
View File

@ -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 <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <shellapi.h>
#include <wincrypt.h>
static HANDLE heap;
const cc_result ReturnCode_FileShareViolation = ERROR_SHARING_VIOLATION;
const cc_result ReturnCode_FileNotFound = ERROR_FILE_NOT_FOUND;
const cc_result ReturnCode_SocketInProgess = WSAEINPROGRESS;
const cc_result ReturnCode_SocketWouldBlock = WSAEWOULDBLOCK;
const cc_result ReturnCode_DirectoryExists = ERROR_ALREADY_EXISTS;
/*########################################################################################################################*
*---------------------------------------------------------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

110
src/_PlatformBase.h Normal file
View File

@ -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;
}

View File

@ -1,388 +0,0 @@
#include "Core.h"
#ifdef CC_BUILD_WEB
#include <emscripten/emscripten.h>
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

512
src/interop_web.js Normal file
View File

@ -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);
}
});