mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-09-14 18:15:28 -04:00
Merge pull request #852 from UnknownShadow200/SplitBackend
Splitup Platform.c + rewrite web backend sockets
This commit is contained in:
commit
00b7ef80ba
60
license.txt
60
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.
|
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
|
FreeType license
|
||||||
==================
|
==================
|
||||||
The FreeType Project LICENSE
|
The FreeType Project LICENSE
|
||||||
|
@ -90,7 +90,7 @@ WEB_CC="/home/buildbot/emsdk/emscripten/1.38.31/emcc"
|
|||||||
build_web() {
|
build_web() {
|
||||||
echo "Building web.."
|
echo "Building web.."
|
||||||
rm cc.js
|
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
|
# so game loads textures from classicube.net/static/default.zip
|
||||||
sed -i 's#cc.data#/static/default.zip#g' cc.js
|
sed -i 's#cc.data#/static/default.zip#g' cc.js
|
||||||
# fix mouse wheel scrolling page not being properly prevented
|
# fix mouse wheel scrolling page not being properly prevented
|
||||||
|
@ -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.**
|
**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
|
#### 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)
|
The generated javascript file has some issues. [See here for how to fix](doc/compile-fixes.md#webclient-patches)
|
||||||
|
|
||||||
|
@ -249,6 +249,7 @@
|
|||||||
<ClInclude Include="Widgets.h" />
|
<ClInclude Include="Widgets.h" />
|
||||||
<ClInclude Include="Window.h" />
|
<ClInclude Include="Window.h" />
|
||||||
<ClInclude Include="World.h" />
|
<ClInclude Include="World.h" />
|
||||||
|
<ClInclude Include="_PlatformBase.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="Animations.c" />
|
<ClCompile Include="Animations.c" />
|
||||||
@ -280,6 +281,9 @@
|
|||||||
<ClCompile Include="Deflate.c" />
|
<ClCompile Include="Deflate.c" />
|
||||||
<ClCompile Include="Model.c" />
|
<ClCompile Include="Model.c" />
|
||||||
<ClCompile Include="Menus.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="Protocol.c" />
|
||||||
<ClCompile Include="Physics.c" />
|
<ClCompile Include="Physics.c" />
|
||||||
<ClCompile Include="IsometricDrawer.c" />
|
<ClCompile Include="IsometricDrawer.c" />
|
||||||
@ -293,7 +297,6 @@
|
|||||||
<ClCompile Include="BlockPhysics.c" />
|
<ClCompile Include="BlockPhysics.c" />
|
||||||
<ClCompile Include="PickedPosRenderer.c" />
|
<ClCompile Include="PickedPosRenderer.c" />
|
||||||
<ClCompile Include="Picking.c" />
|
<ClCompile Include="Picking.c" />
|
||||||
<ClCompile Include="Platform.c" />
|
|
||||||
<ClCompile Include="Program.c" />
|
<ClCompile Include="Program.c" />
|
||||||
<ClCompile Include="Resources.c" />
|
<ClCompile Include="Resources.c" />
|
||||||
<ClCompile Include="Screens.c" />
|
<ClCompile Include="Screens.c" />
|
||||||
|
@ -312,6 +312,9 @@
|
|||||||
<ClInclude Include="Animations.h">
|
<ClInclude Include="Animations.h">
|
||||||
<Filter>Header Files\TexturePack</Filter>
|
<Filter>Header Files\TexturePack</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="_PlatformBase.h">
|
||||||
|
<Filter>Header Files\Platform</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="String.c">
|
<ClCompile Include="String.c">
|
||||||
@ -431,9 +434,6 @@
|
|||||||
<ClCompile Include="Vorbis.c">
|
<ClCompile Include="Vorbis.c">
|
||||||
<Filter>Source Files\Audio</Filter>
|
<Filter>Source Files\Audio</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="Platform.c">
|
|
||||||
<Filter>Source Files\Platform</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="Model.c">
|
<ClCompile Include="Model.c">
|
||||||
<Filter>Source Files\Entities</Filter>
|
<Filter>Source Files\Entities</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@ -536,5 +536,14 @@
|
|||||||
<ClCompile Include="Animations.c">
|
<ClCompile Include="Animations.c">
|
||||||
<Filter>Source Files\TexturePack</Filter>
|
<Filter>Source Files\TexturePack</Filter>
|
||||||
</ClCompile>
|
</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>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
@ -235,7 +235,6 @@ Thus it is **NOT SAFE** to allocate a string on the stack. */
|
|||||||
#define CC_BUILD_SDL
|
#define CC_BUILD_SDL
|
||||||
#elif defined __EMSCRIPTEN__
|
#elif defined __EMSCRIPTEN__
|
||||||
#define CC_BUILD_WEB
|
#define CC_BUILD_WEB
|
||||||
#define CC_BUILD_POSIX
|
|
||||||
#define CC_BUILD_GL
|
#define CC_BUILD_GL
|
||||||
#define CC_BUILD_GLMODERN
|
#define CC_BUILD_GLMODERN
|
||||||
#define CC_BUILD_GLES
|
#define CC_BUILD_GLES
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
#if defined CC_BUILD_WEB
|
#if defined CC_BUILD_WEB
|
||||||
/* Can't see native CPU state with javascript */
|
/* Can't see native CPU state with javascript */
|
||||||
#undef CC_BUILD_POSIX
|
|
||||||
#elif defined CC_BUILD_WIN
|
#elif defined CC_BUILD_WIN
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#define NOSERVICE
|
#define NOSERVICE
|
||||||
|
File diff suppressed because it is too large
Load Diff
471
src/Platform_Web.c
Normal file
471
src/Platform_Web.c
Normal 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
809
src/Platform_WinApi.c
Normal 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
110
src/_PlatformBase.h
Normal 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;
|
||||||
|
}
|
@ -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
512
src/interop_web.js
Normal 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);
|
||||||
|
}
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user