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