1 // Copyright 2014 Wouter van Oortmerssen. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 // Misc platform specific stuff.
16
17 #include "lobster/stdafx.h"
18 #include <stdarg.h>
19 #include <time.h>
20
21 #ifdef _WIN32
22 #define VC_EXTRALEAN
23 #define WIN32_LEAN_AND_MEAN
24 #define NOMINMAX
25 #include <windows.h>
26 #define FILESEP '\\'
27 #include <intrin.h>
28 #else
29 #include <sys/time.h>
30 #ifndef PLATFORM_ES3
31 #include <glob.h>
32 #include <sys/stat.h>
33 #endif
34 #define FILESEP '/'
35 #endif
36
37 #ifdef __linux__
38 #include <unistd.h>
39 #endif
40
41 #ifdef __APPLE__
42 #include "CoreFoundation/CoreFoundation.h"
43 #ifndef __IOS__
44 #include <Carbon/Carbon.h>
45 #endif
46 #endif
47
48 #ifdef __ANDROID__
49 #include <android/log.h>
50 #include "sdlincludes.h" // FIXME
51 #endif
52
53 // Dirs to load files relative to, they typically contain, and will be searched in this order:
54 // - The project specific files. This is where the bytecode file you're running or the main
55 // .lobster file you're compiling reside.
56 // - The standard lobster files. On windows this is where lobster.exe resides, on apple
57 // platforms it's the Resource folder in the bundle.
58 // - The same as writedir below (to be able to load files the program has been written).
59 // - Any additional dirs declared with "include from".
60 vector<string> data_dirs;
61
62 // Folder to write to, usually the same as project dir, special folder on mobile platforms.
63 string write_dir;
64
65 string maindir;
66 string projectdir;
ProjectDir()67 string_view ProjectDir() { return projectdir; }
MainDir()68 string_view MainDir() { return maindir; }
69
70 FileLoader cur_loader = nullptr;
71
72 bool have_console = true;
73
74
75 #ifndef _WIN32 // Emulate QPC on *nix, thanks Lee.
76 struct LARGE_INTEGER {
77 long long int QuadPart;
78 };
79
QueryPerformanceCounter(LARGE_INTEGER * dst)80 void QueryPerformanceCounter(LARGE_INTEGER *dst) {
81 struct timeval t;
82 gettimeofday (& t, nullptr);
83 dst->QuadPart = t.tv_sec * 1000000LL + t.tv_usec;
84 }
85
QueryPerformanceFrequency(LARGE_INTEGER * dst)86 void QueryPerformanceFrequency(LARGE_INTEGER *dst) {
87 dst->QuadPart = 1000000LL;
88 }
89 #endif
90
91 static LARGE_INTEGER time_frequency, time_start;
InitTime()92 void InitTime() {
93 QueryPerformanceFrequency(&time_frequency);
94 QueryPerformanceCounter(&time_start);
95 }
96
SecondsSinceStart()97 double SecondsSinceStart() {
98 LARGE_INTEGER end;
99 QueryPerformanceCounter(&end);
100 return double(end.QuadPart - time_start.QuadPart) / double(time_frequency.QuadPart);
101 }
102
103 uint hwthreads = 2, hwcores = 1;
InitCPU()104 void InitCPU() {
105 // This can fail and return 0, so default to 2 threads:
106 hwthreads = max(2U, thread::hardware_concurrency());
107 // As a baseline, assume desktop CPUs are hyperthreaded, and mobile ones are not.
108 #ifdef PLATFORM_ES3
109 hwcores = hwthreads;
110 #else
111 hwcores = max(1U, hwthreads / 2);
112 #endif
113 // On Windows, we can do better and actually count cores.
114 #ifdef _WIN32
115 DWORD buflen = 0;
116 if (!GetLogicalProcessorInformation(nullptr, &buflen) && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
117 vector<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> buf(buflen / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION));
118 if (GetLogicalProcessorInformation(buf.data(), &buflen)) {
119 uint cores = 0;
120 for (auto &lpi : buf) {
121 if (lpi.Relationship == RelationProcessorCore) cores++;
122 }
123 // Only overwrite our baseline if we actually found any cores.
124 if (cores) hwcores = cores;
125 }
126 }
127 #endif
128 }
129
NumHWThreads()130 uint NumHWThreads() { return hwthreads; }
NumHWCores()131 uint NumHWCores() { return hwcores; }
132
133
StripFilePart(string_view filepath)134 string_view StripFilePart(string_view filepath) {
135 auto fpos = filepath.find_last_of(FILESEP);
136 return fpos != string_view::npos ? filepath.substr(0, fpos + 1) : "";
137 }
138
StripDirPart(const char * filepath)139 const char *StripDirPart(const char *filepath) {
140 auto fpos = strrchr(filepath, FILESEP);
141 if (!fpos) fpos = strrchr(filepath, ':');
142 return fpos ? fpos + 1 : filepath;
143 }
144
StripTrailing(string_view in,string_view tail)145 string_view StripTrailing(string_view in, string_view tail) {
146 if (in.size() >= tail.size() && in.substr(in.size() - tail.size()) == tail)
147 return in.substr(0, in.size() - tail.size());
148 return in;
149 }
150
GetMainDirFromExePath(const char * argv_0)151 string GetMainDirFromExePath(const char *argv_0) {
152 string md = SanitizePath(argv_0);
153 #ifdef _WIN32
154 // Windows can pass just the exe name without a full path, which is useless.
155 char winfn[MAX_PATH + 1];
156 GetModuleFileName(NULL, winfn, MAX_PATH + 1);
157 md = winfn;
158 #endif
159 #ifdef __linux__
160 char path[PATH_MAX];
161 ssize_t length = readlink("/proc/self/exe", path, sizeof(path)-1);
162 if (length != -1) {
163 path[length] = '\0';
164 md = string(path);
165 }
166 #endif
167 md = StripTrailing(StripTrailing(StripFilePart(md), "bin/"), "bin\\");
168 return md;
169 }
170
DefaultLoadFile(string_view absfilename,string * dest,int64_t start,int64_t len)171 int64_t DefaultLoadFile(string_view absfilename, string *dest, int64_t start, int64_t len) {
172 LOG_INFO("DefaultLoadFile: ", absfilename);
173 auto f = fopen(null_terminated(absfilename), "rb");
174 if (!f) return -1;
175 if (fseek(f, 0, SEEK_END)) {
176 fclose(f);
177 return -1;
178 }
179 auto filelen = ftell(f);
180 if (!len) { // Just the file length requested.
181 fclose(f);
182 return filelen;
183 }
184 if (len < 0) len = filelen;
185 fseek(f, (long)start, SEEK_SET); // FIXME: 32-bit on WIN32.
186 dest->resize((size_t)len);
187 auto rlen = fread(&(*dest)[0], 1, (size_t)len, f);
188 fclose(f);
189 return len != (int64_t)rlen ? -1 : len;
190 }
191
InitPlatform(string _maindir,const char * auxfilepath,bool from_bundle,FileLoader loader)192 bool InitPlatform(string _maindir, const char *auxfilepath, bool from_bundle,
193 FileLoader loader) {
194 maindir = _maindir;
195 InitTime();
196 InitCPU();
197 cur_loader = loader;
198 // FIXME: use SDL_GetBasePath() instead?
199 #if defined(__APPLE__)
200 if (from_bundle) {
201 have_console = false;
202 // Default data dir is the Resources folder inside the .app bundle.
203 CFBundleRef mainBundle = CFBundleGetMainBundle();
204 CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle);
205 char path[PATH_MAX];
206 auto res = CFURLGetFileSystemRepresentation(resourcesURL, TRUE, (UInt8 *)path,
207 PATH_MAX);
208 CFRelease(resourcesURL);
209 if (!res)
210 return false;
211 auto resources_dir = string(path) + "/";
212 data_dirs.push_back(resources_dir);
213 #ifdef __IOS__
214 // There's probably a better way to do this in CF.
215 write_dir = StripFilePart(path) + "Documents/";
216 data_dirs.push_back(write_dir);
217 #else
218 // FIXME: This should probably be ~/Library/Application Support/AppName,
219 // but for now this works for non-app store apps.
220 write_dir = resources_dir;
221 #endif
222 return true;
223 }
224 #else
225 (void)from_bundle;
226 #endif
227 #if defined(__ANDROID__)
228 // FIXME: removed SDL dependency for other platforms. So on Android, move calls to
229 // SDL_AndroidGetInternalStoragePath into SDL and pass the results into here somehow.
230 SDL_Init(0); // FIXME: Is this needed? bad dependency.
231 auto internalstoragepath = SDL_AndroidGetInternalStoragePath();
232 auto externalstoragepath = SDL_AndroidGetExternalStoragePath();
233 LOG_INFO(internalstoragepath);
234 LOG_INFO(externalstoragepath);
235 if (internalstoragepath) data_dirs.push_back(internalstoragepath + string_view("/"));
236 if (externalstoragepath) write_dir = externalstoragepath + string_view("/");
237 // For some reason, the above SDL functionality doesn't actually work,
238 // we have to use the relative path only to access APK files:
239 data_dirs.clear(); // FIXME.
240 data_dirs.push_back("");
241 data_dirs.push_back(write_dir);
242 #else // Linux, Windows, and OS X console mode.
243
244 if (auxfilepath) {
245 projectdir = StripFilePart(SanitizePath(auxfilepath));
246 data_dirs.push_back(projectdir);
247 write_dir = projectdir;
248 } else {
249 write_dir = maindir;
250 }
251 data_dirs.push_back(maindir);
252 #ifdef PLATFORM_DATADIR
253 data_dirs.push_back(PLATFORM_DATADIR);
254 #endif
255 #ifdef _WIN32
256 have_console = GetConsoleWindow() != nullptr;
257 #endif
258 #endif
259 return true;
260 }
261
AddDataDir(string_view path)262 void AddDataDir(string_view path) {
263 for (auto &dir : data_dirs) if (dir == path) return;
264 data_dirs.push_back(projectdir + SanitizePath(path));
265 }
266
SanitizePath(string_view path)267 string SanitizePath(string_view path) {
268 string r;
269 for (auto c : path) {
270 r += c == '\\' || c == '/' ? FILESEP : c;
271 }
272 return r;
273 }
274
275 map<string, tuple<string, int64_t, int64_t, int64_t>, less<>> pakfile_registry;
276
AddPakFileEntry(string_view pakfilename,string_view relfilename,int64_t off,int64_t len,int64_t uncompressed)277 void AddPakFileEntry(string_view pakfilename, string_view relfilename, int64_t off,
278 int64_t len, int64_t uncompressed) {
279 pakfile_registry[string(relfilename)] = make_tuple(pakfilename, off, len, uncompressed);
280 }
281
LoadFileFromAny(string_view srelfilename,string * dest,int64_t start,int64_t len)282 int64_t LoadFileFromAny(string_view srelfilename, string *dest, int64_t start, int64_t len) {
283 for (auto &dir : data_dirs) {
284 auto l = cur_loader(dir + srelfilename, dest, start, len);
285 if (l >= 0) return l;
286 }
287 return -1;
288 }
289
290 // We don't generally load in ways that allow stdio text mode conversions, so this function
291 // emulates them at best effort.
TextModeConvert(string & s,bool binary)292 void TextModeConvert(string &s, bool binary) {
293 if (binary) return;
294 #ifdef _WIN32
295 s.erase(remove(s.begin(), s.end(), '\r'), s.end());
296 #endif
297 }
298
LoadFile(string_view relfilename,string * dest,int64_t start,int64_t len,bool binary)299 int64_t LoadFile(string_view relfilename, string *dest, int64_t start, int64_t len, bool binary) {
300 assert(cur_loader);
301 auto it = pakfile_registry.find(relfilename);
302 if (it != pakfile_registry.end()) {
303 auto &[fname, foff, flen, funcompressed] = it->second;
304 auto l = LoadFileFromAny(fname, dest, foff, flen);
305 if (l >= 0) {
306 if (funcompressed >= 0) {
307 string uncomp;
308 WEntropyCoder<false>((const uchar *)dest->c_str(), dest->length(),
309 (size_t)funcompressed, uncomp);
310 dest->swap(uncomp);
311 TextModeConvert(*dest, binary);
312 return funcompressed;
313 } else {
314 TextModeConvert(*dest, binary);
315 return l;
316 }
317 }
318 }
319 if (len > 0) LOG_INFO("load: ", relfilename);
320 auto size = LoadFileFromAny(SanitizePath(relfilename), dest, start, len);
321 TextModeConvert(*dest, binary);
322 return size;
323 }
324
WriteFileName(string_view relfilename)325 string WriteFileName(string_view relfilename) {
326 return write_dir + SanitizePath(relfilename);
327 }
328
OpenForWriting(string_view relfilename,bool binary)329 FILE *OpenForWriting(string_view relfilename, bool binary) {
330 auto fn = WriteFileName(relfilename);
331 LOG_INFO("write: ", fn);
332 return fopen(fn.c_str(), binary ? "wb" : "w");
333 }
334
WriteFile(string_view relfilename,bool binary,string_view contents)335 bool WriteFile(string_view relfilename, bool binary, string_view contents) {
336 FILE *f = OpenForWriting(relfilename, binary);
337 size_t written = 0;
338 if (f) {
339 written = fwrite(contents.data(), contents.size(), 1, f);
340 fclose(f);
341 }
342 return written == 1;
343 }
344
FileExists(string_view relfilename)345 bool FileExists(string_view relfilename) {
346 auto f = fopen(WriteFileName(relfilename).c_str(), "rb");
347 if (f) fclose(f);
348 return f;
349 }
350
FileDelete(string_view relfilename)351 bool FileDelete(string_view relfilename) {
352 return remove(WriteFileName(relfilename).c_str()) == 0;
353 }
354
355 // TODO: can now replace all this platform specific stuff with std::filesystem code.
356 // https://github.com/tvaneerd/cpp17_in_TTs/blob/master/ALL_IN_ONE.md
357 // http://en.cppreference.com/w/cpp/experimental/fs
ScanDirAbs(string_view absdir,vector<pair<string,int64_t>> & dest)358 bool ScanDirAbs(string_view absdir, vector<pair<string, int64_t>> &dest) {
359 string folder = SanitizePath(absdir);
360 #ifdef _WIN32
361 WIN32_FIND_DATA fdata;
362 HANDLE fh = FindFirstFile((folder + "\\*.*").c_str(), &fdata);
363 if (fh != INVALID_HANDLE_VALUE) {
364 do {
365 if (strcmp(fdata.cFileName, ".") && strcmp(fdata.cFileName, "..")) {
366 ULONGLONG size =
367 (static_cast<ULONGLONG>(fdata.nFileSizeHigh) << (sizeof(uint) * 8)) |
368 fdata.nFileSizeLow;
369 dest.push_back(
370 { fdata.cFileName,
371 fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY
372 ? -1
373 : (int64_t)size });
374 }
375 }
376 while(FindNextFile(fh, &fdata));
377 FindClose(fh);
378 return true;
379 }
380 #elif !defined(PLATFORM_ES3)
381 glob_t gl;
382 string mask = folder + "/*";
383 if (!glob(mask.c_str(), GLOB_MARK | GLOB_TILDE, nullptr, &gl)) {
384 for (size_t fi = 0; fi < gl.gl_pathc; fi++) {
385 string xFileName = gl.gl_pathv[fi];
386 bool isDir = xFileName[xFileName.length()-1] == '/';
387 if (isDir) xFileName = xFileName.substr(0, xFileName.length() - 1);
388 string cFileName = xFileName.substr(xFileName.find_last_of('/') + 1);
389 struct stat st;
390 stat(gl.gl_pathv[fi], &st);
391 dest.push_back({ cFileName, isDir ? -1 : (int64_t)st.st_size });
392 }
393 globfree(&gl);
394 return true;
395 }
396 #endif
397 return false;
398 }
399
ScanDir(string_view reldir,vector<pair<string,int64_t>> & dest)400 bool ScanDir(string_view reldir, vector<pair<string, int64_t>> &dest) {
401 auto srfn = SanitizePath(reldir);
402 for (auto &dir : data_dirs) {
403 if (ScanDirAbs(dir + srfn, dest)) return true;
404 }
405 return false;
406 }
407
408 OutputType min_output_level = OUTPUT_WARN;
409
LogOutput(OutputType ot,const char * buf)410 void LogOutput(OutputType ot, const char *buf) {
411 if (ot < min_output_level) return;
412 #ifdef __ANDROID__
413 auto tag = "lobster";
414 switch (ot) {
415 case OUTPUT_DEBUG: __android_log_print(ANDROID_LOG_DEBUG, tag, "%s", buf); break;
416 case OUTPUT_INFO: __android_log_print(ANDROID_LOG_INFO, tag, "%s", buf); break;
417 case OUTPUT_WARN: __android_log_print(ANDROID_LOG_WARN, tag, "%s", buf); break;
418 case OUTPUT_PROGRAM: __android_log_print(ANDROID_LOG_ERROR, tag, "%s", buf); break;
419 case OUTPUT_ERROR: __android_log_print(ANDROID_LOG_ERROR, tag, "%s", buf); break;
420 }
421 #elif defined(_WIN32)
422 OutputDebugStringA(buf);
423 OutputDebugStringA("\n");
424 if (ot >= OUTPUT_INFO) {
425 printf("%s\n", buf);
426 }
427 #elif defined(__IOS__)
428 extern void IOSLog(const char *msg);
429 IOSLog(buf);
430 #else
431 printf("%s\n", buf);
432 #endif
433 if (!have_console) {
434 auto f = fopen((maindir + "lobster.exe.con.log").c_str(), "a");
435 if (f) {
436 fputs(buf, f);
437 fputs("\n", f);
438 fclose(f);
439 }
440 }
441 }
442
443 // Use this instead of assert to break on a condition and still be able to continue in the debugger.
ConditionalBreakpoint(bool shouldbreak)444 void ConditionalBreakpoint(bool shouldbreak) {
445 if (shouldbreak) {
446 #ifdef _WIN32
447 __debugbreak();
448 #elif __GCC__
449 __builtin_trap();
450 #endif
451 }
452 }
453
454 // Insert without args to find out which iteration it gets to, then insert that iteration number.
CountingBreakpoint(int i)455 void CountingBreakpoint(int i) {
456 static int j = 0;
457 if (i < 0) LOG_DEBUG("counting breakpoint: ", j);
458 ConditionalBreakpoint(j++ == i);
459 }
460
MakeDPIAware()461 void MakeDPIAware() {
462 #ifdef _WIN32
463 // Without this, Windows scales the GL window if scaling is set in display settings.
464 #ifndef DPI_ENUMS_DECLARED
465 typedef enum PROCESS_DPI_AWARENESS
466 {
467 PROCESS_DPI_UNAWARE = 0,
468 PROCESS_SYSTEM_DPI_AWARE = 1,
469 PROCESS_PER_MONITOR_DPI_AWARE = 2
470 } PROCESS_DPI_AWARENESS;
471 #endif
472
473 typedef BOOL (WINAPI * SETPROCESSDPIAWARE_T)(void);
474 typedef HRESULT (WINAPI * SETPROCESSDPIAWARENESS_T)(PROCESS_DPI_AWARENESS);
475 HMODULE shcore = LoadLibraryA("Shcore.dll");
476 SETPROCESSDPIAWARENESS_T SetProcessDpiAwareness = NULL;
477 if (shcore) {
478 SetProcessDpiAwareness =
479 (SETPROCESSDPIAWARENESS_T)GetProcAddress(shcore, "SetProcessDpiAwareness");
480 }
481 HMODULE user32 = LoadLibraryA("User32.dll");
482 SETPROCESSDPIAWARE_T SetProcessDPIAware = NULL;
483 if (user32) {
484 SetProcessDPIAware =
485 (SETPROCESSDPIAWARE_T)GetProcAddress(user32, "SetProcessDPIAware");
486 }
487
488 if (SetProcessDpiAwareness) {
489 SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
490 } else if (SetProcessDPIAware) {
491 SetProcessDPIAware();
492 }
493
494 if (user32) FreeLibrary(user32);
495 if (shcore) FreeLibrary(shcore);
496 #endif
497 }
498
GetDateTime()499 string GetDateTime() {
500 time_t t;
501 time(&t);
502 auto tm = localtime(&t);
503 char buf[1024];
504 strftime(buf, sizeof(buf), "%F-%H-%M-%S", tm);
505 return buf;
506 }
507
SetConsole(bool on)508 void SetConsole(bool on) {
509 have_console = on;
510 #ifdef _WIN32
511 if (on) AllocConsole();
512 else FreeConsole();
513 #endif
514 }
515