1 //
2 // Copyright (c) 2008-2017 the Urho3D project.
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 //
22 
23 #include "../Precompiled.h"
24 
25 #include "../Core/ProcessUtils.h"
26 #include "../Core/StringUtils.h"
27 #include "../IO/FileSystem.h"
28 
29 #include <cstdio>
30 #include <fcntl.h>
31 
32 #ifdef __APPLE__
33 #include "TargetConditionals.h"
34 #endif
35 
36 #if defined(IOS)
37 #include <mach/mach_host.h>
38 #elif defined(TVOS)
39 extern "C" unsigned SDL_TVOS_GetActiveProcessorCount();
40 #elif !defined(__linux__) && !defined(__EMSCRIPTEN__)
41 #include <LibCpuId/libcpuid.h>
42 #endif
43 
44 #if defined(_WIN32)
45 #include <windows.h>
46 #include <io.h>
47 #if defined(_MSC_VER)
48 #include <float.h>
49 #include <Lmcons.h> // For UNLEN.
50 #elif defined(__MINGW32__)
51 #include <lmcons.h> // For UNLEN. Apparently MSVC defines "<Lmcons.h>" (with an upperscore 'L' but MinGW uses an underscore 'l').
52 #include <ntdef.h>
53 #endif
54 #elif defined(__linux__) && !defined(__ANDROID__)
55 #include <pwd.h>
56 #include <sys/sysinfo.h>
57 #include <sys/utsname.h>
58 #elif defined(__APPLE__)
59 #include <sys/sysctl.h>
60 #include <SystemConfiguration/SystemConfiguration.h> // For the detection functions inside GetLoginName().
61 #endif
62 #ifndef _WIN32
63 #include <unistd.h>
64 #endif
65 
66 #if defined(__EMSCRIPTEN__) && defined(__EMSCRIPTEN_PTHREADS__)
67 #include <emscripten/threading.h>
68 #endif
69 
70 #if defined(__i386__)
71 // From http://stereopsis.com/FPU.html
72 
73 #define FPU_CW_PREC_MASK        0x0300
74 #define FPU_CW_PREC_SINGLE      0x0000
75 #define FPU_CW_PREC_DOUBLE      0x0200
76 #define FPU_CW_PREC_EXTENDED    0x0300
77 #define FPU_CW_ROUND_MASK       0x0c00
78 #define FPU_CW_ROUND_NEAR       0x0000
79 #define FPU_CW_ROUND_DOWN       0x0400
80 #define FPU_CW_ROUND_UP         0x0800
81 #define FPU_CW_ROUND_CHOP       0x0c00
82 
GetFPUState()83 inline unsigned GetFPUState()
84 {
85     unsigned control = 0;
86     __asm__ __volatile__ ("fnstcw %0" : "=m" (control));
87     return control;
88 }
89 
SetFPUState(unsigned control)90 inline void SetFPUState(unsigned control)
91 {
92     __asm__ __volatile__ ("fldcw %0" : : "m" (control));
93 }
94 #endif
95 
96 #ifndef MINI_URHO
97 #include <SDL/SDL.h>
98 #endif
99 
100 #include "../DebugNew.h"
101 
102 namespace Urho3D
103 {
104 
105 #ifdef _WIN32
106 static bool consoleOpened = false;
107 #endif
108 static String currentLine;
109 static Vector<String> arguments;
110 static String miniDumpDir;
111 
112 #if defined(IOS)
GetCPUData(host_basic_info_data_t * data)113 static void GetCPUData(host_basic_info_data_t* data)
114 {
115     mach_msg_type_number_t infoCount;
116     infoCount = HOST_BASIC_INFO_COUNT;
117     host_info(mach_host_self(), HOST_BASIC_INFO, (host_info_t)data, &infoCount);
118 }
119 #elif defined(__linux__)
120 struct CpuCoreCount
121 {
122     unsigned numPhysicalCores_;
123     unsigned numLogicalCores_;
124 };
125 
126 // This function is used by all the target triplets with Linux as the OS, such as Android, RPI, desktop Linux, etc
GetCPUData(struct CpuCoreCount * data)127 static void GetCPUData(struct CpuCoreCount* data)
128 {
129     // Sanity check
130     assert(data);
131     // At least return 1 core
132     data->numPhysicalCores_ = data->numLogicalCores_ = 1;
133 
134     FILE* fp;
135     int res;
136     unsigned i, j;
137 
138     fp = fopen("/sys/devices/system/cpu/present", "r");
139     if (fp)
140     {
141         res = fscanf(fp, "%d-%d", &i, &j);
142         fclose(fp);
143 
144         if (res == 2 && i == 0)
145         {
146             data->numPhysicalCores_ = data->numLogicalCores_ = j + 1;
147 
148             fp = fopen("/sys/devices/system/cpu/cpu0/topology/thread_siblings_list", "r");
149             if (fp)
150             {
151                 res = fscanf(fp, "%d,%d,%d,%d", &i, &j, &i, &j);
152                 fclose(fp);
153 
154                 // Having sibling thread(s) indicates the CPU is using HT/SMT technology
155                 if (res > 1)
156                     data->numPhysicalCores_ /= res;
157             }
158         }
159     }
160 }
161 
162 #elif !defined(__EMSCRIPTEN__) && !defined(TVOS)
GetCPUData(struct cpu_id_t * data)163 static void GetCPUData(struct cpu_id_t* data)
164 {
165     if (cpu_identify(0, data) < 0)
166     {
167         data->num_logical_cpus = 1;
168         data->num_cores = 1;
169     }
170 }
171 #endif
172 
InitFPU()173 void InitFPU()
174 {
175     // Make sure FPU is in round-to-nearest, single precision mode
176     // This ensures Direct3D and OpenGL behave similarly, and all threads behave similarly
177 #if defined(_MSC_VER) && defined(_M_IX86)
178     _controlfp(_RC_NEAR | _PC_24, _MCW_RC | _MCW_PC);
179 #elif defined(__i386__)
180     unsigned control = GetFPUState();
181     control &= ~(FPU_CW_PREC_MASK | FPU_CW_ROUND_MASK);
182     control |= (FPU_CW_PREC_SINGLE | FPU_CW_ROUND_NEAR);
183     SetFPUState(control);
184 #endif
185 }
186 
ErrorDialog(const String & title,const String & message)187 void ErrorDialog(const String& title, const String& message)
188 {
189 #ifndef MINI_URHO
190     SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title.CString(), message.CString(), 0);
191 #endif
192 }
193 
ErrorExit(const String & message,int exitCode)194 void ErrorExit(const String& message, int exitCode)
195 {
196     if (!message.Empty())
197         PrintLine(message, true);
198 
199     exit(exitCode);
200 }
201 
OpenConsoleWindow()202 void OpenConsoleWindow()
203 {
204 #ifdef _WIN32
205     if (consoleOpened)
206         return;
207 
208     AllocConsole();
209 
210     freopen("CONIN$", "r", stdin);
211     freopen("CONOUT$", "w", stdout);
212 
213     consoleOpened = true;
214 #endif
215 }
216 
PrintUnicode(const String & str,bool error)217 void PrintUnicode(const String& str, bool error)
218 {
219 #if !defined(__ANDROID__) && !defined(IOS) && !defined(TVOS)
220 #ifdef _WIN32
221     // If the output stream has been redirected, use fprintf instead of WriteConsoleW,
222     // though it means that proper Unicode output will not work
223     FILE* out = error ? stderr : stdout;
224     if (!_isatty(_fileno(out)))
225         fprintf(out, "%s", str.CString());
226     else
227     {
228         HANDLE stream = GetStdHandle(error ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE);
229         if (stream == INVALID_HANDLE_VALUE)
230             return;
231         WString strW(str);
232         DWORD charsWritten;
233         WriteConsoleW(stream, strW.CString(), strW.Length(), &charsWritten, 0);
234     }
235 #else
236     fprintf(error ? stderr : stdout, "%s", str.CString());
237 #endif
238 #endif
239 }
240 
PrintUnicodeLine(const String & str,bool error)241 void PrintUnicodeLine(const String& str, bool error)
242 {
243     PrintUnicode(str + "\n", error);
244 }
245 
PrintLine(const String & str,bool error)246 void PrintLine(const String& str, bool error)
247 {
248 #if !defined(__ANDROID__) && !defined(IOS) && !defined(TVOS)
249     fprintf(error ? stderr : stdout, "%s\n", str.CString());
250 #endif
251 }
252 
ParseArguments(const String & cmdLine,bool skipFirstArgument)253 const Vector<String>& ParseArguments(const String& cmdLine, bool skipFirstArgument)
254 {
255     arguments.Clear();
256 
257     unsigned cmdStart = 0, cmdEnd = 0;
258     bool inCmd = false;
259     bool inQuote = false;
260 
261     for (unsigned i = 0; i < cmdLine.Length(); ++i)
262     {
263         if (cmdLine[i] == '\"')
264             inQuote = !inQuote;
265         if (cmdLine[i] == ' ' && !inQuote)
266         {
267             if (inCmd)
268             {
269                 inCmd = false;
270                 cmdEnd = i;
271                 // Do not store the first argument (executable name)
272                 if (!skipFirstArgument)
273                     arguments.Push(cmdLine.Substring(cmdStart, cmdEnd - cmdStart));
274                 skipFirstArgument = false;
275             }
276         }
277         else
278         {
279             if (!inCmd)
280             {
281                 inCmd = true;
282                 cmdStart = i;
283             }
284         }
285     }
286     if (inCmd)
287     {
288         cmdEnd = cmdLine.Length();
289         if (!skipFirstArgument)
290             arguments.Push(cmdLine.Substring(cmdStart, cmdEnd - cmdStart));
291     }
292 
293     // Strip double quotes from the arguments
294     for (unsigned i = 0; i < arguments.Size(); ++i)
295         arguments[i].Replace("\"", "");
296 
297     return arguments;
298 }
299 
ParseArguments(const char * cmdLine)300 const Vector<String>& ParseArguments(const char* cmdLine)
301 {
302     return ParseArguments(String(cmdLine));
303 }
304 
ParseArguments(const WString & cmdLine)305 const Vector<String>& ParseArguments(const WString& cmdLine)
306 {
307     return ParseArguments(String(cmdLine));
308 }
309 
ParseArguments(const wchar_t * cmdLine)310 const Vector<String>& ParseArguments(const wchar_t* cmdLine)
311 {
312     return ParseArguments(String(cmdLine));
313 }
314 
ParseArguments(int argc,char ** argv)315 const Vector<String>& ParseArguments(int argc, char** argv)
316 {
317     String cmdLine;
318 
319     for (int i = 0; i < argc; ++i)
320         cmdLine.AppendWithFormat("\"%s\" ", (const char*)argv[i]);
321 
322     return ParseArguments(cmdLine);
323 }
324 
GetArguments()325 const Vector<String>& GetArguments()
326 {
327     return arguments;
328 }
329 
GetConsoleInput()330 String GetConsoleInput()
331 {
332     String ret;
333 #ifdef URHO3D_TESTING
334     // When we are running automated tests, reading the console may block. Just return empty in that case
335     return ret;
336 #else
337 #ifdef _WIN32
338     HANDLE input = GetStdHandle(STD_INPUT_HANDLE);
339     HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);
340     if (input == INVALID_HANDLE_VALUE || output == INVALID_HANDLE_VALUE)
341         return ret;
342 
343     // Use char-based input
344     SetConsoleMode(input, ENABLE_PROCESSED_INPUT);
345 
346     INPUT_RECORD record;
347     DWORD events = 0;
348     DWORD readEvents = 0;
349 
350     if (!GetNumberOfConsoleInputEvents(input, &events))
351         return ret;
352 
353     while (events--)
354     {
355         ReadConsoleInputW(input, &record, 1, &readEvents);
356         if (record.EventType == KEY_EVENT && record.Event.KeyEvent.bKeyDown)
357         {
358             unsigned c = record.Event.KeyEvent.uChar.UnicodeChar;
359             if (c)
360             {
361                 if (c == '\b')
362                 {
363                     PrintUnicode("\b \b");
364                     int length = currentLine.LengthUTF8();
365                     if (length)
366                         currentLine = currentLine.SubstringUTF8(0, length - 1);
367                 }
368                 else if (c == '\r')
369                 {
370                     PrintUnicode("\n");
371                     ret = currentLine;
372                     currentLine.Clear();
373                     return ret;
374                 }
375                 else
376                 {
377                     // We have disabled echo, so echo manually
378                     wchar_t out = c;
379                     DWORD charsWritten;
380                     WriteConsoleW(output, &out, 1, &charsWritten, 0);
381                     currentLine.AppendUTF8(c);
382                 }
383             }
384         }
385     }
386 #elif !defined(__ANDROID__) && !defined(IOS) && !defined(TVOS)
387     int flags = fcntl(STDIN_FILENO, F_GETFL);
388     fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
389     for (;;)
390     {
391         int ch = fgetc(stdin);
392         if (ch >= 0 && ch != '\n')
393             ret += (char)ch;
394         else
395             break;
396     }
397 #endif
398 
399     return ret;
400 #endif
401 }
402 
GetPlatform()403 String GetPlatform()
404 {
405 #if defined(__ANDROID__)
406     return "Android";
407 #elif defined(IOS)
408     return "iOS";
409 #elif defined(TVOS)
410     return "tvOS";
411 #elif defined(__APPLE__)
412     return "macOS";
413 #elif defined(_WIN32)
414     return "Windows";
415 #elif defined(RPI)
416     return "Raspberry Pi";
417 #elif defined(__EMSCRIPTEN__)
418     return "Web";
419 #elif defined(__linux__)
420     return "Linux";
421 #else
422     return "(?)";
423 #endif
424 }
425 
GetNumPhysicalCPUs()426 unsigned GetNumPhysicalCPUs()
427 {
428 #if defined(IOS)
429     host_basic_info_data_t data;
430     GetCPUData(&data);
431 #if defined(TARGET_OS_SIMULATOR)
432     // Hardcoded to dual-core on simulator mode even if the host has more
433     return Min(2, data.physical_cpu);
434 #else
435     return data.physical_cpu;
436 #endif
437 #elif defined(TVOS)
438 #if defined(TARGET_OS_SIMULATOR)
439     return Min(2, SDL_TVOS_GetActiveProcessorCount());
440 #else
441     return SDL_TVOS_GetActiveProcessorCount();
442 #endif
443 #elif defined(__linux__)
444     struct CpuCoreCount data;
445     GetCPUData(&data);
446     return data.numPhysicalCores_;
447 #elif defined(__EMSCRIPTEN__)
448 #ifdef __EMSCRIPTEN_PTHREADS__
449     return emscripten_num_logical_cores();
450 #else
451     return 1; // Targeting a single-threaded Emscripten build.
452 #endif
453 #else
454     struct cpu_id_t data;
455     GetCPUData(&data);
456     return (unsigned)data.num_cores;
457 #endif
458 }
459 
GetNumLogicalCPUs()460 unsigned GetNumLogicalCPUs()
461 {
462 #if defined(IOS)
463     host_basic_info_data_t data;
464     GetCPUData(&data);
465 #if defined(TARGET_OS_SIMULATOR)
466     return Min(2, data.logical_cpu);
467 #else
468     return data.logical_cpu;
469 #endif
470 #elif defined(TVOS)
471 #if defined(TARGET_OS_SIMULATOR)
472     return Min(2, SDL_TVOS_GetActiveProcessorCount());
473 #else
474     return SDL_TVOS_GetActiveProcessorCount();
475 #endif
476 #elif defined(__linux__)
477     struct CpuCoreCount data;
478     GetCPUData(&data);
479     return data.numLogicalCores_;
480 #elif defined(__EMSCRIPTEN__)
481 #ifdef __EMSCRIPTEN_PTHREADS__
482     return emscripten_num_logical_cores();
483 #else
484     return 1; // Targeting a single-threaded Emscripten build.
485 #endif
486 #else
487     struct cpu_id_t data;
488     GetCPUData(&data);
489     return (unsigned)data.num_logical_cpus;
490 #endif
491 }
492 
SetMiniDumpDir(const String & pathName)493 void SetMiniDumpDir(const String& pathName)
494 {
495     miniDumpDir = AddTrailingSlash(pathName);
496 }
497 
GetMiniDumpDir()498 String GetMiniDumpDir()
499 {
500 #ifndef MINI_URHO
501     if (miniDumpDir.Empty())
502     {
503         char* pathName = SDL_GetPrefPath("urho3d", "crashdumps");
504         if (pathName)
505         {
506             String ret(pathName);
507             SDL_free(pathName);
508             return ret;
509         }
510     }
511 #endif
512 
513     return miniDumpDir;
514 }
515 
GetTotalMemory()516 unsigned long long GetTotalMemory()
517 {
518 #if defined(__linux__) && !defined(__ANDROID__)
519     struct sysinfo s;
520     if (sysinfo(&s) != -1)
521         return s.totalram;
522 #elif defined(_WIN32)
523     MEMORYSTATUSEX state;
524     state.dwLength = sizeof(state);
525     if (GlobalMemoryStatusEx(&state))
526         return state.ullTotalPhys;
527 #elif defined(__APPLE__)
528     unsigned long long memSize;
529     size_t len = sizeof(memSize);
530     int mib[2];
531     mib[0] = CTL_HW;
532     mib[1] = HW_MEMSIZE;
533     sysctl(mib, 2, &memSize, &len, NULL, 0);
534     return memSize;
535 #endif
536     return 0ull;
537 }
538 
GetLoginName()539 String GetLoginName()
540 {
541 #if defined(__linux__) && !defined(__ANDROID__)
542     struct passwd *p = getpwuid(getuid());
543     if (p != NULL)
544         return p->pw_name;
545 #elif defined(_WIN32)
546     char name[UNLEN + 1];
547     DWORD len = UNLEN + 1;
548     if (GetUserName(name, &len))
549         return name;
550 #elif defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
551     SCDynamicStoreRef s = SCDynamicStoreCreate(NULL, CFSTR("GetConsoleUser"), NULL, NULL);
552     if (s != NULL)
553     {
554         uid_t u;
555         CFStringRef n = SCDynamicStoreCopyConsoleUser(s, &u, NULL);
556         CFRelease(s);
557         if (n != NULL)
558         {
559             char name[256];
560             Boolean b = CFStringGetCString(n, name, 256, kCFStringEncodingUTF8);
561             CFRelease(n);
562 
563             if (b == true)
564                 return name;
565         }
566     }
567 #endif
568     return "(?)";
569 }
570 
GetHostName()571 String GetHostName()
572 {
573 #if (defined(__linux__) || defined(__APPLE__)) && !defined(__ANDROID__)
574     char buffer[256];
575     if (gethostname(buffer, 256) == 0)
576         return buffer;
577 #elif defined(_WIN32)
578     char buffer[MAX_COMPUTERNAME_LENGTH + 1];
579     DWORD len = MAX_COMPUTERNAME_LENGTH + 1;
580     if (GetComputerName(buffer, &len))
581         return buffer;
582 #endif
583     return "(?)";
584 }
585 
586 // Disable Windows OS version functionality when compiling mini version for Web, see https://github.com/urho3d/Urho3D/issues/1998
587 #if defined(_WIN32) && defined(HAVE_RTL_OSVERSIONINFOW) && !defined(MINI_URHO)
588 typedef NTSTATUS (WINAPI *RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
589 
GetOS(RTL_OSVERSIONINFOW * r)590 static void GetOS(RTL_OSVERSIONINFOW *r)
591 {
592     HMODULE m = GetModuleHandle("ntdll.dll");
593     if (m)
594     {
595         RtlGetVersionPtr fPtr = (RtlGetVersionPtr) GetProcAddress(m, "RtlGetVersion");
596         if (r && fPtr && fPtr(r) == 0)
597             r->dwOSVersionInfoSize = sizeof *r;
598     }
599 }
600 #endif
601 
GetOSVersion()602 String GetOSVersion()
603 {
604 #if defined(__linux__) && !defined(__ANDROID__)
605     struct utsname u;
606     if (uname(&u) == 0)
607         return String(u.sysname) + " " + u.release;
608 #elif defined(_WIN32) && defined(HAVE_RTL_OSVERSIONINFOW) && !defined(MINI_URHO)
609     RTL_OSVERSIONINFOW r;
610     GetOS(&r);
611     // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724832(v=vs.85).aspx
612     if (r.dwMajorVersion == 5 && r.dwMinorVersion == 0)
613         return "Windows 2000";
614     else if (r.dwMajorVersion == 5 && r.dwMinorVersion == 1)
615         return "Windows XP";
616     else if (r.dwMajorVersion == 5 && r.dwMinorVersion == 2)
617         return "Windows XP 64-Bit Edition/Windows Server 2003/Windows Server 2003 R2";
618     else if (r.dwMajorVersion == 6 && r.dwMinorVersion == 0)
619         return "Windows Vista/Windows Server 2008";
620     else if (r.dwMajorVersion == 6 && r.dwMinorVersion == 1)
621         return "Windows 7/Windows Server 2008 R2";
622     else if (r.dwMajorVersion == 6 && r.dwMinorVersion == 2)
623         return "Windows 8/Windows Server 2012";
624     else if (r.dwMajorVersion == 6 && r.dwMinorVersion == 3)
625         return "Windows 8.1/Windows Server 2012 R2";
626     else if (r.dwMajorVersion == 10 && r.dwMinorVersion == 0)
627         return "Windows 10/Windows Server 2016";
628     else
629         return "Windows Unknown";
630 #elif defined(__APPLE__)
631     char kernel_r[256];
632     size_t size = sizeof(kernel_r);
633 
634     if (sysctlbyname("kern.osrelease", &kernel_r, &size, NULL, 0) != -1)
635     {
636         Vector<String> kernel_version = String(kernel_r).Split('.');
637         String version = "macOS/Mac OS X ";
638         int major = ToInt(kernel_version[0]);
639         int minor = ToInt(kernel_version[1]);
640 
641         // https://en.wikipedia.org/wiki/Darwin_(operating_system)
642         if (major == 16) // macOS Sierra
643         {
644             version += "Sierra ";
645             switch(minor)
646             {
647                 case 0: version += "10.12.0 "; break;
648                 case 1: version += "10.12.1 "; break;
649                 case 3: version += "10.12.2 "; break;
650             }
651         }
652         else if (major == 15) // OS X El Capitan
653         {
654             version += "El Capitan ";
655             switch(minor)
656             {
657                 case 0: version += "10.11.0 "; break;
658                 case 6: version += "10.11.6 "; break;
659             }
660         }
661         else if (major == 14) // OS X Yosemite
662         {
663             version += "Yosemite ";
664             switch(minor)
665             {
666                 case 0: version += "10.10.0 "; break;
667                 case 5: version += "10.10.5 "; break;
668             }
669         }
670         else if (major == 13) // OS X Mavericks
671         {
672             version += "Mavericks ";
673             switch(minor)
674             {
675                 case 0: version += "10.9.0 "; break;
676                 case 4: version += "10.9.5 "; break;
677             }
678         }
679         else if (major == 12) // OS X Mountain Lion
680         {
681             version += "Mountain Lion ";
682             switch(minor)
683             {
684                 case 0: version += "10.8.0 "; break;
685                 case 6: version += "10.8.5 "; break;
686             }
687         }
688         else if (major == 11) // Mac OS X Lion
689         {
690             version += "Lion ";
691             switch(minor)
692             {
693                 case 0: version += "10.7.0 "; break;
694                 case 4: version += "10.7.5 "; break;
695             }
696         }
697         else
698         {
699             version += "Unknown ";
700         }
701 
702         return version + " (Darwin kernel " + kernel_version[0] + "." + kernel_version[1] + "." + kernel_version[2] + ")";
703     }
704 #endif
705     return "(?)";
706 }
707 
708 }
709