Bug Summary

File:Platform.c
Warning:line 674, column 2
Use of memory after it is freed

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -triple amd64-unknown-openbsd6.8 -analyze -disable-free -disable-llvm-verifier -discard-value-names -main-file-name Platform.c -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 1 -pic-is-pie -mthread-model posix -mframe-pointer=all -relaxed-aliasing -fno-rounding-math -masm-verbose -mconstructor-aliases -munwind-tables -target-cpu x86-64 -target-feature +retpoline-indirect-calls -target-feature +retpoline-indirect-branches -dwarf-column-info -fno-split-dwarf-inlining -debugger-tuning=gdb -resource-dir /usr/local/lib/clang/10.0.1 -I /usr/X11R6/include -I /usr/local/include -fdebug-compilation-dir /home/ben/Projects/ClassiCube/src -ferror-limit 19 -fmessage-length 0 -fwrapv -D_RET_PROTECTOR -ret-protector -fgnuc-version=4.2.1 -fobjc-runtime=gnustep -fdiagnostics-show-option -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-valloc -fno-builtin-free -fno-builtin-strdup -fno-builtin-strndup -analyzer-output=html -faddrsig -o /home/ben/Projects/ClassiCube/src/scan/2020-12-10-180422-33404-1 -x c Platform.c
1#include "Platform.h"
2#include "String.h"
3#include "Logger.h"
4#include "Stream.h"
5#include "ExtMath.h"
6#include "Drawer2D.h"
7#include "Funcs.h"
8#include "Window.h"
9#include "Utils.h"
10#include "Errors.h"
11
12#if defined CC_BUILD_WIN
13#define WIN32_LEAN_AND_MEAN
14#define NOSERVICE
15#define NOMCX
16#define NOIME
17#ifndef UNICODE
18#define UNICODE
19#define _UNICODE
20#endif
21
22#include <windows.h>
23#include <winsock2.h>
24#include <ws2tcpip.h>
25#include <shellapi.h>
26#include <wincrypt.h>
27
28#define Socket__Error()(*__errno()) WSAGetLastError()
29static HANDLE heap;
30
31const cc_result ReturnCode_FileShareViolation = ERROR_SHARING_VIOLATION;
32const cc_result ReturnCode_FileNotFound = ERROR_FILE_NOT_FOUND;
33const cc_result ReturnCode_SocketInProgess = WSAEINPROGRESS;
34const cc_result ReturnCode_SocketWouldBlock = WSAEWOULDBLOCK;
35#elif defined CC_BUILD_POSIX
36/* POSIX can be shared between Linux/BSD/macOS */
37#include <errno(*__errno()).h>
38#include <time.h>
39#include <stdlib.h>
40#include <string.h>
41#include <unistd.h>
42#include <dirent.h>
43#include <fcntl.h>
44#include <pthread.h>
45#include <arpa/inet.h>
46#include <netinet/in.h>
47#include <sys/socket.h>
48#include <sys/ioctl.h>
49#include <sys/types.h>
50#include <sys/stat.h>
51#include <sys/time.h>
52#include <utime.h>
53#include <signal.h>
54#include <stdio.h>
55
56#define Socket__Error()(*__errno()) errno(*__errno())
57static char* defaultDirectory;
58const cc_result ReturnCode_FileShareViolation = 1000000000; /* TODO: not used apparently */
59const cc_result ReturnCode_FileNotFound = ENOENT2;
60const cc_result ReturnCode_SocketInProgess = EINPROGRESS36;
61const cc_result ReturnCode_SocketWouldBlock = EWOULDBLOCK35;
62#endif
63/* Platform specific include files (Try to share for UNIX-ish) */
64#if defined CC_BUILD_OSX
65#include <mach/mach_time.h>
66#include <mach-o/dyld.h>
67#include <ApplicationServices/ApplicationServices.h>
68#elif defined CC_BUILD_SOLARIS
69#include <sys/filio.h>
70#elif defined CC_BUILD_BSD
71#include <sys/sysctl.h>
72#elif defined CC_BUILD_HAIKU
73/* TODO: Use load_image/resume_thread instead of fork */
74/* Otherwise opening browser never works because fork fails */
75#include <kernel/image.h>
76#elif defined CC_BUILD_WEB
77#include <emscripten.h>
78#include "Chat.h"
79#endif
80
81
82/*########################################################################################################################*
83*---------------------------------------------------------Memory----------------------------------------------------------*
84*#########################################################################################################################*/
85void Mem_Set(void* dst, cc_uint8 value, cc_uint32 numBytes) { memset(dst, value, numBytes); }
86void Mem_Copy(void* dst, const void* src, cc_uint32 numBytes) { memcpy(dst, src, numBytes); }
87
88CC_NOINLINE__attribute__((noinline)) static void AbortOnAllocFailed(const char* place) {
89 cc_string log; char logBuffer[STRING_SIZE64+20 + 1];
90 String_InitArray_NT(log, logBuffer)log.buffer = logBuffer; log.length = 0; log.capacity = sizeof
(logBuffer) - 1;
;
91
92 String_Format1(&log, "Out of memory! (when allocating %c)", place);
93 log.buffer[log.length] = '\0';
94 Logger_Abort(log.buffer);
95}
96
97void* Mem_Alloc(cc_uint32 numElems, cc_uint32 elemsSize, const char* place) {
98 void* ptr = Mem_TryAlloc(numElems, elemsSize);
2
Calling 'Mem_TryAlloc'
5
Returned allocated memory
99 if (!ptr) AbortOnAllocFailed(place);
6
Assuming 'ptr' is non-null
7
Taking false branch
100 return ptr;
101}
102
103void* Mem_AllocCleared(cc_uint32 numElems, cc_uint32 elemsSize, const char* place) {
104 void* ptr = Mem_TryAllocCleared(numElems, elemsSize);
105 if (!ptr) AbortOnAllocFailed(place);
106 return ptr;
107}
108
109void* Mem_Realloc(void* mem, cc_uint32 numElems, cc_uint32 elemsSize, const char* place) {
110 void* ptr = Mem_TryRealloc(mem, numElems, elemsSize);
111 if (!ptr) AbortOnAllocFailed(place);
112 return ptr;
113}
114
115static cc_uint32 CalcMemSize(cc_uint32 numElems, cc_uint32 elemsSize) {
116 cc_uint32 numBytes = numElems * elemsSize; /* TODO: avoid overflow here */
117 if (numBytes < numElems) return 0; /* TODO: Use proper overflow checking */
118 return numBytes;
119}
120
121#if defined CC_BUILD_WIN
122void* Mem_TryAlloc(cc_uint32 numElems, cc_uint32 elemsSize) {
123 cc_uint32 size = CalcMemSize(numElems, elemsSize);
124 return size ? HeapAlloc(heap, 0, size) : NULL((void*)0);
125}
126
127void* Mem_TryAllocCleared(cc_uint32 numElems, cc_uint32 elemsSize) {
128 cc_uint32 size = CalcMemSize(numElems, elemsSize);
129 return size ? HeapAlloc(heap, HEAP_ZERO_MEMORY, size) : NULL((void*)0);
130}
131
132void* Mem_TryRealloc(void* mem, cc_uint32 numElems, cc_uint32 elemsSize) {
133 cc_uint32 size = CalcMemSize(numElems, elemsSize);
134 return size ? HeapReAlloc(heap, 0, mem, size) : NULL((void*)0);
135}
136
137void Mem_Free(void* mem) {
138 if (mem) HeapFree(heap, 0, mem);
139}
140#elif defined CC_BUILD_POSIX
141void* Mem_TryAlloc(cc_uint32 numElems, cc_uint32 elemsSize) {
142 cc_uint32 size = CalcMemSize(numElems, elemsSize);
143 return size
2.1
'size' is 8
? malloc(size) : NULL((void*)0);
3
'?' condition is true
4
Memory is allocated
144}
145
146void* Mem_TryAllocCleared(cc_uint32 numElems, cc_uint32 elemsSize) {
147 return calloc(numElems, elemsSize);
148}
149
150void* Mem_TryRealloc(void* mem, cc_uint32 numElems, cc_uint32 elemsSize) {
151 cc_uint32 size = CalcMemSize(numElems, elemsSize);
152 return size ? realloc(mem, size) : NULL((void*)0);
153}
154
155void Mem_Free(void* mem) {
156 if (mem
16.1
'mem' is non-null
) free(mem);
17
Taking true branch
18
Memory is released
157}
158#endif
159
160
161/*########################################################################################################################*
162*------------------------------------------------------Logging/Time-------------------------------------------------------*
163*#########################################################################################################################*/
164void Platform_Log1(const char* format, const void* a1) {
165 Platform_Log4(format, a1, NULL((void*)0), NULL((void*)0), NULL((void*)0));
166}
167void Platform_Log2(const char* format, const void* a1, const void* a2) {
168 Platform_Log4(format, a1, a2, NULL((void*)0), NULL((void*)0));
169}
170void Platform_Log3(const char* format, const void* a1, const void* a2, const void* a3) {
171 Platform_Log4(format, a1, a2, a3, NULL((void*)0));
172}
173
174void Platform_Log4(const char* format, const void* a1, const void* a2, const void* a3, const void* a4) {
175 cc_string msg; char msgBuffer[512];
176 String_InitArray(msg, msgBuffer)msg.buffer = msgBuffer; msg.length = 0; msg.capacity = sizeof
(msgBuffer);
;
177
178 String_Format4(&msg, format, a1, a2, a3, a4);
179 Platform_Log(msg.buffer, msg.length);
180}
181
182void Platform_LogConst(const char* message) {
183 Platform_Log(message, String_Length(message));
184}
185
186/* TODO: check this is actually accurate */
187static cc_uint64 sw_freqMul = 1, sw_freqDiv = 1;
188cc_uint64 Stopwatch_ElapsedMicroseconds(cc_uint64 beg, cc_uint64 end) {
189 if (end < beg) return 0;
190 return ((end - beg) * sw_freqMul) / sw_freqDiv;
191}
192
193cc_uint64 Stopwatch_ElapsedMilliseconds(cc_uint64 beg, cc_uint64 end) {
194 return Stopwatch_ElapsedMicroseconds(beg, end) / 1000;
195}
196
197#if defined CC_BUILD_WIN
198static HANDLE conHandle;
199static BOOL hasDebugger;
200
201void Platform_Log(const char* msg, int len) {
202 char tmp[2048 + 1];
203 DWORD wrote;
204
205 if (conHandle) {
206 WriteFile(conHandle, msg, len, &wrote, NULL((void*)0));
207 WriteFile(conHandle, "\n", 1, &wrote, NULL((void*)0));
208 }
209
210 if (!hasDebugger) return;
211 len = min(len, 2048)((len) < (2048) ? (len) : (2048));
212 Mem_Copy(tmp, msg, len); tmp[len] = '\0';
213
214 OutputDebugStringA(tmp);
215 OutputDebugStringA("\n");
216}
217
218#define FILETIME_EPOCH 50491123200000ULL
219#define FILETIME_UNIX_EPOCH 11644473600LL
220#define FileTime_TotalMS(time) ((time / 10000) + FILETIME_EPOCH)
221#define FileTime_UnixTime(time) ((time / 10000000) - FILETIME_UNIX_EPOCH)
222TimeMS DateTime_CurrentUTC_MS(void) {
223 FILETIME ft;
224 cc_uint64 raw;
225
226 GetSystemTimeAsFileTime(&ft);
227 /* in 100 nanosecond units, since Jan 1 1601 */
228 raw = ft.dwLowDateTime | ((cc_uint64)ft.dwHighDateTime << 32);
229 return FileTime_TotalMS(raw);
230}
231
232void DateTime_CurrentLocal(struct DateTime* t) {
233 SYSTEMTIME localTime;
234 GetLocalTime(&localTime);
235
236 t->year = localTime.wYear;
237 t->month = localTime.wMonth;
238 t->day = localTime.wDay;
239 t->hour = localTime.wHour;
240 t->minute = localTime.wMinute;
241 t->second = localTime.wSecond;
242}
243
244static cc_bool sw_highRes;
245cc_uint64 Stopwatch_Measure(void) {
246 LARGE_INTEGER t;
247 FILETIME ft;
248
249 if (sw_highRes) {
250 QueryPerformanceCounter(&t);
251 return (cc_uint64)t.QuadPart;
252 } else {
253 GetSystemTimeAsFileTime(&ft);
254 return (cc_uint64)ft.dwLowDateTime | ((cc_uint64)ft.dwHighDateTime << 32);
255 }
256}
257#elif defined CC_BUILD_POSIX
258/* log to android logcat */
259#ifdef CC_BUILD_ANDROID
260#include <android/log.h>
261void Platform_Log(const char* msg, int len) {
262 char tmp[2048 + 1];
263 len = min(len, 2048)((len) < (2048) ? (len) : (2048));
264
265 Mem_Copy(tmp, msg, len); tmp[len] = '\0';
266 __android_log_write(ANDROID_LOG_DEBUG, "ClassiCube", tmp);
267}
268#else
269void Platform_Log(const char* msg, int len) {
270 write(STDOUT_FILENO1, msg, len);
271 write(STDOUT_FILENO1, "\n", 1);
272}
273#endif
274
275#define UnixTime_TotalMS(time)((cc_uint64)time.tv_sec * 1000 + 62135596800000ULL + (time.tv_usec
/ 1000))
((cc_uint64)time.tv_sec * 1000 + UNIX_EPOCH62135596800000ULL + (time.tv_usec / 1000))
276TimeMS DateTime_CurrentUTC_MS(void) {
277 struct timeval cur;
278 gettimeofday(&cur, NULL((void*)0));
279 return UnixTime_TotalMS(cur)((cc_uint64)cur.tv_sec * 1000 + 62135596800000ULL + (cur.tv_usec
/ 1000))
;
280}
281
282void DateTime_CurrentLocal(struct DateTime* t) {
283 struct timeval cur;
284 struct tm loc_time;
285 gettimeofday(&cur, NULL((void*)0));
286 localtime_r(&cur.tv_sec, &loc_time);
287
288 t->year = loc_time.tm_year + 1900;
289 t->month = loc_time.tm_mon + 1;
290 t->day = loc_time.tm_mday;
291 t->hour = loc_time.tm_hour;
292 t->minute = loc_time.tm_min;
293 t->second = loc_time.tm_sec;
294}
295
296#define NS_PER_SEC1000000000ULL 1000000000ULL
297#endif
298/* clock_gettime is optional, see http://pubs.opengroup.org/onlinepubs/009696899/functions/clock_getres.html */
299/* "... These functions are part of the Timers option and need not be available on all implementations..." */
300#if defined CC_BUILD_WEB
301cc_uint64 Stopwatch_Measure(void) {
302 /* time is a milliseconds double */
303 return (cc_uint64)(emscripten_get_now() * 1000);
304}
305#elif defined CC_BUILD_OSX
306cc_uint64 Stopwatch_Measure(void) { return mach_absolute_time(); }
307#elif defined CC_BUILD_SOLARIS
308cc_uint64 Stopwatch_Measure(void) { return gethrtime(); }
309#elif defined CC_BUILD_POSIX
310cc_uint64 Stopwatch_Measure(void) {
311 struct timespec t;
312 /* TODO: CLOCK_MONOTONIC_RAW ?? */
313 clock_gettime(CLOCK_MONOTONIC3, &t);
314 return (cc_uint64)t.tv_sec * NS_PER_SEC1000000000ULL + t.tv_nsec;
315}
316#endif
317
318
319/*########################################################################################################################*
320*-----------------------------------------------------Directory/File------------------------------------------------------*
321*#########################################################################################################################*/
322#if defined CC_BUILD_WIN
323int Directory_Exists(const cc_string* path) {
324 TCHAR str[NATIVE_STR_LEN600];
325 DWORD attribs;
326
327 Platform_EncodeString(str, path);
328 attribs = GetFileAttributes(str);
329 return attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY);
330}
331
332cc_result Directory_Create(const cc_string* path) {
333 TCHAR str[NATIVE_STR_LEN600];
334 BOOL success;
335
336 Platform_EncodeString(str, path);
337 success = CreateDirectory(str, NULL((void*)0));
338 return success ? 0 : GetLastError();
339}
340
341int File_Exists(const cc_string* path) {
342 TCHAR str[NATIVE_STR_LEN600];
343 DWORD attribs;
344
345 Platform_EncodeString(str, path);
346 attribs = GetFileAttributes(str);
347 return attribs != INVALID_FILE_ATTRIBUTES && !(attribs & FILE_ATTRIBUTE_DIRECTORY);
348}
349
350cc_result Directory_Enum(const cc_string* dirPath, void* obj, Directory_EnumCallback callback) {
351 cc_string path; char pathBuffer[MAX_PATH + 10];
352 TCHAR str[NATIVE_STR_LEN600];
353 TCHAR* src;
354 WIN32_FIND_DATA entry;
355 HANDLE find;
356 cc_result res;
357 int i;
358
359 /* Need to append \* to search for files in directory */
360 String_InitArray(path, pathBuffer)path.buffer = pathBuffer; path.length = 0; path.capacity = sizeof
(pathBuffer);
;
361 String_Format1(&path, "%s\\*", dirPath);
362 Platform_EncodeString(str, &path);
363
364 find = FindFirstFile(str, &entry);
365 if (find == INVALID_HANDLE_VALUE) return GetLastError();
366
367 do {
368 path.length = 0;
369 String_Format1(&path, "%s/", dirPath);
370
371 /* ignore . and .. entry */
372 src = entry.cFileName;
373 if (src[0] == '.' && src[1] == '\0') continue;
374 if (src[0] == '.' && src[1] == '.' && src[2] == '\0') continue;
375
376 for (i = 0; i < MAX_PATH && src[i]; i++) {
377 /* TODO: UTF16 to codepoint conversion */
378 String_Append(&path, Convert_CodepointToCP437(src[i]));
379 }
380
381 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
382 res = Directory_Enum(&path, obj, callback);
383 if (res) { FindClose(find); return res; }
384 } else {
385 callback(&path, obj);
386 }
387 } while (FindNextFile(find, &entry));
388
389 res = GetLastError(); /* return code from FindNextFile */
390 FindClose(find);
391 return res == ERROR_NO_MORE_FILES ? 0 : GetLastError();
392}
393
394static cc_result File_Do(cc_file* file, const cc_string* path, DWORD access, DWORD createMode) {
395 TCHAR str[NATIVE_STR_LEN600];
396 Platform_EncodeString(str, path);
397 *file = CreateFile(str, access, FILE_SHARE_READ, NULL((void*)0), createMode, 0, NULL((void*)0));
398 return *file != INVALID_HANDLE_VALUE ? 0 : GetLastError();
399}
400
401cc_result File_Open(cc_file* file, const cc_string* path) {
402 return File_Do(file, path, GENERIC_READ, OPEN_EXISTING);
403}
404cc_result File_Create(cc_file* file, const cc_string* path) {
405 return File_Do(file, path, GENERIC_WRITE | GENERIC_READ, CREATE_ALWAYS);
406}
407cc_result File_OpenOrCreate(cc_file* file, const cc_string* path) {
408 return File_Do(file, path, GENERIC_WRITE | GENERIC_READ, OPEN_ALWAYS);
409}
410
411cc_result File_Read(cc_file file, cc_uint8* data, cc_uint32 count, cc_uint32* bytesRead) {
412 BOOL success = ReadFile(file, data, count, bytesRead, NULL((void*)0));
413 return success ? 0 : GetLastError();
414}
415
416cc_result File_Write(cc_file file, const cc_uint8* data, cc_uint32 count, cc_uint32* bytesWrote) {
417 BOOL success = WriteFile(file, data, count, bytesWrote, NULL((void*)0));
418 return success ? 0 : GetLastError();
419}
420
421cc_result File_Close(cc_file file) {
422 return CloseHandle(file) ? 0 : GetLastError();
423}
424
425cc_result File_Seek(cc_file file, int offset, int seekType) {
426 static cc_uint8 modes[3] = { FILE_BEGIN, FILE_CURRENT, FILE_END };
427 DWORD pos = SetFilePointer(file, offset, NULL((void*)0), modes[seekType]);
428 return pos != INVALID_SET_FILE_POINTER ? 0 : GetLastError();
429}
430
431cc_result File_Position(cc_file file, cc_uint32* pos) {
432 *pos = SetFilePointer(file, 0, NULL((void*)0), FILE_CURRENT);
433 return *pos != INVALID_SET_FILE_POINTER ? 0 : GetLastError();
434}
435
436cc_result File_Length(cc_file file, cc_uint32* len) {
437 *len = GetFileSize(file, NULL((void*)0));
438 return *len != INVALID_FILE_SIZE ? 0 : GetLastError();
439}
440#elif defined CC_BUILD_POSIX
441int Directory_Exists(const cc_string* path) {
442 char str[NATIVE_STR_LEN600];
443 struct stat sb;
444 Platform_EncodeString(str, path);
445 return stat(str, &sb) == 0 && S_ISDIR(sb.st_mode)((sb.st_mode & 0170000) == 0040000);
446}
447
448cc_result Directory_Create(const cc_string* path) {
449 char str[NATIVE_STR_LEN600];
450 Platform_EncodeString(str, path);
451 /* read/write/search permissions for owner and group, and with read/search permissions for others. */
452 /* TODO: Is the default mode in all cases */
453 return mkdir(str, S_IRWXU0000700 | S_IRWXG0000070 | S_IROTH0000004 | S_IXOTH0000001) == -1 ? errno(*__errno()) : 0;
454}
455
456int File_Exists(const cc_string* path) {
457 char str[NATIVE_STR_LEN600];
458 struct stat sb;
459 Platform_EncodeString(str, path);
460 return stat(str, &sb) == 0 && S_ISREG(sb.st_mode)((sb.st_mode & 0170000) == 0100000);
461}
462
463cc_result Directory_Enum(const cc_string* dirPath, void* obj, Directory_EnumCallback callback) {
464 cc_string path; char pathBuffer[FILENAME_SIZE260];
465 char str[NATIVE_STR_LEN600];
466 DIR* dirPtr;
467 struct dirent* entry;
468 char* src;
469 int len, res;
470
471 Platform_EncodeString(str, dirPath);
472 dirPtr = opendir(str);
473 if (!dirPtr) return errno(*__errno());
474
475 /* POSIX docs: "When the end of the directory is encountered, a null pointer is returned and errno is not changed." */
476 /* errno is sometimes leftover from previous calls, so always reset it before readdir gets called */
477 errno(*__errno()) = 0;
478 String_InitArray(path, pathBuffer)path.buffer = pathBuffer; path.length = 0; path.capacity = sizeof
(pathBuffer);
;
479
480 while ((entry = readdir(dirPtr))) {
481 path.length = 0;
482 String_Format1(&path, "%s/", dirPath);
483
484 /* ignore . and .. entry */
485 src = entry->d_name;
486 if (src[0] == '.' && src[1] == '\0') continue;
487 if (src[0] == '.' && src[1] == '.' && src[2] == '\0') continue;
488
489 len = String_Length(src);
490 Platform_DecodeString(&path, src, len);
491
492 /* TODO: fallback to stat when this fails */
493 if (entry->d_type == DT_DIR4) {
494 res = Directory_Enum(&path, obj, callback);
495 if (res) { closedir(dirPtr); return res; }
496 } else {
497 callback(&path, obj);
498 }
499 errno(*__errno()) = 0;
500 }
501
502 res = errno(*__errno()); /* return code from readdir */
503 closedir(dirPtr);
504 return res;
505}
506
507static cc_result File_Do(cc_file* file, const cc_string* path, int mode) {
508 char str[NATIVE_STR_LEN600];
509 Platform_EncodeString(str, path);
510 *file = open(str, mode, S_IRUSR0000400 | S_IWUSR0000200 | S_IRGRP0000040 | S_IROTH0000004);
511 return *file == -1 ? errno(*__errno()) : 0;
512}
513
514cc_result File_Open(cc_file* file, const cc_string* path) {
515 return File_Do(file, path, O_RDONLY0x0000);
516}
517cc_result File_Create(cc_file* file, const cc_string* path) {
518 return File_Do(file, path, O_RDWR0x0002 | O_CREAT0x0200 | O_TRUNC0x0400);
519}
520cc_result File_OpenOrCreate(cc_file* file, const cc_string* path) {
521 return File_Do(file, path, O_RDWR0x0002 | O_CREAT0x0200);
522}
523
524cc_result File_Read(cc_file file, cc_uint8* data, cc_uint32 count, cc_uint32* bytesRead) {
525 *bytesRead = read(file, data, count);
526 return *bytesRead == -1 ? errno(*__errno()) : 0;
527}
528
529cc_result File_Write(cc_file file, const cc_uint8* data, cc_uint32 count, cc_uint32* bytesWrote) {
530 *bytesWrote = write(file, data, count);
531 return *bytesWrote == -1 ? errno(*__errno()) : 0;
532}
533
534cc_result File_Close(cc_file file) {
535#ifndef CC_BUILD_WEB
536 return close(file) == -1 ? errno(*__errno()) : 0;
537#else
538 int ret = close(file) == -1 ? errno(*__errno()) : 0;
539 EM_ASM( FS.syncfs(false0, function(err) { if (err) console.log(err); }); );
540 return ret;
541#endif
542}
543
544cc_result File_Seek(cc_file file, int offset, int seekType) {
545 static cc_uint8 modes[3] = { SEEK_SET0, SEEK_CUR1, SEEK_END2 };
546 return lseek(file, offset, modes[seekType]) == -1 ? errno(*__errno()) : 0;
547}
548
549cc_result File_Position(cc_file file, cc_uint32* pos) {
550 *pos = lseek(file, 0, SEEK_CUR1);
551 return *pos == -1 ? errno(*__errno()) : 0;
552}
553
554cc_result File_Length(cc_file file, cc_uint32* len) {
555 struct stat st;
556 if (fstat(file, &st) == -1) { *len = -1; return errno(*__errno()); }
557 *len = st.st_size; return 0;
558}
559#endif
560
561
562/*########################################################################################################################*
563*--------------------------------------------------------Threading--------------------------------------------------------*
564*#########################################################################################################################*/
565#if defined CC_BUILD_WIN
566void Thread_Sleep(cc_uint32 milliseconds) { Sleep(milliseconds); }
567static DWORD WINAPI ExecThread(void* param) {
568 Thread_StartFunc func = (Thread_StartFunc)param;
569 func();
570 return 0;
571}
572
573void* Thread_Start(Thread_StartFunc func, cc_bool detach) {
574 DWORD threadID;
575 void* handle = CreateThread(NULL((void*)0), 0, ExecThread, (void*)func, 0, &threadID);
576 if (!handle) {
577 Logger_Abort2(GetLastError(), "Creating thread");
578 }
579
580 if (detach) Thread_Detach(handle);
581 return handle;
582}
583
584void Thread_Detach(void* handle) {
585 if (!CloseHandle((HANDLE)handle)) {
586 Logger_Abort2(GetLastError(), "Freeing thread handle");
587 }
588}
589
590void Thread_Join(void* handle) {
591 WaitForSingleObject((HANDLE)handle, INFINITE);
592 Thread_Detach(handle);
593}
594
595void* Mutex_Create(void) {
596 CRITICAL_SECTION* ptr = (CRITICAL_SECTION*)Mem_Alloc(1, sizeof(CRITICAL_SECTION), "mutex");
597 InitializeCriticalSection(ptr);
598 return ptr;
599}
600
601void Mutex_Free(void* handle) {
602 DeleteCriticalSection((CRITICAL_SECTION*)handle);
603 Mem_Free(handle);
604}
605void Mutex_Lock(void* handle) { EnterCriticalSection((CRITICAL_SECTION*)handle); }
606void Mutex_Unlock(void* handle) { LeaveCriticalSection((CRITICAL_SECTION*)handle); }
607
608void* Waitable_Create(void) {
609 void* handle = CreateEvent(NULL((void*)0), false0, false0, NULL((void*)0));
610 if (!handle) {
611 Logger_Abort2(GetLastError(), "Creating waitable");
612 }
613 return handle;
614}
615
616void Waitable_Free(void* handle) {
617 if (!CloseHandle((HANDLE)handle)) {
618 Logger_Abort2(GetLastError(), "Freeing waitable");
619 }
620}
621
622void Waitable_Signal(void* handle) { SetEvent((HANDLE)handle); }
623void Waitable_Wait(void* handle) {
624 WaitForSingleObject((HANDLE)handle, INFINITE);
625}
626
627void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) {
628 WaitForSingleObject((HANDLE)handle, milliseconds);
629}
630#elif defined CC_BUILD_WEB
631/* No real threading support with emscripten backend */
632void Thread_Sleep(cc_uint32 milliseconds) { }
633void* Thread_Start(Thread_StartFunc func, cc_bool detach) { func(); return NULL((void*)0); }
634void Thread_Detach(void* handle) { }
635void Thread_Join(void* handle) { }
636
637void* Mutex_Create(void) { return NULL((void*)0); }
638void Mutex_Free(void* handle) { }
639void Mutex_Lock(void* handle) { }
640void Mutex_Unlock(void* handle) { }
641
642void* Waitable_Create(void) { return NULL((void*)0); }
643void Waitable_Free(void* handle) { }
644void Waitable_Signal(void* handle) { }
645void Waitable_Wait(void* handle) { }
646void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) { }
647#elif defined CC_BUILD_POSIX
648void Thread_Sleep(cc_uint32 milliseconds) { usleep(milliseconds * 1000); }
649
650#ifdef CC_BUILD_ANDROID
651/* All threads using JNI must detach BEFORE they exit */
652/* (see https://developer.android.com/training/articles/perf-jni */
653static void* ExecThread(void* param) {
654 JNIEnv* env;
655 JavaGetCurrentEnv(env);
656
657 ((Thread_StartFunc)param)();
658 (*VM_Ptr)->DetachCurrentThread(VM_Ptr);
659 return NULL((void*)0);
660}
661#else
662static void* ExecThread(void* param) {
663 ((Thread_StartFunc)param)();
664 return NULL((void*)0);
665}
666#endif
667
668void* Thread_Start(Thread_StartFunc func, cc_bool detach) {
669 pthread_t* ptr = (pthread_t*)Mem_Alloc(1, sizeof(pthread_t), "thread");
1
Calling 'Mem_Alloc'
8
Returned allocated memory
670 int res = pthread_create(ptr, NULL((void*)0), ExecThread, (void*)func);
671 if (res) Logger_Abort2(res, "Creating thread");
9
Assuming 'res' is 0
10
Taking false branch
672
673 if (detach) Thread_Detach(ptr);
11
Assuming 'detach' is not equal to 0
12
Taking true branch
13
Calling 'Thread_Detach'
20
Returning; memory was released via 1st parameter
674 return ptr;
21
Use of memory after it is freed
675}
676
677void Thread_Detach(void* handle) {
678 pthread_t* ptr = (pthread_t*)handle;
679 int res = pthread_detach(*ptr);
680 if (res) Logger_Abort2(res, "Detaching thread");
14
Assuming 'res' is 0
15
Taking false branch
681 Mem_Free(ptr);
16
Calling 'Mem_Free'
19
Returning; memory was released via 1st parameter
682}
683
684void Thread_Join(void* handle) {
685 pthread_t* ptr = (pthread_t*)handle;
686 int res = pthread_join(*ptr, NULL((void*)0));
687 if (res) Logger_Abort2(res, "Joining thread");
688 Mem_Free(ptr);
689}
690
691void* Mutex_Create(void) {
692 pthread_mutex_t* ptr = (pthread_mutex_t*)Mem_Alloc(1, sizeof(pthread_mutex_t), "mutex");
693 int res = pthread_mutex_init(ptr, NULL((void*)0));
694 if (res) Logger_Abort2(res, "Creating mutex");
695 return ptr;
696}
697
698void Mutex_Free(void* handle) {
699 int res = pthread_mutex_destroy((pthread_mutex_t*)handle);
700 if (res) Logger_Abort2(res, "Destroying mutex");
701 Mem_Free(handle);
702}
703
704void Mutex_Lock(void* handle) {
705 int res = pthread_mutex_lock((pthread_mutex_t*)handle);
706 if (res) Logger_Abort2(res, "Locking mutex");
707}
708
709void Mutex_Unlock(void* handle) {
710 int res = pthread_mutex_unlock((pthread_mutex_t*)handle);
711 if (res) Logger_Abort2(res, "Unlocking mutex");
712}
713
714struct WaitData {
715 pthread_cond_t cond;
716 pthread_mutex_t mutex;
717 int signalled;
718};
719
720void* Waitable_Create(void) {
721 struct WaitData* ptr = (struct WaitData*)Mem_Alloc(1, sizeof(struct WaitData), "waitable");
722 int res;
723
724 res = pthread_cond_init(&ptr->cond, NULL((void*)0));
725 if (res) Logger_Abort2(res, "Creating waitable");
726 res = pthread_mutex_init(&ptr->mutex, NULL((void*)0));
727 if (res) Logger_Abort2(res, "Creating waitable mutex");
728
729 ptr->signalled = false0;
730 return ptr;
731}
732
733void Waitable_Free(void* handle) {
734 struct WaitData* ptr = (struct WaitData*)handle;
735 int res;
736
737 res = pthread_cond_destroy(&ptr->cond);
738 if (res) Logger_Abort2(res, "Destroying waitable");
739 res = pthread_mutex_destroy(&ptr->mutex);
740 if (res) Logger_Abort2(res, "Destroying waitable mutex");
741 Mem_Free(handle);
742}
743
744void Waitable_Signal(void* handle) {
745 struct WaitData* ptr = (struct WaitData*)handle;
746 int res;
747
748 Mutex_Lock(&ptr->mutex);
749 ptr->signalled = true1;
750 Mutex_Unlock(&ptr->mutex);
751
752 res = pthread_cond_signal(&ptr->cond);
753 if (res) Logger_Abort2(res, "Signalling event");
754}
755
756void Waitable_Wait(void* handle) {
757 struct WaitData* ptr = (struct WaitData*)handle;
758 int res;
759
760 Mutex_Lock(&ptr->mutex);
761 if (!ptr->signalled) {
762 res = pthread_cond_wait(&ptr->cond, &ptr->mutex);
763 if (res) Logger_Abort2(res, "Waitable wait");
764 }
765 ptr->signalled = false0;
766 Mutex_Unlock(&ptr->mutex);
767}
768
769void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) {
770 struct WaitData* ptr = (struct WaitData*)handle;
771 struct timeval tv;
772 struct timespec ts;
773 int res;
774 gettimeofday(&tv, NULL((void*)0));
775
776 /* absolute time for some silly reason */
777 ts.tv_sec = tv.tv_sec + milliseconds / 1000;
778 ts.tv_nsec = 1000 * (tv.tv_usec + 1000 * (milliseconds % 1000));
779 ts.tv_sec += ts.tv_nsec / NS_PER_SEC1000000000ULL;
780 ts.tv_nsec %= NS_PER_SEC1000000000ULL;
781
782 Mutex_Lock(&ptr->mutex);
783 if (!ptr->signalled) {
784 res = pthread_cond_timedwait(&ptr->cond, &ptr->mutex, &ts);
785 if (res && res != ETIMEDOUT60) Logger_Abort2(res, "Waitable wait for");
786 }
787 ptr->signalled = false0;
788 Mutex_Unlock(&ptr->mutex);
789}
790#endif
791
792
793/*########################################################################################################################*
794*--------------------------------------------------------Font/Text--------------------------------------------------------*
795*#########################################################################################################################*/
796#ifdef CC_BUILD_WEB
797void Platform_LoadSysFonts(void) { }
798#else
799static void FontDirCallback(const cc_string* path, void* obj) {
800 static const cc_string fonExt = String_FromConst(".fon"){ ".fon", (sizeof(".fon") - 1), (sizeof(".fon") - 1)};
801 /* Completely skip windows .FON files */
802 if (String_CaselessEnds(path, &fonExt)) return;
803 SysFonts_Register(path);
804}
805
806void Platform_LoadSysFonts(void) {
807 int i;
808#if defined CC_BUILD_WIN
809 char winFolder[FILENAME_SIZE260];
810 TCHAR winTmp[FILENAME_SIZE260];
811 UINT winLen;
812 /* System folder path may not be C:/Windows */
813 cc_string dirs[1];
814 String_InitArray(dirs[0], winFolder)dirs[0].buffer = winFolder; dirs[0].length = 0; dirs[0].capacity
= sizeof(winFolder);
;
815
816 winLen = GetWindowsDirectory(winTmp, FILENAME_SIZE260);
817 if (winLen) {
818 Platform_DecodeString(&dirs[0], winTmp, winLen);
819 } else {
820 String_AppendConst(&dirs[0], "C:/Windows");
821 }
822 String_AppendConst(&dirs[0], "/fonts");
823
824#elif defined CC_BUILD_ANDROID
825 static const cc_string dirs[3] = {
826 String_FromConst("/system/fonts"){ "/system/fonts", (sizeof("/system/fonts") - 1), (sizeof("/system/fonts"
) - 1)}
,
827 String_FromConst("/system/font"){ "/system/font", (sizeof("/system/font") - 1), (sizeof("/system/font"
) - 1)}
,
828 String_FromConst("/data/fonts"){ "/data/fonts", (sizeof("/data/fonts") - 1), (sizeof("/data/fonts"
) - 1)}
,
829 };
830#elif defined CC_BUILD_NETBSD
831 static const cc_string dirs[3] = {
832 String_FromConst("/usr/X11R7/lib/X11/fonts"){ "/usr/X11R7/lib/X11/fonts", (sizeof("/usr/X11R7/lib/X11/fonts"
) - 1), (sizeof("/usr/X11R7/lib/X11/fonts") - 1)}
,
833 String_FromConst("/usr/pkg/lib/X11/fonts"){ "/usr/pkg/lib/X11/fonts", (sizeof("/usr/pkg/lib/X11/fonts")
- 1), (sizeof("/usr/pkg/lib/X11/fonts") - 1)}
,
834 String_FromConst("/usr/pkg/share/fonts"){ "/usr/pkg/share/fonts", (sizeof("/usr/pkg/share/fonts") - 1
), (sizeof("/usr/pkg/share/fonts") - 1)}
835 };
836#elif defined CC_BUILD_HAIKU
837 static const cc_string dirs[1] = {
838 String_FromConst("/system/data/fonts"){ "/system/data/fonts", (sizeof("/system/data/fonts") - 1), (
sizeof("/system/data/fonts") - 1)}
839 };
840#elif defined CC_BUILD_OSX
841 static const cc_string dirs[2] = {
842 String_FromConst("/System/Library/Fonts"){ "/System/Library/Fonts", (sizeof("/System/Library/Fonts") -
1), (sizeof("/System/Library/Fonts") - 1)}
,
843 String_FromConst("/Library/Fonts"){ "/Library/Fonts", (sizeof("/Library/Fonts") - 1), (sizeof("/Library/Fonts"
) - 1)}
844 };
845#elif defined CC_BUILD_POSIX
846 static const cc_string dirs[2] = {
847 String_FromConst("/usr/share/fonts"){ "/usr/share/fonts", (sizeof("/usr/share/fonts") - 1), (sizeof
("/usr/share/fonts") - 1)}
,
848 String_FromConst("/usr/local/share/fonts"){ "/usr/local/share/fonts", (sizeof("/usr/local/share/fonts")
- 1), (sizeof("/usr/local/share/fonts") - 1)}
849 };
850#endif
851 for (i = 0; i < Array_Elems(dirs)(sizeof(dirs) / sizeof(dirs[0])); i++) {
852 Directory_Enum(&dirs[i], NULL((void*)0), FontDirCallback);
853 }
854}
855#endif
856
857
858/*########################################################################################################################*
859*---------------------------------------------------------Socket----------------------------------------------------------*
860*#########################################################################################################################*/
861cc_result Socket_Create(cc_socket* s) {
862 *s = socket(AF_INET2, SOCK_STREAM1, IPPROTO_TCP6);
863 return *s == -1 ? Socket__Error()(*__errno()) : 0;
864}
865
866static cc_result Socket_ioctl(cc_socket s, cc_uint32 cmd, int* data) {
867#if defined CC_BUILD_WIN
868 return ioctlsocket(s, cmd, data);
869#else
870 return ioctl(s, cmd, data);
871#endif
872}
873
874cc_result Socket_Available(cc_socket s, cc_uint32* available) {
875 return Socket_ioctl(s, FIONREAD((unsigned long)0x40000000 | ((sizeof(int) & 0x1fff) <<
16) | ((('f')) << 8) | ((127)))
, available);
876}
877cc_result Socket_SetBlocking(cc_socket s, cc_bool blocking) {
878#if defined CC_BUILD_WEB
879 return ERR_NOT_SUPPORTED; /* sockets always async */
880#else
881 int blocking_raw = blocking ? 0 : -1;
882 return Socket_ioctl(s, FIONBIO((unsigned long)0x80000000 | ((sizeof(int) & 0x1fff) <<
16) | ((('f')) << 8) | ((126)))
, &blocking_raw);
883#endif
884}
885
886cc_result Socket_GetError(cc_socket s, cc_result* result) {
887 socklen_t resultSize = sizeof(cc_result);
888 return getsockopt(s, SOL_SOCKET0xffff, SO_ERROR0x1007, result, &resultSize);
889}
890
891cc_result Socket_Connect(cc_socket s, const cc_string* ip, int port) {
892 struct sockaddr addr;
893 cc_result res;
894
895 addr.sa_family = AF_INET2;
896 Stream_SetU16_BE( (cc_uint8*)&addr.sa_data[0], port);
897 Utils_ParseIP(ip, (cc_uint8*)&addr.sa_data[2]);
898
899 res = connect(s, &addr, sizeof(addr));
900 return res == -1 ? Socket__Error()(*__errno()) : 0;
901}
902
903cc_result Socket_Read(cc_socket s, cc_uint8* data, cc_uint32 count, cc_uint32* modified) {
904#ifdef CC_BUILD_WEB
905 /* recv only reads one WebSocket frame at most, hence call it multiple times */
906 int recvCount = 0, pending;
907 *modified = 0;
908
909 while (count && !Socket_Available(s, &pending) && pending) {
910 recvCount = recv(s, data, count, 0);
911 if (recvCount == -1) return Socket__Error()(*__errno());
912
913 *modified += recvCount;
914 data += recvCount; count -= recvCount;
915 }
916 return 0;
917#else
918 int recvCount = recv(s, data, count, 0);
919 if (recvCount != -1) { *modified = recvCount; return 0; }
920 *modified = 0; return Socket__Error()(*__errno());
921#endif
922}
923
924cc_result Socket_Write(cc_socket s, const cc_uint8* data, cc_uint32 count, cc_uint32* modified) {
925 int sentCount = send(s, data, count, 0);
926 if (sentCount != -1) { *modified = sentCount; return 0; }
927 *modified = 0; return Socket__Error()(*__errno());
928}
929
930cc_result Socket_Close(cc_socket s) {
931 cc_result res = 0;
932 cc_result res1, res2;
933
934#if defined CC_BUILD_WEB
935 res1 = 0;
936#elif defined CC_BUILD_WIN
937 res1 = shutdown(s, SD_BOTH);
938#else
939 res1 = shutdown(s, SHUT_RDWR2);
940#endif
941 if (res1 == -1) res = Socket__Error()(*__errno());
942
943#if defined CC_BUILD_WIN
944 res2 = closesocket(s);
945#else
946 res2 = close(s);
947#endif
948 if (res2 == -1) res = Socket__Error()(*__errno());
949 return res;
950}
951
952/* Alas, a simple cross-platform select() is not good enough */
953#if defined CC_BUILD_WIN
954cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) {
955 fd_set set;
956 struct timeval time = { 0 };
957 int selectCount;
958
959 set.fd_count = 1;
960 set.fd_array[0] = s;
961
962 if (mode == SOCKET_POLL_READ) {
963 selectCount = select(1, &set, NULL((void*)0), NULL((void*)0), &time);
964 } else {
965 selectCount = select(1, NULL((void*)0), &set, NULL((void*)0), &time);
966 }
967
968 if (selectCount == -1) { *success = false0; return Socket__Error()(*__errno()); }
969
970 *success = set.fd_count != 0; return 0;
971}
972#elif defined CC_BUILD_OSX
973/* poll is broken on old OSX apparently https://daniel.haxx.se/docs/poll-vs-select.html */
974cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) {
975 fd_set set;
976 struct timeval time = { 0 };
977 int selectCount;
978
979 FD_ZERO(&set)do { fd_set *_p = (&set); __size_t _n = (((1024) + ((((unsigned
)(sizeof(__fd_mask) * 8))) - 1)) / (((unsigned)(sizeof(__fd_mask
) * 8)))); while (_n > 0) _p->fds_bits[--_n] = 0; } while
(0)
;
980 FD_SET(s, &set)__fd_set((s), (&set));
981
982 if (mode == SOCKET_POLL_READ) {
983 selectCount = select(s + 1, &set, NULL((void*)0), NULL((void*)0), &time);
984 } else {
985 selectCount = select(s + 1, NULL((void*)0), &set, NULL((void*)0), &time);
986 }
987
988 if (selectCount == -1) { *success = false0; return Socket__Error()(*__errno()); }
989 *success = FD_ISSET(s, &set)__fd_isset((s), (&set)) != 0; return 0;
990}
991#else
992#include <poll.h>
993cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) {
994 struct pollfd pfd;
995 int flags;
996
997 pfd.fd = s;
998 pfd.events = mode == SOCKET_POLL_READ ? POLLIN0x0001 : POLLOUT0x0004;
999 if (poll(&pfd, 1, 0) == -1) { *success = false0; return Socket__Error()(*__errno()); }
1000
1001 /* to match select, closed socket still counts as readable */
1002 flags = mode == SOCKET_POLL_READ ? (POLLIN0x0001 | POLLHUP0x0010) : POLLOUT0x0004;
1003 *success = (pfd.revents & flags) != 0;
1004 return 0;
1005}
1006#endif
1007
1008
1009/*########################################################################################################################*
1010*-----------------------------------------------------Process/Module------------------------------------------------------*
1011*#########################################################################################################################*/
1012#if defined CC_BUILD_WIN
1013static cc_result Process_RawStart(const TCHAR* path, TCHAR* args) {
1014 STARTUPINFO si = { 0 };
1015 PROCESS_INFORMATION pi = { 0 };
1016 BOOL ok;
1017
1018 si.cb = sizeof(STARTUPINFO);
1019 ok = CreateProcess(path, args, NULL((void*)0), NULL((void*)0), false0, 0, NULL((void*)0), NULL((void*)0), &si, &pi);
1020 if (!ok) return GetLastError();
1021
1022 /* Don't leak memory for proess return code */
1023 CloseHandle(pi.hProcess);
1024 CloseHandle(pi.hThread);
1025 return 0;
1026}
1027
1028static cc_result Process_RawGetExePath(TCHAR* path, int* len) {
1029 *len = GetModuleFileName(NULL((void*)0), path, NATIVE_STR_LEN600);
1030 return *len ? 0 : GetLastError();
1031}
1032
1033cc_result Process_StartGame(const cc_string* args) {
1034 cc_string argv; char argvBuffer[NATIVE_STR_LEN600];
1035 TCHAR raw[NATIVE_STR_LEN600], path[NATIVE_STR_LEN600 + 1];
1036 int len;
1037
1038 cc_result res = Process_RawGetExePath(path, &len);
1039 if (res) return res;
1040 path[len] = '\0';
1041
1042 String_InitArray(argv, argvBuffer)argv.buffer = argvBuffer; argv.length = 0; argv.capacity = sizeof
(argvBuffer);
;
1043 String_Format1(&argv, "ClassiCube.exe %s", args);
1044 Platform_EncodeString(raw, &argv);
1045 return Process_RawStart(path, raw);
1046}
1047void Process_Exit(cc_result code) { ExitProcess(code); }
1048
1049void Process_StartOpen(const cc_string* args) {
1050 TCHAR str[NATIVE_STR_LEN600];
1051 Platform_EncodeString(str, args);
1052 ShellExecute(NULL((void*)0), NULL((void*)0), str, NULL((void*)0), NULL((void*)0), SW_SHOWNORMAL);
1053}
1054#elif defined CC_BUILD_WEB
1055cc_result Process_StartGame(const cc_string* args) { return ERR_NOT_SUPPORTED; }
1056void Process_Exit(cc_result code) { exit(code); }
1057
1058void Process_StartOpen(const cc_string* args) {
1059 char str[NATIVE_STR_LEN600];
1060 Platform_EncodeString(str, args);
1061 EM_ASM_({ window.open(UTF8ToString($0)); }, str);
1062}
1063#elif defined CC_BUILD_ANDROID
1064static char gameArgsBuffer[512];
1065static cc_string gameArgs = String_FromArray(gameArgsBuffer){ gameArgsBuffer, 0, sizeof(gameArgsBuffer)};
1066
1067cc_result Process_StartGame(const cc_string* args) {
1068 String_Copy(&gameArgs, args);
1069 return 0; /* TODO: Is there a clean way of handling an error */
1070}
1071void Process_Exit(cc_result code) { exit(code); }
1072
1073void Process_StartOpen(const cc_string* args) {
1074 JavaCall_String_Void("startOpen", args);
1075}
1076#elif defined CC_BUILD_POSIX
1077static cc_result Process_RawStart(const char* path, char** argv) {
1078 pid_t pid = fork();
1079 if (pid == -1) return errno(*__errno());
1080
1081 if (pid == 0) {
1082 /* Executed in child process */
1083 execvp(path, argv);
1084 _exit(127); /* "command not found" */
1085 } else {
1086 /* Executed in parent process */
1087 /* We do nothing here.. */
1088 return 0;
1089 }
1090}
1091
1092static cc_result Process_RawGetExePath(char* path, int* len);
1093
1094cc_result Process_StartGame(const cc_string* args) {
1095 char path[NATIVE_STR_LEN600], raw[NATIVE_STR_LEN600];
1096 int i, j, len = 0;
1097 char* argv[15];
1098
1099 cc_result res = Process_RawGetExePath(path, &len);
1100 if (res) return res;
1101 path[len] = '\0';
1102
1103 Platform_EncodeString(raw, args);
1104 argv[0] = path; argv[1] = raw;
1105
1106 /* need to null-terminate multiple arguments */
1107 for (i = 0, j = 2; raw[i] && i < Array_Elems(raw)(sizeof(raw) / sizeof(raw[0])); i++) {
1108 if (raw[i] != ' ') continue;
1109
1110 /* null terminate previous argument */
1111 raw[i] = '\0';
1112 argv[j++] = &raw[i + 1];
1113 }
1114
1115 if (defaultDirectory) { argv[j++] = defaultDirectory; }
1116 argv[j] = NULL((void*)0);
1117 return Process_RawStart(path, argv);
1118}
1119
1120void Process_Exit(cc_result code) { exit(code); }
1121
1122/* Opening browser/starting shell is not really standardised */
1123#if defined CC_BUILD_OSX
1124void Process_StartOpen(const cc_string* args) {
1125 UInt8 str[NATIVE_STR_LEN600];
1126 CFURLRef urlCF;
1127 int len;
1128
1129 len = Platform_EncodeString(str, args);
1130 urlCF = CFURLCreateWithBytes(kCFAllocatorDefault, str, len, kCFStringEncodingUTF8, NULL((void*)0));
1131 LSOpenCFURLRef(urlCF, NULL((void*)0));
1132 CFRelease(urlCF);
1133}
1134#elif defined CC_BUILD_HAIKU
1135void Process_StartOpen(const cc_string* args) {
1136 char str[NATIVE_STR_LEN600];
1137 char* cmd[3];
1138 Platform_EncodeString(str, args);
1139
1140 cmd[0] = "open"; cmd[1] = str; cmd[2] = NULL((void*)0);
1141 Process_RawStart("open", cmd);
1142}
1143#else
1144void Process_StartOpen(const cc_string* args) {
1145 char str[NATIVE_STR_LEN600];
1146 char* cmd[3];
1147 Platform_EncodeString(str, args);
1148
1149 /* TODO: Can xdg-open be used on original Solaris, or is it just an OpenIndiana thing */
1150 cmd[0] = "xdg-open"; cmd[1] = str; cmd[2] = NULL((void*)0);
1151 Process_RawStart("xdg-open", cmd);
1152}
1153#endif
1154
1155/* Retrieving exe path is completely OS dependant */
1156#if defined CC_BUILD_OSX
1157static cc_result Process_RawGetExePath(char* path, int* len) {
1158 Mem_Set(path, '\0', NATIVE_STR_LEN600);
1159 cc_uint32 size = NATIVE_STR_LEN600;
1160 if (_NSGetExecutablePath(path, &size)) return ERR_INVALID_ARGUMENT;
1161
1162 /* despite what you'd assume, size is NOT changed to length of path */
1163 *len = String_CalcLen(path, NATIVE_STR_LEN600);
1164 return 0;
1165}
1166#elif defined CC_BUILD_LINUX
1167static cc_result Process_RawGetExePath(char* path, int* len) {
1168 *len = readlink("/proc/self/exe", path, NATIVE_STR_LEN600);
1169 return *len == -1 ? errno(*__errno()) : 0;
1170}
1171#elif defined CC_BUILD_FREEBSD
1172static cc_result Process_RawGetExePath(char* path, int* len) {
1173 static int mib[4] = { CTL_KERN1, KERN_PROC66, KERN_PROC_PATHNAME, -1 };
1174 size_t size = NATIVE_STR_LEN600;
1175
1176 if (sysctl(mib, 4, path, &size, NULL((void*)0), 0) == -1) return errno(*__errno());
1177 *len = String_CalcLen(path, NATIVE_STR_LEN600);
1178 return 0;
1179}
1180#elif defined CC_BUILD_OPENBSD
1181static cc_result Process_RawGetExePath(char* path, int* len) {
1182 static int mib[4] = { CTL_KERN1, KERN_PROC_ARGS55, 0, KERN_PROC_ARGV1 };
1183 char tmp[NATIVE_STR_LEN600];
1184 size_t size;
1185 char* argv[100];
1186 char* str;
1187
1188 /* NOTE: OpenBSD doesn't seem to let us get executable's location, so fallback to argv[0] */
1189 /* See OpenBSD sysctl manpage for why argv array is so large */
1190 /*... The buffer pointed to by oldp is filled with an array of char pointers followed by the strings themselves... */
1191 mib[2] = getpid();
1192 size = 100 * sizeof(char*);
1193 if (sysctl(mib, 4, argv, &size, NULL((void*)0), 0) == -1) return errno(*__errno());
1194
1195 str = argv[0];
1196 if (str[0] != '/') {
1197 /* relative path */
1198 if (!realpath(str, tmp)) return errno(*__errno());
1199 str = tmp;
1200 }
1201
1202 *len = String_CalcLen(str, NATIVE_STR_LEN600);
1203 Mem_Copy(path, str, *len);
1204 return 0;
1205}
1206#elif defined CC_BUILD_NETBSD
1207static cc_result Process_RawGetExePath(char* path, int* len) {
1208 static int mib[4] = { CTL_KERN1, KERN_PROC_ARGS55, -1, KERN_PROC_PATHNAME };
1209 size_t size = NATIVE_STR_LEN600;
1210
1211 if (sysctl(mib, 4, path, &size, NULL((void*)0), 0) == -1) return errno(*__errno());
1212 *len = String_CalcLen(path, NATIVE_STR_LEN600);
1213 return 0;
1214}
1215#elif defined CC_BUILD_SOLARIS
1216static cc_result Process_RawGetExePath(char* path, int* len) {
1217 *len = readlink("/proc/self/path/a.out", path, NATIVE_STR_LEN600);
1218 return *len == -1 ? errno(*__errno()) : 0;
1219}
1220#elif defined CC_BUILD_HAIKU
1221static cc_result Process_RawGetExePath(char* path, int* len) {
1222 image_info info;
1223 int32 cookie = 0;
1224
1225 cc_result res = get_next_image_info(B_CURRENT_TEAM, &cookie, &info);
1226 if (res != B_OK) return res;
1227
1228 *len = String_CalcLen(info.name, NATIVE_STR_LEN600);
1229 Mem_Copy(path, info.name, *len);
1230 return 0;
1231}
1232#endif
1233#endif /* CC_BUILD_POSIX */
1234
1235
1236/*########################################################################################################################*
1237*--------------------------------------------------------Updater----------------------------------------------------------*
1238*#########################################################################################################################*/
1239#if defined CC_BUILD_WIN
1240#define UPDATE_TMP TEXT("CC_prev.exe")
1241#define UPDATE_SRC TEXT(UPDATE_FILE"ClassiCube.update")
1242
1243#if _WIN64
1244const char* const Updater_D3D9 = "ClassiCube.64.exe";
1245const char* const Updater_OGL = "ClassiCube.64-opengl.exe";
1246#else
1247const char* const Updater_D3D9 = "ClassiCube.exe";
1248const char* const Updater_OGL = "ClassiCube.opengl.exe";
1249#endif
1250
1251cc_bool Updater_Clean(void) {
1252 return DeleteFile(UPDATE_TMP) || GetLastError() == ERROR_FILE_NOT_FOUND;
1253}
1254
1255cc_result Updater_Start(const char** action) {
1256 TCHAR path[NATIVE_STR_LEN600 + 1];
1257 TCHAR args[2] = { 'a', '\0' }; /* don't actually care about arguments */
1258 cc_result res;
1259 int len = 0;
1260
1261 *action = "Getting executable path";
1262 if ((res = Process_RawGetExePath(path, &len))) return res;
1263 path[len] = '\0';
1264
1265 *action = "Moving executable to CC_prev.exe";
1266 if (!MoveFileEx(path, UPDATE_TMP, MOVEFILE_REPLACE_EXISTING)) return GetLastError();
1267 *action = "Replacing executable";
1268 if (!MoveFileEx(UPDATE_SRC, path, MOVEFILE_REPLACE_EXISTING)) return GetLastError();
1269
1270 *action = "Restarting game";
1271 return Process_RawStart(path, args);
1272}
1273
1274cc_result Updater_GetBuildTime(cc_uint64* timestamp) {
1275 TCHAR path[NATIVE_STR_LEN600 + 1];
1276 cc_file file;
1277 FILETIME ft;
1278 cc_uint64 raw;
1279 int len = 0;
1280
1281 cc_result res = Process_RawGetExePath(path, &len);
1282 if (res) return res;
1283 path[len] = '\0';
1284
1285 file = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL((void*)0), OPEN_EXISTING, 0, NULL((void*)0));
1286 if (file == INVALID_HANDLE_VALUE) return GetLastError();
1287
1288 if (GetFileTime(file, NULL((void*)0), NULL((void*)0), &ft)) {
1289 raw = ft.dwLowDateTime | ((cc_uint64)ft.dwHighDateTime << 32);
1290 *timestamp = FileTime_UnixTime(raw);
1291 } else {
1292 res = GetLastError();
1293 }
1294
1295 File_Close(file);
1296 return res;
1297}
1298
1299/* Don't need special execute permission on windows */
1300cc_result Updater_MarkExecutable(void) { return 0; }
1301cc_result Updater_SetNewBuildTime(cc_uint64 timestamp) {
1302 static const cc_string path = String_FromConst(UPDATE_FILE){ "ClassiCube.update", (sizeof("ClassiCube.update") - 1), (sizeof
("ClassiCube.update") - 1)}
;
1303 cc_file file;
1304 FILETIME ft;
1305 cc_uint64 raw;
1306 cc_result res = File_OpenOrCreate(&file, &path);
1307 if (res) return res;
1308
1309 raw = 10000000 * (timestamp + FILETIME_UNIX_EPOCH);
1310 ft.dwLowDateTime = (cc_uint32)raw;
1311 ft.dwHighDateTime = (cc_uint32)(raw >> 32);
1312
1313 if (!SetFileTime(file, NULL((void*)0), NULL((void*)0), &ft)) res = GetLastError();
1314 File_Close(file);
1315 return res;
1316}
1317
1318#elif defined CC_BUILD_WEB || defined CC_BUILD_ANDROID
1319const char* const Updater_D3D9 = NULL((void*)0);
1320const char* const Updater_OGL = NULL((void*)0);
1321
1322#if defined CC_BUILD_WEB
1323cc_result Updater_GetBuildTime(cc_uint64* t) { return ERR_NOT_SUPPORTED; }
1324#else
1325cc_result Updater_GetBuildTime(cc_uint64* t) {
1326 JNIEnv* env;
1327 JavaGetCurrentEnv(env);
1328
1329 /* https://developer.android.com/reference/java/io/File#lastModified() */
1330 /* lastModified is returned in milliseconds */
1331 *t = JavaCallLong(env, "getApkUpdateTime", "()J", NULL((void*)0)) / 1000;
1332 return 0;
1333}
1334#endif
1335
1336cc_bool Updater_Clean(void) { return true1; }
1337cc_result Updater_Start(const char** action) { *action = "Updating game"; return ERR_NOT_SUPPORTED; }
1338cc_result Updater_MarkExecutable(void) { return 0; }
1339cc_result Updater_SetNewBuildTime(cc_uint64 t) { return ERR_NOT_SUPPORTED; }
1340#elif defined CC_BUILD_POSIX
1341cc_bool Updater_Clean(void) { return true1; }
1342
1343const char* const Updater_D3D9 = NULL((void*)0);
1344#if defined CC_BUILD_LINUX
1345#if __x86_64__1
1346const char* const Updater_OGL = "ClassiCube";
1347#elif __i386__
1348const char* const Updater_OGL = "ClassiCube.32";
1349#elif CC_BUILD_RPI
1350const char* const Updater_OGL = "ClassiCube.rpi";
1351#else
1352const char* const Updater_OGL = NULL((void*)0);
1353#endif
1354#elif defined CC_BUILD_OSX
1355#if __x86_64__1
1356const char* const Updater_OGL = "ClassiCube.64.osx";
1357#elif __i386__
1358const char* const Updater_OGL = "ClassiCube.osx";
1359#else
1360const char* const Updater_OGL = NULL((void*)0);
1361#endif
1362#else
1363const char* const Updater_OGL = NULL((void*)0);
1364#endif
1365
1366cc_result Updater_Start(const char** action) {
1367 char path[NATIVE_STR_LEN600 + 1];
1368 char* argv[2];
1369 cc_result res;
1370 int len = 0;
1371
1372 *action = "Getting executable path";
1373 if ((res = Process_RawGetExePath(path, &len))) return res;
1374 path[len] = '\0';
1375
1376 /* Because the process is only referenced by inode, we can */
1377 /* just unlink current filename and rename updated file to it */
1378 *action = "Deleting executable";
1379 if (unlink(path) == -1) return errno(*__errno());
1380 *action = "Replacing executable";
1381 if (rename(UPDATE_FILE"ClassiCube.update", path) == -1) return errno(*__errno());
1382
1383 argv[0] = path; argv[1] = NULL((void*)0);
1384 *action = "Restarting game";
1385 return Process_RawStart(path, argv);
1386}
1387
1388cc_result Updater_GetBuildTime(cc_uint64* timestamp) {
1389 char path[NATIVE_STR_LEN600 + 1];
1390 struct stat sb;
1391 int len = 0;
1392
1393 cc_result res = Process_RawGetExePath(path, &len);
1394 if (res) return res;
1395 path[len] = '\0';
1396
1397 if (stat(path, &sb) == -1) return errno(*__errno());
1398 *timestamp = (cc_uint64)sb.st_mtimest_mtim.tv_sec;
1399 return 0;
1400}
1401
1402cc_result Updater_MarkExecutable(void) {
1403 struct stat st;
1404 if (stat(UPDATE_FILE"ClassiCube.update", &st) == -1) return errno(*__errno());
1405
1406 st.st_mode |= S_IXUSR0000100;
1407 return chmod(UPDATE_FILE"ClassiCube.update", st.st_mode) == -1 ? errno(*__errno()) : 0;
1408}
1409
1410cc_result Updater_SetNewBuildTime(cc_uint64 timestamp) {
1411 struct utimbuf times = { 0 };
1412 times.modtime = timestamp;
1413 return utime(UPDATE_FILE"ClassiCube.update", &times) == -1 ? errno(*__errno()) : 0;
1414}
1415#endif
1416
1417
1418/*########################################################################################################################*
1419*-------------------------------------------------------Dynamic lib-------------------------------------------------------*
1420*#########################################################################################################################*/
1421#if defined CC_BUILD_WIN
1422const cc_string DynamicLib_Ext = String_FromConst(".dll"){ ".dll", (sizeof(".dll") - 1), (sizeof(".dll") - 1)};
1423
1424void* DynamicLib_Load2(const cc_string* path) {
1425 TCHAR str[NATIVE_STR_LEN600];
1426 Platform_EncodeString(str, path);
1427 return LoadLibrary(str);
1428}
1429
1430void* DynamicLib_Get2(void* lib, const char* name) {
1431 return GetProcAddress((HMODULE)lib, name);
1432}
1433
1434cc_bool DynamicLib_DescribeError(cc_string* dst) {
1435 cc_result res = GetLastError();
1436 Platform_DescribeError(res, dst);
1437 String_Format1(dst, " (error %i)", &res);
1438 return true1;
1439}
1440#elif defined CC_BUILD_WEB
1441void* DynamicLib_Load2(const cc_string* path) { return NULL((void*)0); }
1442void* DynamicLib_Get2(void* lib, const char* name) { return NULL((void*)0); }
1443cc_bool DynamicLib_DescribeError(cc_string* dst) { return false0; }
1444#elif defined MAC_OS_X_VERSION_MIN_REQUIRED && (MAC_OS_X_VERSION_MIN_REQUIRED < 1040)
1445/* Really old mac OS versions don't have the dlopen/dlsym API */
1446const cc_string DynamicLib_Ext = String_FromConst(".dylib"){ ".dylib", (sizeof(".dylib") - 1), (sizeof(".dylib") - 1)};
1447
1448void* DynamicLib_Load2(const cc_string* path) {
1449 char str[NATIVE_STR_LEN600];
1450 Platform_EncodeString(str, path);
1451 return NSAddImage(str, NSADDIMAGE_OPTION_WITH_SEARCHING |
1452 NSADDIMAGE_OPTION_RETURN_ON_ERROR);
1453}
1454
1455void* DynamicLib_Get2(void* lib, const char* name) {
1456 cc_string tmp; char tmpBuffer[128];
1457 NSSymbol sym;
1458 String_InitArray_NT(tmp, tmpBuffer)tmp.buffer = tmpBuffer; tmp.length = 0; tmp.capacity = sizeof
(tmpBuffer) - 1;
;
1459
1460 /* NS linker api rquires symbols to have a _ prefix */
1461 String_Append(&tmp, '_');
1462 String_AppendConst(&tmp, name);
1463 tmp.buffer[tmp.length] = '\0';
1464
1465 sym = NSLookupSymbolInImage(lib, tmp.buffer, NSLOOKUPSYMBOLINIMAGE_OPTION_BIND_NOW |
1466 NSLOOKUPSYMBOLINIMAGE_OPTION_RETURN_ON_ERROR);
1467 return sym ? NSAddressOfSymbol(sym) : NULL((void*)0);
1468}
1469
1470cc_bool DynamicLib_DescribeError(cc_string* dst) {
1471 NSLinkEditErrors err = 0;
1472 const char* name = "";
1473 const char* msg = "";
1474 int errNum = 0;
1475
1476 NSLinkEditError(&err, &errNum, &name, &msg);
1477 String_Format4(dst, "%c in %c (%i, sys %i)", msg, name, &err, &errNum);
1478 return true1;
1479}
1480#elif defined CC_BUILD_POSIX
1481#include <dlfcn.h>
1482/* TODO: Should we use .bundle instead of .dylib? */
1483
1484#ifdef CC_BUILD_OSX
1485const cc_string DynamicLib_Ext = String_FromConst(".dylib"){ ".dylib", (sizeof(".dylib") - 1), (sizeof(".dylib") - 1)};
1486#else
1487const cc_string DynamicLib_Ext = String_FromConst(".so"){ ".so", (sizeof(".so") - 1), (sizeof(".so") - 1)};
1488#endif
1489
1490void* DynamicLib_Load2(const cc_string* path) {
1491 char str[NATIVE_STR_LEN600];
1492 Platform_EncodeString(str, path);
1493 return dlopen(str, RTLD_NOW2);
1494}
1495
1496void* DynamicLib_Get2(void* lib, const char* name) {
1497 return dlsym(lib, name);
1498}
1499
1500cc_bool DynamicLib_DescribeError(cc_string* dst) {
1501 char* err = dlerror();
1502 if (err) String_AppendConst(dst, err);
1503 return err && err[0];
1504}
1505#endif
1506
1507cc_result DynamicLib_Load(const cc_string* path, void** lib) {
1508 *lib = DynamicLib_Load2(path);
1509 return *lib == NULL((void*)0);
1510}
1511cc_result DynamicLib_Get(void* lib, const char* name, void** symbol) {
1512 *symbol = DynamicLib_Get2(lib, name);
1513 return *symbol == NULL((void*)0);
1514}
1515
1516
1517cc_bool DynamicLib_GetAll(void* lib, const struct DynamicLibSym* syms, int count) {
1518 int i, loaded = 0;
1519 void* addr;
1520
1521 for (i = 0; i < count; i++) {
1522 addr = DynamicLib_Get2(lib, syms[i].name);
1523 if (addr) loaded++;
1524 *syms[i].symAddr = addr;
1525 }
1526 return loaded == count;
1527}
1528
1529
1530/*########################################################################################################################*
1531*--------------------------------------------------------Platform---------------------------------------------------------*
1532*#########################################################################################################################*/
1533#if defined CC_BUILD_WIN
1534int Platform_EncodeString(void* data, const cc_string* src) {
1535 TCHAR* dst = (TCHAR*)data;
1536 int i;
1537 if (src->length > FILENAME_SIZE260) Logger_Abort("String too long to expand");
1538
1539 for (i = 0; i < src->length; i++) {
1540 *dst++ = Convert_CP437ToUnicode(src->buffer[i]);
1541 }
1542 *dst = '\0';
1543 return src->length * 2;
1544}
1545
1546void Platform_DecodeString(cc_string* dst, const void* data, int len) {
1547#ifdef UNICODE
1548 String_AppendUtf16(dst, (const cc_unichar*)data, len * 2);
1549#else
1550 String_DecodeCP1252(dst, (const cc_uint8*)data, len);
1551#endif
1552}
1553
1554static void Platform_InitStopwatch(void) {
1555 LARGE_INTEGER freq;
1556 sw_highRes = QueryPerformanceFrequency(&freq);
1557
1558 if (sw_highRes) {
1559 sw_freqMul = 1000 * 1000;
1560 sw_freqDiv = freq.QuadPart;
1561 } else { sw_freqDiv = 10; }
1562}
1563
1564typedef BOOL (WINAPI *FUNC_AttachConsole)(DWORD dwProcessId);
1565static void AttachParentConsole(void) {
1566 static const cc_string kernel32 = String_FromConst("KERNEL32.DLL"){ "KERNEL32.DLL", (sizeof("KERNEL32.DLL") - 1), (sizeof("KERNEL32.DLL"
) - 1)}
;
1567 FUNC_AttachConsole attach;
1568 void* lib;
1569
1570 /* NOTE: Need to dynamically load, not supported on Windows 2000 */
1571 if ((lib = DynamicLib_Load2(&kernel32))) {
1572 attach = (FUNC_AttachConsole)DynamicLib_Get2(lib, "AttachConsole");
1573 if (attach) attach((DWORD)-1); /* ATTACH_PARENT_PROCESS */
1574 }
1575}
1576
1577void Platform_Init(void) {
1578 WSADATA wsaData;
1579 cc_result res;
1580
1581 Platform_InitStopwatch();
1582 heap = GetProcessHeap();
1583
1584 res = WSAStartup(MAKEWORD(2, 2), &wsaData);
1585 if (res) Logger_SysWarn(res, "starting WSA");
1586
1587 hasDebugger = IsDebuggerPresent();
1588 /* For when user runs from command prompt */
1589 AttachParentConsole();
1590
1591 conHandle = GetStdHandle(STD_OUTPUT_HANDLE);
1592 if (conHandle == INVALID_HANDLE_VALUE) conHandle = NULL((void*)0);
1593}
1594
1595void Platform_Free(void) {
1596 WSACleanup();
1597 HeapDestroy(heap);
1598}
1599
1600cc_result Platform_Encrypt(const cc_string* key, const void* data, int len, cc_string* dst) {
1601 DATA_BLOB input, output;
1602 int i;
1603 input.cbData = len; input.pbData = (BYTE*)data;
1604 if (!CryptProtectData(&input, NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0), 0, &output)) return GetLastError();
1605
1606 for (i = 0; i < output.cbData; i++) {
1607 String_Append(dst, output.pbData[i]);
1608 }
1609 LocalFree(output.pbData);
1610 return 0;
1611}
1612cc_result Platform_Decrypt(const cc_string* key, const void* data, int len, cc_string* dst) {
1613 DATA_BLOB input, output;
1614 int i;
1615 input.cbData = len; input.pbData = (BYTE*)data;
1616 if (!CryptUnprotectData(&input, NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0), 0, &output)) return GetLastError();
1617
1618 for (i = 0; i < output.cbData; i++) {
1619 String_Append(dst, output.pbData[i]);
1620 }
1621 LocalFree(output.pbData);
1622 return 0;
1623}
1624
1625cc_bool Platform_DescribeErrorExt(cc_result res, cc_string* dst, void* lib) {
1626 TCHAR chars[NATIVE_STR_LEN600];
1627 DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
1628 if (lib) flags |= FORMAT_MESSAGE_FROM_HMODULE;
1629
1630 res = FormatMessage(flags, lib, res, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
1631 chars, NATIVE_STR_LEN600, NULL((void*)0));
1632 if (!res) return false0;
1633
1634 Platform_DecodeString(dst, chars, res);
1635 return true1;
1636}
1637
1638cc_bool Platform_DescribeError(cc_result res, cc_string* dst) {
1639 return Platform_DescribeErrorExt(res, dst, NULL((void*)0));
1640}
1641#elif defined CC_BUILD_POSIX
1642int Platform_EncodeString(void* data, const cc_string* src) {
1643 cc_uint8* dst = (cc_uint8*)data;
1644 cc_uint8* cur;
1645 int i, len = 0;
1646 if (src->length > FILENAME_SIZE260) Logger_Abort("String too long to expand");
1647
1648 for (i = 0; i < src->length; i++) {
1649 cur = dst + len;
1650 len += Convert_CP437ToUtf8(src->buffer[i], cur);
1651 }
1652 dst[len] = '\0';
1653 return len;
1654}
1655
1656void Platform_DecodeString(cc_string* dst, const void* data, int len) {
1657 String_AppendUtf8(dst, (const cc_uint8*)data, len);
1658}
1659
1660static void Platform_InitPosix(void) {
1661 signal(SIGCHLD20, SIG_IGN(void (*)(int))1);
1662 /* So writing to closed socket doesn't raise SIGPIPE */
1663 signal(SIGPIPE13, SIG_IGN(void (*)(int))1);
1664 /* Assume stopwatch is in nanoseconds */
1665 /* Some platforms (e.g. macOS) override this */
1666 sw_freqDiv = 1000;
1667}
1668void Platform_Free(void) { }
1669
1670cc_result Platform_Encrypt(const cc_string* key, const void* data, int len, cc_string* dst) {
1671 /* TODO: Is there a similar API for macOS/Linux? */
1672 /* Fallback to NOT SECURE XOR. Prevents simple reading from options.txt */
1673 const cc_uint8* src = data;
1674 cc_uint8 c;
1675 int i;
1676
1677 for (i = 0; i < len; i++) {
1678 c = (cc_uint8)(src[i] ^ key->buffer[i % key->length] ^ 0x43);
1679 String_Append(dst, c);
1680 }
1681 return 0;
1682}
1683cc_result Platform_Decrypt(const cc_string* key, const void* data, int len, cc_string* dst) {
1684 /* TODO: Is there a similar API for macOS/Linux? */
1685 return Platform_Encrypt(key, data, len, dst);
1686}
1687
1688cc_bool Platform_DescribeError(cc_result res, cc_string* dst) {
1689 char chars[NATIVE_STR_LEN600];
1690 int len;
1691
1692 /* For unrecognised error codes, strerror_r might return messages */
1693 /* such as 'No error information', which is not very useful */
1694 /* (could check errno here but quicker just to skip entirely) */
1695 if (res >= 1000) return false0;
1696
1697 len = strerror_r(res, chars, NATIVE_STR_LEN600);
1698 if (len == -1) return false0;
1699
1700 len = String_CalcLen(chars, NATIVE_STR_LEN600);
1701 Platform_DecodeString(dst, chars, len);
1702 return true1;
1703}
1704
1705#if defined CC_BUILD_OSX
1706static void Platform_InitStopwatch(void) {
1707 mach_timebase_info_data_t tb = { 0 };
1708 mach_timebase_info(&tb);
1709
1710 sw_freqMul = tb.numer;
1711 /* tb.denom may be large, so multiplying by 1000 overflows 32 bits */
1712 /* (one powerpc system had tb.denom of 33329426) */
1713 sw_freqDiv = (cc_uint64)tb.denom * 1000;
1714}
1715
1716void Platform_Init(void) {
1717 ProcessSerialNumber psn; /* TODO: kCurrentProcess */
1718 Platform_InitPosix();
1719 Platform_InitStopwatch();
1720
1721 /* NOTE: Call as soon as possible, otherwise can't click on dialog boxes. */
1722 GetCurrentProcess(&psn);
1723 /* NOTE: TransformProcessType is macOS 10.3 or later */
1724 TransformProcessType(&psn, kProcessTransformToForegroundApplication);
1725}
1726#elif defined CC_BUILD_WEB
1727void Platform_Init(void) {
1728 char tmp[64+1] = { 0 };
1729 EM_ASM( Module['websocket']['subprotocol'] = 'ClassiCube'; );
1730 /* Check if an error occurred when pre-loading IndexedDB */
1731 EM_ASM_({ if (window.cc_idbErr) stringToUTF8(window.cc_idbErr, $0, 64); }, tmp);
1732
1733 EM_ASM({
1734 Module.saveBlob = function(blob, name) {
1735 if (window.navigator.msSaveBlob) {
1736 window.navigator.msSaveBlob(blob, name); return;
1737 }
1738 var url = window.URL.createObjectURL(blob);
1739 var elem = document.createElement('a');
1740
1741 elem.href = url;
1742 elem.download = name;
1743 elem.style.display = 'none';
1744
1745 document.body.appendChild(elem);
1746 elem.click();
1747 document.body.removeChild(elem);
1748 window.URL.revokeObjectURL(url);
1749 }
1750 });
1751
1752 if (!tmp[0]) return;
1753 Chat_Add1("&cError preloading IndexedDB: %c", tmp);
1754 Chat_AddRaw("&cPreviously saved settings/maps will be lost");
1755
1756 /* NOTE: You must pre-load IndexedDB before main() */
1757 /* (because pre-loading only works asynchronously) */
1758 /* If you don't, you'll get errors later trying to sync local to remote */
1759 /* See doc/hosting-webclient.md for example preloading IndexedDB code */
1760}
1761#else
1762void Platform_Init(void) { Platform_InitPosix(); }
1763#endif
1764#endif /* CC_BUILD_POSIX */
1765
1766
1767/*########################################################################################################################*
1768*--------------------------------------------------------Platform---------------------------------------------------------*
1769*#########################################################################################################################*/
1770#if defined CC_BUILD_WIN
1771static cc_string Platform_NextArg(STRING_REF cc_string* args) {
1772 cc_string arg;
1773 int end;
1774
1775 /* get rid of leading spaces before arg */
1776 while (args->length && args->buffer[0] == ' ') {
1777 *args = String_UNSAFE_SubstringAt(args, 1);
1778 }
1779
1780 if (args->length && args->buffer[0] == '"') {
1781 /* "xy za" is used for arg with spaces */
1782 *args = String_UNSAFE_SubstringAt(args, 1);
1783 end = String_IndexOf(args, '"')String_IndexOfAt(args, 0, '"');
1784 } else {
1785 end = String_IndexOf(args, ' ')String_IndexOfAt(args, 0, ' ');
1786 }
1787
1788 if (end == -1) {
1789 arg = *args;
1790 args->length = 0;
1791 } else {
1792 arg = String_UNSAFE_Substring(args, 0, end);
1793 *args = String_UNSAFE_SubstringAt(args, end + 1);
1794 }
1795 return arg;
1796}
1797
1798int Platform_GetCommandLineArgs(int argc, STRING_REF char** argv, cc_string* args) {
1799 cc_string cmdArgs = String_FromReadonly(GetCommandLineA());
1800 int i;
1801 Platform_NextArg(&cmdArgs); /* skip exe path */
1802
1803 for (i = 0; i < GAME_MAX_CMDARGS5; i++) {
1804 args[i] = Platform_NextArg(&cmdArgs);
1805
1806 if (!args[i].length) break;
1807 }
1808 return i;
1809}
1810
1811cc_result Platform_SetDefaultCurrentDirectory(int argc, char **argv) {
1812 TCHAR path[NATIVE_STR_LEN600 + 1];
1813 int i, len;
1814 cc_result res = Process_RawGetExePath(path, &len);
1815 if (res) return res;
1816
1817 /* Get rid of filename at end of directory */
1818 for (i = len - 1; i >= 0; i--, len--) {
1819 if (path[i] == '/' || path[i] == '\\') break;
1820 }
1821
1822 path[len] = '\0';
1823 return SetCurrentDirectory(path) ? 0 : GetLastError();
1824}
1825#elif defined CC_BUILD_WEB
1826int Platform_GetCommandLineArgs(int argc, STRING_REF char** argv, cc_string* args) {
1827 int i, count;
1828 argc--; argv++; /* skip executable path argument */
1829
1830 count = min(argc, GAME_MAX_CMDARGS)((argc) < (5) ? (argc) : (5));
1831 for (i = 0; i < count; i++) { args[i] = String_FromReadonly(argv[i]); }
1832 return count;
1833}
1834
1835cc_result Platform_SetDefaultCurrentDirectory(int argc, char **argv) {
1836 return chdir("/classicube") == -1 ? errno(*__errno()) : 0;
1837}
1838#elif defined CC_BUILD_ANDROID
1839int Platform_GetCommandLineArgs(int argc, STRING_REF char** argv, cc_string* args) {
1840 if (!gameArgs.length) return 0;
1841 return String_UNSAFE_Split(&gameArgs, ' ', args, GAME_MAX_CMDARGS5);
1842}
1843
1844cc_result Platform_SetDefaultCurrentDirectory(int argc, char **argv) {
1845 cc_string dir; char dirBuffer[FILENAME_SIZE260 + 1];
1846 String_InitArray_NT(dir, dirBuffer)dir.buffer = dirBuffer; dir.length = 0; dir.capacity = sizeof
(dirBuffer) - 1;
;
1847
1848 JavaCall_Void_String("getExternalAppDir", &dir);
1849 dir.buffer[dir.length] = '\0';
1850 Platform_Log1("EXTERNAL DIR: %s|", &dir);
1851 return chdir(dir.buffer) == -1 ? errno(*__errno()) : 0;
1852}
1853#elif defined CC_BUILD_POSIX
1854int Platform_GetCommandLineArgs(int argc, STRING_REF char** argv, cc_string* args) {
1855 int i, count;
1856 argc--; argv++; /* skip executable path argument */
1857
1858#ifdef CC_BUILD_OSX
1859 if (argc) {
1860 static const cc_string psn = String_FromConst("-psn_0_"){ "-psn_0_", (sizeof("-psn_0_") - 1), (sizeof("-psn_0_") - 1)
}
;
1861 cc_string arg0 = String_FromReadonly(argv[0]);
1862 if (String_CaselessStarts(&arg0, &psn)) { argc--; argv++; }
1863 }
1864#endif
1865
1866 count = min(argc, GAME_MAX_CMDARGS)((argc) < (5) ? (argc) : (5));
1867 for (i = 0; i < count; i++) {
1868 if (argv[i][0] == '-' && argv[i][1] == 'd' && argv[i][2]) {
1869 --count;
1870 continue;
1871 }
1872 args[i] = String_FromReadonly(argv[i]);
1873 }
1874 return count;
1875}
1876
1877
1878cc_result Platform_SetDefaultCurrentDirectory(int argc, char **argv) {
1879 char path[NATIVE_STR_LEN600];
1880 int i, len = 0;
1881 cc_result res;
1882
1883 for (i = 1; i < argc; ++i) {
1884 if (argv[i][0] == '-' && argv[i][1] == 'd' && argv[i][2]) {
1885 defaultDirectory = argv[i];
1886 break;
1887 }
1888 }
1889
1890 if (defaultDirectory) {
1891 return chdir(defaultDirectory + 2) == -1 ? errno(*__errno()) : 0;
1892 }
1893
1894 res = Process_RawGetExePath(path, &len);
1895 if (res) return res;
1896
1897 /* get rid of filename at end of directory */
1898 for (i = len - 1; i >= 0; i--, len--) {
1899 if (path[i] == '/') break;
1900 }
1901
1902#ifdef CC_BUILD_OSX
1903 static const cc_string bundle = String_FromConst(".app/Contents/MacOS/"){ ".app/Contents/MacOS/", (sizeof(".app/Contents/MacOS/") - 1
), (sizeof(".app/Contents/MacOS/") - 1)}
;
1904 cc_string raw = String_Init(path, len, 0);
1905
1906 if (String_CaselessEnds(&raw, &bundle)) {
1907 len -= bundle.length;
1908
1909 for (i = len - 1; i >= 0; i--, len--) {
1910 if (path[i] == '/') break;
1911 }
1912 }
1913#endif
1914
1915 path[len] = '\0';
1916 return chdir(path) == -1 ? errno(*__errno()) : 0;
1917}
1918#endif
1919
1920/* Android java interop stuff */
1921#if defined CC_BUILD_ANDROID
1922jclass App_Class;
1923jobject App_Instance;
1924JavaVM* VM_Ptr;
1925
1926/* JNI helpers */
1927cc_string JavaGetString(JNIEnv* env, jstring str, char* buffer) {
1928 const char* src; int len;
1929 cc_string dst;
1930 src = (*env)->GetStringUTFChars(env, str, NULL((void*)0));
1931 len = (*env)->GetStringUTFLength(env, str);
1932
1933 dst.buffer = buffer;
1934 dst.length = 0;
1935 dst.capacity = NATIVE_STR_LEN600;
1936 String_AppendUtf8(&dst, (const cc_uint8*)src, len);
1937
1938 (*env)->ReleaseStringUTFChars(env, str, src);
1939 return dst;
1940}
1941
1942jobject JavaMakeString(JNIEnv* env, const cc_string* str) {
1943 cc_uint8 tmp[2048 + 4];
1944 cc_uint8* cur;
1945 int i, len = 0;
1946
1947 for (i = 0; i < str->length && len < 2048; i++) {
1948 cur = tmp + len;
1949 len += Convert_CP437ToUtf8(str->buffer[i], cur);
1950 }
1951 tmp[len] = '\0';
1952 return (*env)->NewStringUTF(env, (const char*)tmp);
1953}
1954
1955jbyteArray JavaMakeBytes(JNIEnv* env, const cc_uint8* src, cc_uint32 len) {
1956 if (!len) return NULL((void*)0);
1957 jbyteArray arr = (*env)->NewByteArray(env, len);
1958 (*env)->SetByteArrayRegion(env, arr, 0, len, src);
1959 return arr;
1960}
1961
1962void JavaCallVoid(JNIEnv* env, const char* name, const char* sig, jvalue* args) {
1963 jmethodID method = (*env)->GetMethodID(env, App_Class, name, sig);
1964 (*env)->CallVoidMethodA(env, App_Instance, method, args);
1965}
1966
1967jint JavaCallInt(JNIEnv* env, const char* name, const char* sig, jvalue* args) {
1968 jmethodID method = (*env)->GetMethodID(env, App_Class, name, sig);
1969 return (*env)->CallIntMethodA(env, App_Instance, method, args);
1970}
1971
1972jlong JavaCallLong(JNIEnv* env, const char* name, const char* sig, jvalue* args) {
1973 jmethodID method = (*env)->GetMethodID(env, App_Class, name, sig);
1974 return (*env)->CallLongMethodA(env, App_Instance, method, args);
1975}
1976
1977jfloat JavaCallFloat(JNIEnv* env, const char* name, const char* sig, jvalue* args) {
1978 jmethodID method = (*env)->GetMethodID(env, App_Class, name, sig);
1979 return (*env)->CallFloatMethodA(env, App_Instance, method, args);
1980}
1981
1982jobject JavaCallObject(JNIEnv* env, const char* name, const char* sig, jvalue* args) {
1983 jmethodID method = (*env)->GetMethodID(env, App_Class, name, sig);
1984 return (*env)->CallObjectMethodA(env, App_Instance, method, args);
1985}
1986
1987void JavaCall_String_Void(const char* name, const cc_string* value) {
1988 JNIEnv* env;
1989 jvalue args[1];
1990 JavaGetCurrentEnv(env);
1991
1992 args[0].l = JavaMakeString(env, value);
1993 JavaCallVoid(env, name, "(Ljava/lang/String;)V", args);
1994 (*env)->DeleteLocalRef(env, args[0].l);
1995}
1996
1997static void ReturnString(JNIEnv* env, jobject obj, cc_string* dst) {
1998 const jchar* src;
1999 jsize len;
2000 if (!obj) return;
2001
2002 src = (*env)->GetStringChars(env, obj, NULL((void*)0));
2003 len = (*env)->GetStringLength(env, obj);
2004 String_AppendUtf16(dst, src, len * 2);
2005 (*env)->ReleaseStringChars(env, obj, src);
2006 (*env)->DeleteLocalRef(env, obj);
2007}
2008
2009void JavaCall_Void_String(const char* name, cc_string* dst) {
2010 JNIEnv* env;
2011 jobject obj;
2012 JavaGetCurrentEnv(env);
2013
2014 obj = JavaCallObject(env, name, "()Ljava/lang/String;", NULL((void*)0));
2015 ReturnString(env, obj, dst);
2016}
2017
2018void JavaCall_String_String(const char* name, const cc_string* arg, cc_string* dst) {
2019 JNIEnv* env;
2020 jobject obj;
2021 jvalue args[1];
2022 JavaGetCurrentEnv(env);
2023
2024 args[0].l = JavaMakeString(env, arg);
2025 obj = JavaCallObject(env, name, "(Ljava/lang/String;)Ljava/lang/String;", args);
2026 ReturnString(env, obj, dst);
2027 (*env)->DeleteLocalRef(env, args[0].l);
2028}
2029#endif