| File: | Platform.c |
| Warning: | line 674, column 2 Use of memory after it is freed |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 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() | |||
| 29 | static HANDLE heap; | |||
| 30 | ||||
| 31 | const cc_result ReturnCode_FileShareViolation = ERROR_SHARING_VIOLATION; | |||
| 32 | const cc_result ReturnCode_FileNotFound = ERROR_FILE_NOT_FOUND; | |||
| 33 | const cc_result ReturnCode_SocketInProgess = WSAEINPROGRESS; | |||
| 34 | const 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()) | |||
| 57 | static char* defaultDirectory; | |||
| 58 | const cc_result ReturnCode_FileShareViolation = 1000000000; /* TODO: not used apparently */ | |||
| 59 | const cc_result ReturnCode_FileNotFound = ENOENT2; | |||
| 60 | const cc_result ReturnCode_SocketInProgess = EINPROGRESS36; | |||
| 61 | const 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 | *#########################################################################################################################*/ | |||
| 85 | void Mem_Set(void* dst, cc_uint8 value, cc_uint32 numBytes) { memset(dst, value, numBytes); } | |||
| 86 | void Mem_Copy(void* dst, const void* src, cc_uint32 numBytes) { memcpy(dst, src, numBytes); } | |||
| 87 | ||||
| 88 | CC_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 | ||||
| 97 | void* Mem_Alloc(cc_uint32 numElems, cc_uint32 elemsSize, const char* place) { | |||
| 98 | void* ptr = Mem_TryAlloc(numElems, elemsSize); | |||
| 99 | if (!ptr) AbortOnAllocFailed(place); | |||
| 100 | return ptr; | |||
| 101 | } | |||
| 102 | ||||
| 103 | void* 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 | ||||
| 109 | void* 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 | ||||
| 115 | static 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 | |||
| 122 | void* 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 | ||||
| 127 | void* 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 | ||||
| 132 | void* 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 | ||||
| 137 | void Mem_Free(void* mem) { | |||
| 138 | if (mem) HeapFree(heap, 0, mem); | |||
| 139 | } | |||
| 140 | #elif defined CC_BUILD_POSIX | |||
| 141 | void* Mem_TryAlloc(cc_uint32 numElems, cc_uint32 elemsSize) { | |||
| 142 | cc_uint32 size = CalcMemSize(numElems, elemsSize); | |||
| 143 | return size
| |||
| 144 | } | |||
| 145 | ||||
| 146 | void* Mem_TryAllocCleared(cc_uint32 numElems, cc_uint32 elemsSize) { | |||
| 147 | return calloc(numElems, elemsSize); | |||
| 148 | } | |||
| 149 | ||||
| 150 | void* 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 | ||||
| 155 | void Mem_Free(void* mem) { | |||
| 156 | if (mem
| |||
| 157 | } | |||
| 158 | #endif | |||
| 159 | ||||
| 160 | ||||
| 161 | /*########################################################################################################################* | |||
| 162 | *------------------------------------------------------Logging/Time-------------------------------------------------------* | |||
| 163 | *#########################################################################################################################*/ | |||
| 164 | void Platform_Log1(const char* format, const void* a1) { | |||
| 165 | Platform_Log4(format, a1, NULL((void*)0), NULL((void*)0), NULL((void*)0)); | |||
| 166 | } | |||
| 167 | void Platform_Log2(const char* format, const void* a1, const void* a2) { | |||
| 168 | Platform_Log4(format, a1, a2, NULL((void*)0), NULL((void*)0)); | |||
| 169 | } | |||
| 170 | void 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 | ||||
| 174 | void 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 | ||||
| 182 | void Platform_LogConst(const char* message) { | |||
| 183 | Platform_Log(message, String_Length(message)); | |||
| 184 | } | |||
| 185 | ||||
| 186 | /* TODO: check this is actually accurate */ | |||
| 187 | static cc_uint64 sw_freqMul = 1, sw_freqDiv = 1; | |||
| 188 | cc_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 | ||||
| 193 | cc_uint64 Stopwatch_ElapsedMilliseconds(cc_uint64 beg, cc_uint64 end) { | |||
| 194 | return Stopwatch_ElapsedMicroseconds(beg, end) / 1000; | |||
| 195 | } | |||
| 196 | ||||
| 197 | #if defined CC_BUILD_WIN | |||
| 198 | static HANDLE conHandle; | |||
| 199 | static BOOL hasDebugger; | |||
| 200 | ||||
| 201 | void 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) | |||
| 222 | TimeMS 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 | ||||
| 232 | void 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 | ||||
| 244 | static cc_bool sw_highRes; | |||
| 245 | cc_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> | |||
| 261 | void 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 | |||
| 269 | void 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)) | |||
| 276 | TimeMS 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 | ||||
| 282 | void 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 | |||
| 301 | cc_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 | |||
| 306 | cc_uint64 Stopwatch_Measure(void) { return mach_absolute_time(); } | |||
| 307 | #elif defined CC_BUILD_SOLARIS | |||
| 308 | cc_uint64 Stopwatch_Measure(void) { return gethrtime(); } | |||
| 309 | #elif defined CC_BUILD_POSIX | |||
| 310 | cc_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 | |||
| 323 | int 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 | ||||
| 332 | cc_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 | ||||
| 341 | int 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 | ||||
| 350 | cc_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 | ||||
| 394 | static 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 | ||||
| 401 | cc_result File_Open(cc_file* file, const cc_string* path) { | |||
| 402 | return File_Do(file, path, GENERIC_READ, OPEN_EXISTING); | |||
| 403 | } | |||
| 404 | cc_result File_Create(cc_file* file, const cc_string* path) { | |||
| 405 | return File_Do(file, path, GENERIC_WRITE | GENERIC_READ, CREATE_ALWAYS); | |||
| 406 | } | |||
| 407 | cc_result File_OpenOrCreate(cc_file* file, const cc_string* path) { | |||
| 408 | return File_Do(file, path, GENERIC_WRITE | GENERIC_READ, OPEN_ALWAYS); | |||
| 409 | } | |||
| 410 | ||||
| 411 | cc_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 | ||||
| 416 | cc_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 | ||||
| 421 | cc_result File_Close(cc_file file) { | |||
| 422 | return CloseHandle(file) ? 0 : GetLastError(); | |||
| 423 | } | |||
| 424 | ||||
| 425 | cc_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 | ||||
| 431 | cc_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 | ||||
| 436 | cc_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 | |||
| 441 | int 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 | ||||
| 448 | cc_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 | ||||
| 456 | int 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 | ||||
| 463 | cc_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 | ||||
| 507 | static 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 | ||||
| 514 | cc_result File_Open(cc_file* file, const cc_string* path) { | |||
| 515 | return File_Do(file, path, O_RDONLY0x0000); | |||
| 516 | } | |||
| 517 | cc_result File_Create(cc_file* file, const cc_string* path) { | |||
| 518 | return File_Do(file, path, O_RDWR0x0002 | O_CREAT0x0200 | O_TRUNC0x0400); | |||
| 519 | } | |||
| 520 | cc_result File_OpenOrCreate(cc_file* file, const cc_string* path) { | |||
| 521 | return File_Do(file, path, O_RDWR0x0002 | O_CREAT0x0200); | |||
| 522 | } | |||
| 523 | ||||
| 524 | cc_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 | ||||
| 529 | cc_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 | ||||
| 534 | cc_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 | ||||
| 544 | cc_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 | ||||
| 549 | cc_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 | ||||
| 554 | cc_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 | |||
| 566 | void Thread_Sleep(cc_uint32 milliseconds) { Sleep(milliseconds); } | |||
| 567 | static DWORD WINAPI ExecThread(void* param) { | |||
| 568 | Thread_StartFunc func = (Thread_StartFunc)param; | |||
| 569 | func(); | |||
| 570 | return 0; | |||
| 571 | } | |||
| 572 | ||||
| 573 | void* 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 | ||||
| 584 | void Thread_Detach(void* handle) { | |||
| 585 | if (!CloseHandle((HANDLE)handle)) { | |||
| 586 | Logger_Abort2(GetLastError(), "Freeing thread handle"); | |||
| 587 | } | |||
| 588 | } | |||
| 589 | ||||
| 590 | void Thread_Join(void* handle) { | |||
| 591 | WaitForSingleObject((HANDLE)handle, INFINITE); | |||
| 592 | Thread_Detach(handle); | |||
| 593 | } | |||
| 594 | ||||
| 595 | void* Mutex_Create(void) { | |||
| 596 | CRITICAL_SECTION* ptr = (CRITICAL_SECTION*)Mem_Alloc(1, sizeof(CRITICAL_SECTION), "mutex"); | |||
| 597 | InitializeCriticalSection(ptr); | |||
| 598 | return ptr; | |||
| 599 | } | |||
| 600 | ||||
| 601 | void Mutex_Free(void* handle) { | |||
| 602 | DeleteCriticalSection((CRITICAL_SECTION*)handle); | |||
| 603 | Mem_Free(handle); | |||
| 604 | } | |||
| 605 | void Mutex_Lock(void* handle) { EnterCriticalSection((CRITICAL_SECTION*)handle); } | |||
| 606 | void Mutex_Unlock(void* handle) { LeaveCriticalSection((CRITICAL_SECTION*)handle); } | |||
| 607 | ||||
| 608 | void* 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 | ||||
| 616 | void Waitable_Free(void* handle) { | |||
| 617 | if (!CloseHandle((HANDLE)handle)) { | |||
| 618 | Logger_Abort2(GetLastError(), "Freeing waitable"); | |||
| 619 | } | |||
| 620 | } | |||
| 621 | ||||
| 622 | void Waitable_Signal(void* handle) { SetEvent((HANDLE)handle); } | |||
| 623 | void Waitable_Wait(void* handle) { | |||
| 624 | WaitForSingleObject((HANDLE)handle, INFINITE); | |||
| 625 | } | |||
| 626 | ||||
| 627 | void 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 */ | |||
| 632 | void Thread_Sleep(cc_uint32 milliseconds) { } | |||
| 633 | void* Thread_Start(Thread_StartFunc func, cc_bool detach) { func(); return NULL((void*)0); } | |||
| 634 | void Thread_Detach(void* handle) { } | |||
| 635 | void Thread_Join(void* handle) { } | |||
| 636 | ||||
| 637 | void* Mutex_Create(void) { return NULL((void*)0); } | |||
| 638 | void Mutex_Free(void* handle) { } | |||
| 639 | void Mutex_Lock(void* handle) { } | |||
| 640 | void Mutex_Unlock(void* handle) { } | |||
| 641 | ||||
| 642 | void* Waitable_Create(void) { return NULL((void*)0); } | |||
| 643 | void Waitable_Free(void* handle) { } | |||
| 644 | void Waitable_Signal(void* handle) { } | |||
| 645 | void Waitable_Wait(void* handle) { } | |||
| 646 | void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) { } | |||
| 647 | #elif defined CC_BUILD_POSIX | |||
| 648 | void 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 */ | |||
| 653 | static 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 | |||
| 662 | static void* ExecThread(void* param) { | |||
| 663 | ((Thread_StartFunc)param)(); | |||
| 664 | return NULL((void*)0); | |||
| 665 | } | |||
| 666 | #endif | |||
| 667 | ||||
| 668 | void* Thread_Start(Thread_StartFunc func, cc_bool detach) { | |||
| 669 | pthread_t* ptr = (pthread_t*)Mem_Alloc(1, sizeof(pthread_t), "thread"); | |||
| ||||
| 670 | int res = pthread_create(ptr, NULL((void*)0), ExecThread, (void*)func); | |||
| 671 | if (res) Logger_Abort2(res, "Creating thread"); | |||
| 672 | ||||
| 673 | if (detach) Thread_Detach(ptr); | |||
| 674 | return ptr; | |||
| ||||
| 675 | } | |||
| 676 | ||||
| 677 | void 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"); | |||
| 681 | Mem_Free(ptr); | |||
| 682 | } | |||
| 683 | ||||
| 684 | void 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 | ||||
| 691 | void* 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 | ||||
| 698 | void 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 | ||||
| 704 | void Mutex_Lock(void* handle) { | |||
| 705 | int res = pthread_mutex_lock((pthread_mutex_t*)handle); | |||
| 706 | if (res) Logger_Abort2(res, "Locking mutex"); | |||
| 707 | } | |||
| 708 | ||||
| 709 | void Mutex_Unlock(void* handle) { | |||
| 710 | int res = pthread_mutex_unlock((pthread_mutex_t*)handle); | |||
| 711 | if (res) Logger_Abort2(res, "Unlocking mutex"); | |||
| 712 | } | |||
| 713 | ||||
| 714 | struct WaitData { | |||
| 715 | pthread_cond_t cond; | |||
| 716 | pthread_mutex_t mutex; | |||
| 717 | int signalled; | |||
| 718 | }; | |||
| 719 | ||||
| 720 | void* 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 | ||||
| 733 | void 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 | ||||
| 744 | void 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 | ||||
| 756 | void 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 | ||||
| 769 | void 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 | |||
| 797 | void Platform_LoadSysFonts(void) { } | |||
| 798 | #else | |||
| 799 | static 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 | ||||
| 806 | void 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 | *#########################################################################################################################*/ | |||
| 861 | cc_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 | ||||
| 866 | static 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 | ||||
| 874 | cc_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 | } | |||
| 877 | cc_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 | ||||
| 886 | cc_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 | ||||
| 891 | cc_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 | ||||
| 903 | cc_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 | ||||
| 924 | cc_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 | ||||
| 930 | cc_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 | |||
| 954 | cc_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 */ | |||
| 974 | cc_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> | |||
| 993 | cc_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 | |||
| 1013 | static 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 | ||||
| 1028 | static 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 | ||||
| 1033 | cc_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 | } | |||
| 1047 | void Process_Exit(cc_result code) { ExitProcess(code); } | |||
| 1048 | ||||
| 1049 | void 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 | |||
| 1055 | cc_result Process_StartGame(const cc_string* args) { return ERR_NOT_SUPPORTED; } | |||
| 1056 | void Process_Exit(cc_result code) { exit(code); } | |||
| 1057 | ||||
| 1058 | void 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 | |||
| 1064 | static char gameArgsBuffer[512]; | |||
| 1065 | static cc_string gameArgs = String_FromArray(gameArgsBuffer){ gameArgsBuffer, 0, sizeof(gameArgsBuffer)}; | |||
| 1066 | ||||
| 1067 | cc_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 | } | |||
| 1071 | void Process_Exit(cc_result code) { exit(code); } | |||
| 1072 | ||||
| 1073 | void Process_StartOpen(const cc_string* args) { | |||
| 1074 | JavaCall_String_Void("startOpen", args); | |||
| 1075 | } | |||
| 1076 | #elif defined CC_BUILD_POSIX | |||
| 1077 | static 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 | ||||
| 1092 | static cc_result Process_RawGetExePath(char* path, int* len); | |||
| 1093 | ||||
| 1094 | cc_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 | ||||
| 1120 | void Process_Exit(cc_result code) { exit(code); } | |||
| 1121 | ||||
| 1122 | /* Opening browser/starting shell is not really standardised */ | |||
| 1123 | #if defined CC_BUILD_OSX | |||
| 1124 | void 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 | |||
| 1135 | void 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 | |||
| 1144 | void 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 | |||
| 1157 | static 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 | |||
| 1167 | static 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 | |||
| 1172 | static 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 | |||
| 1181 | static 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 | |||
| 1207 | static 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 | |||
| 1216 | static 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 | |||
| 1221 | static 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 | |||
| 1244 | const char* const Updater_D3D9 = "ClassiCube.64.exe"; | |||
| 1245 | const char* const Updater_OGL = "ClassiCube.64-opengl.exe"; | |||
| 1246 | #else | |||
| 1247 | const char* const Updater_D3D9 = "ClassiCube.exe"; | |||
| 1248 | const char* const Updater_OGL = "ClassiCube.opengl.exe"; | |||
| 1249 | #endif | |||
| 1250 | ||||
| 1251 | cc_bool Updater_Clean(void) { | |||
| 1252 | return DeleteFile(UPDATE_TMP) || GetLastError() == ERROR_FILE_NOT_FOUND; | |||
| 1253 | } | |||
| 1254 | ||||
| 1255 | cc_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 | ||||
| 1274 | cc_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 */ | |||
| 1300 | cc_result Updater_MarkExecutable(void) { return 0; } | |||
| 1301 | cc_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 | |||
| 1319 | const char* const Updater_D3D9 = NULL((void*)0); | |||
| 1320 | const char* const Updater_OGL = NULL((void*)0); | |||
| 1321 | ||||
| 1322 | #if defined CC_BUILD_WEB | |||
| 1323 | cc_result Updater_GetBuildTime(cc_uint64* t) { return ERR_NOT_SUPPORTED; } | |||
| 1324 | #else | |||
| 1325 | cc_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 | ||||
| 1336 | cc_bool Updater_Clean(void) { return true1; } | |||
| 1337 | cc_result Updater_Start(const char** action) { *action = "Updating game"; return ERR_NOT_SUPPORTED; } | |||
| 1338 | cc_result Updater_MarkExecutable(void) { return 0; } | |||
| 1339 | cc_result Updater_SetNewBuildTime(cc_uint64 t) { return ERR_NOT_SUPPORTED; } | |||
| 1340 | #elif defined CC_BUILD_POSIX | |||
| 1341 | cc_bool Updater_Clean(void) { return true1; } | |||
| 1342 | ||||
| 1343 | const char* const Updater_D3D9 = NULL((void*)0); | |||
| 1344 | #if defined CC_BUILD_LINUX | |||
| 1345 | #if __x86_64__1 | |||
| 1346 | const char* const Updater_OGL = "ClassiCube"; | |||
| 1347 | #elif __i386__ | |||
| 1348 | const char* const Updater_OGL = "ClassiCube.32"; | |||
| 1349 | #elif CC_BUILD_RPI | |||
| 1350 | const char* const Updater_OGL = "ClassiCube.rpi"; | |||
| 1351 | #else | |||
| 1352 | const char* const Updater_OGL = NULL((void*)0); | |||
| 1353 | #endif | |||
| 1354 | #elif defined CC_BUILD_OSX | |||
| 1355 | #if __x86_64__1 | |||
| 1356 | const char* const Updater_OGL = "ClassiCube.64.osx"; | |||
| 1357 | #elif __i386__ | |||
| 1358 | const char* const Updater_OGL = "ClassiCube.osx"; | |||
| 1359 | #else | |||
| 1360 | const char* const Updater_OGL = NULL((void*)0); | |||
| 1361 | #endif | |||
| 1362 | #else | |||
| 1363 | const char* const Updater_OGL = NULL((void*)0); | |||
| 1364 | #endif | |||
| 1365 | ||||
| 1366 | cc_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 | ||||
| 1388 | cc_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 | ||||
| 1402 | cc_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 | ||||
| 1410 | cc_result Updater_SetNewBuildTime(cc_uint64 timestamp) { | |||
| 1411 | struct utimbuf times = { 0 }; | |||
| 1412 | times.modtime = timestamp; | |||
| 1413 | return utime(UPDATE_FILE"ClassiCube.update", ×) == -1 ? errno(*__errno()) : 0; | |||
| 1414 | } | |||
| 1415 | #endif | |||
| 1416 | ||||
| 1417 | ||||
| 1418 | /*########################################################################################################################* | |||
| 1419 | *-------------------------------------------------------Dynamic lib-------------------------------------------------------* | |||
| 1420 | *#########################################################################################################################*/ | |||
| 1421 | #if defined CC_BUILD_WIN | |||
| 1422 | const cc_string DynamicLib_Ext = String_FromConst(".dll"){ ".dll", (sizeof(".dll") - 1), (sizeof(".dll") - 1)}; | |||
| 1423 | ||||
| 1424 | void* DynamicLib_Load2(const cc_string* path) { | |||
| 1425 | TCHAR str[NATIVE_STR_LEN600]; | |||
| 1426 | Platform_EncodeString(str, path); | |||
| 1427 | return LoadLibrary(str); | |||
| 1428 | } | |||
| 1429 | ||||
| 1430 | void* DynamicLib_Get2(void* lib, const char* name) { | |||
| 1431 | return GetProcAddress((HMODULE)lib, name); | |||
| 1432 | } | |||
| 1433 | ||||
| 1434 | cc_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 | |||
| 1441 | void* DynamicLib_Load2(const cc_string* path) { return NULL((void*)0); } | |||
| 1442 | void* DynamicLib_Get2(void* lib, const char* name) { return NULL((void*)0); } | |||
| 1443 | cc_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 */ | |||
| 1446 | const cc_string DynamicLib_Ext = String_FromConst(".dylib"){ ".dylib", (sizeof(".dylib") - 1), (sizeof(".dylib") - 1)}; | |||
| 1447 | ||||
| 1448 | void* 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 | ||||
| 1455 | void* 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 | ||||
| 1470 | cc_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 | |||
| 1485 | const cc_string DynamicLib_Ext = String_FromConst(".dylib"){ ".dylib", (sizeof(".dylib") - 1), (sizeof(".dylib") - 1)}; | |||
| 1486 | #else | |||
| 1487 | const cc_string DynamicLib_Ext = String_FromConst(".so"){ ".so", (sizeof(".so") - 1), (sizeof(".so") - 1)}; | |||
| 1488 | #endif | |||
| 1489 | ||||
| 1490 | void* 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 | ||||
| 1496 | void* DynamicLib_Get2(void* lib, const char* name) { | |||
| 1497 | return dlsym(lib, name); | |||
| 1498 | } | |||
| 1499 | ||||
| 1500 | cc_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 | ||||
| 1507 | cc_result DynamicLib_Load(const cc_string* path, void** lib) { | |||
| 1508 | *lib = DynamicLib_Load2(path); | |||
| 1509 | return *lib == NULL((void*)0); | |||
| 1510 | } | |||
| 1511 | cc_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 | ||||
| 1517 | cc_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 | |||
| 1534 | int 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 | ||||
| 1546 | void 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 | ||||
| 1554 | static 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 | ||||
| 1564 | typedef BOOL (WINAPI *FUNC_AttachConsole)(DWORD dwProcessId); | |||
| 1565 | static 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 | ||||
| 1577 | void 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 | ||||
| 1595 | void Platform_Free(void) { | |||
| 1596 | WSACleanup(); | |||
| 1597 | HeapDestroy(heap); | |||
| 1598 | } | |||
| 1599 | ||||
| 1600 | cc_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 | } | |||
| 1612 | cc_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 | ||||
| 1625 | cc_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 | ||||
| 1638 | cc_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 | |||
| 1642 | int 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 | ||||
| 1656 | void Platform_DecodeString(cc_string* dst, const void* data, int len) { | |||
| 1657 | String_AppendUtf8(dst, (const cc_uint8*)data, len); | |||
| 1658 | } | |||
| 1659 | ||||
| 1660 | static 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 | } | |||
| 1668 | void Platform_Free(void) { } | |||
| 1669 | ||||
| 1670 | cc_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 | } | |||
| 1683 | cc_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 | ||||
| 1688 | cc_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 | |||
| 1706 | static 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 | ||||
| 1716 | void 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 | |||
| 1727 | void 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 | |||
| 1762 | void Platform_Init(void) { Platform_InitPosix(); } | |||
| 1763 | #endif | |||
| 1764 | #endif /* CC_BUILD_POSIX */ | |||
| 1765 | ||||
| 1766 | ||||
| 1767 | /*########################################################################################################################* | |||
| 1768 | *--------------------------------------------------------Platform---------------------------------------------------------* | |||
| 1769 | *#########################################################################################################################*/ | |||
| 1770 | #if defined CC_BUILD_WIN | |||
| 1771 | static 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 | ||||
| 1798 | int 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 | ||||
| 1811 | cc_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 | |||
| 1826 | int 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 | ||||
| 1835 | cc_result Platform_SetDefaultCurrentDirectory(int argc, char **argv) { | |||
| 1836 | return chdir("/classicube") == -1 ? errno(*__errno()) : 0; | |||
| 1837 | } | |||
| 1838 | #elif defined CC_BUILD_ANDROID | |||
| 1839 | int 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 | ||||
| 1844 | cc_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 | |||
| 1854 | int 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 | ||||
| 1878 | cc_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 | |||
| 1922 | jclass App_Class; | |||
| 1923 | jobject App_Instance; | |||
| 1924 | JavaVM* VM_Ptr; | |||
| 1925 | ||||
| 1926 | /* JNI helpers */ | |||
| 1927 | cc_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 | ||||
| 1942 | jobject 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 | ||||
| 1955 | jbyteArray 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 | ||||
| 1962 | void 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 | ||||
| 1967 | jint 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 | ||||
| 1972 | jlong 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 | ||||
| 1977 | jfloat 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 | ||||
| 1982 | jobject 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 | ||||
| 1987 | void 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 | ||||
| 1997 | static 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 | ||||
| 2009 | void 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 | ||||
| 2018 | void 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 |