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