1/* RetroArch - A frontend for libretro.
2 * Copyright (C) 2010-2014 - Hans-Kristian Arntzen
3 * Copyright (C) 2011-2017 - Daniel De Matteis
4 * Copyright (C) 2012-2014 - Jason Fetters
5 * Copyright (C) 2014-2015 - Jay McCarthy
6 *
7 * RetroArch is free software: you can redistribute it and/or modify it under the terms
8 * of the GNU General Public License as published by the Free Software Found-
9 * ation, either version 3 of the License, or (at your option) any later version.
10 *
11 * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
12 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
13 * PURPOSE. See the GNU General Public License for more details.
14 * * You should have received a copy of the GNU General Public License along with RetroArch.
15 * If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include <stdint.h>
19#include <stddef.h>
20#include <string.h>
21#include <unistd.h>
22
23#include <sys/utsname.h>
24
25#include <mach/mach_host.h>
26
27#include <CoreFoundation/CoreFoundation.h>
28#include <CoreFoundation/CFArray.h>
29
30#ifdef HAVE_CONFIG_H
31#include "../../config.h"
32#endif
33
34#ifdef __OBJC__
35#include <Foundation/NSPathUtilities.h>
36#include <objc/message.h>
37#endif
38
39#if defined(OSX)
40#include <Carbon/Carbon.h>
41#include <IOKit/ps/IOPowerSources.h>
42#include <IOKit/ps/IOPSKeys.h>
43
44#include <sys/sysctl.h>
45#elif defined(IOS)
46#include <UIKit/UIDevice.h>
47#include <sys/sysctl.h>
48#endif
49
50#include <boolean.h>
51#include <compat/apple_compat.h>
52#include <retro_assert.h>
53#include <retro_miscellaneous.h>
54#include <file/file_path.h>
55#include <streams/file_stream.h>
56#include <features/features_cpu.h>
57#include <string/stdstring.h>
58
59#ifdef HAVE_MENU
60#include "../../menu/menu_driver.h"
61#endif
62
63#include "../frontend_driver.h"
64#include "../../file_path_special.h"
65#include "../../configuration.h"
66#include "../../defaults.h"
67#include "../../retroarch.h"
68#include "../../verbosity.h"
69#include "../../msg_hash.h"
70#include "../../ui/ui_companion_driver.h"
71#include "../../paths.h"
72
73#if 1
74#define RELEASE_BUILD
75#endif
76
77typedef enum
78{
79   CFApplicationDirectory           = 1,   /* Supported applications (Applications) */
80   CFDemoApplicationDirectory       = 2,   /* Unsupported applications, demonstration versions (Demos) */
81   CFDeveloperApplicationDirectory  = 3,   /* Developer applications (Developer/Applications). DEPRECATED - there is no one single Developer directory. */
82   CFAdminApplicationDirectory      = 4,   /* System and network administration applications (Administration) */
83   CFLibraryDirectory               = 5,   /* various documentation, support, and configuration files, resources (Library) */
84   CFDeveloperDirectory             = 6,   /* developer resources (Developer) DEPRECATED - there is no one single Developer directory. */
85   CFUserDirectory                  = 7,   /* User home directories (Users) */
86   CFDocumentationDirectory         = 8,   /* Documentation (Documentation) */
87   CFDocumentDirectory              = 9,   /* Documents (Documents) */
88   CFCoreServiceDirectory           = 10,  /* Location of CoreServices directory (System/Library/CoreServices) */
89   CFAutosavedInformationDirectory  = 11,  /* Location of autosaved documents (Documents/Autosaved) */
90   CFDesktopDirectory               = 12,  /* Location of user's desktop */
91   CFCachesDirectory                = 13,  /* Location of discardable cache files (Library/Caches) */
92   CFApplicationSupportDirectory    = 14,  /* Location of application support files (plug-ins, etc) (Library/Application Support) */
93   CFDownloadsDirectory             = 15,  /* Location of the user's "Downloads" directory */
94   CFInputMethodsDirectory          = 16,  /* Input methods (Library/Input Methods) */
95   CFMoviesDirectory                = 17,  /* Location of user's Movies directory (~/Movies) */
96   CFMusicDirectory                 = 18,  /* Location of user's Music directory (~/Music) */
97   CFPicturesDirectory              = 19,  /* Location of user's Pictures directory (~/Pictures) */
98   CFPrinterDescriptionDirectory    = 20,  /* Location of system's PPDs directory (Library/Printers/PPDs) */
99   CFSharedPublicDirectory          = 21,  /* Location of user's Public sharing directory (~/Public) */
100   CFPreferencePanesDirectory       = 22,  /* Location of the PreferencePanes directory for use with System Preferences (Library/PreferencePanes) */
101   CFApplicationScriptsDirectory    = 23,  /* Location of the user scripts folder for the calling application (~/Library/Application Scripts/code-signing-id) */
102   CFItemReplacementDirectory       = 99,  /* For use with NSFileManager's URLForDirectory:inDomain:appropriateForURL:create:error: */
103   CFAllApplicationsDirectory       = 100, /* all directories where applications can occur */
104   CFAllLibrariesDirectory          = 101, /* all directories where resources can occur */
105   CFTrashDirectory                 = 102  /* location of Trash directory */
106} CFSearchPathDirectory;
107
108typedef enum
109{
110   CFUserDomainMask     = 1,       /* user's home directory --- place to install user's personal items (~) */
111   CFLocalDomainMask    = 2,       /* local to the current machine --- place to install items available to everyone on this machine (/Library) */
112   CFNetworkDomainMask  = 4,       /* publically available location in the local area network --- place to install items available on the network (/Network) */
113   CFSystemDomainMask   = 8,       /* provided by Apple, unmodifiable (/System) */
114   CFAllDomainsMask     = 0x0ffff  /* All domains: all of the above and future items */
115} CFDomainMask;
116
117#if (defined(OSX) && !(defined(__ppc__) || defined(__ppc64__)))
118static int speak_pid                            = 0;
119#endif
120
121static char darwin_cpu_model_name[64] = {0};
122
123static NSSearchPathDirectory NSConvertFlagsCF(unsigned flags)
124{
125   switch (flags)
126   {
127      case CFDocumentDirectory:
128#if TARGET_OS_TV
129           return NSCachesDirectory;
130#else
131           return NSDocumentDirectory;
132#endif
133   }
134
135   return 0;
136}
137
138static NSSearchPathDomainMask NSConvertDomainFlagsCF(unsigned flags)
139{
140   switch (flags)
141   {
142      case CFUserDomainMask:
143         return NSUserDomainMask;
144   }
145
146   return 0;
147}
148
149static void CFSearchPathForDirectoriesInDomains(unsigned flags,
150      unsigned domain_mask, unsigned expand_tilde,
151      char *s, size_t len)
152{
153   CFTypeRef array_val = (CFTypeRef)CFBridgingRetainCompat(
154         NSSearchPathForDirectoriesInDomains(NSConvertFlagsCF(flags),
155            NSConvertDomainFlagsCF(domain_mask), (BOOL)expand_tilde));
156   CFArrayRef   array  = array_val ? CFRetain(array_val) : NULL;
157   CFTypeRef path_val  = (CFTypeRef)CFArrayGetValueAtIndex(array, 0);
158   CFStringRef    path = path_val ? CFRetain(path_val) : NULL;
159   if (!path || !array)
160      return;
161
162   CFStringGetCString(path, s, len, kCFStringEncodingUTF8);
163   CFRelease(path);
164   CFRelease(array);
165}
166
167static void CFTemporaryDirectory(char *s, size_t len)
168{
169#if __has_feature(objc_arc)
170   CFStringRef path = (__bridge CFStringRef)NSTemporaryDirectory();
171#else
172   CFStringRef path = (CFStringRef)NSTemporaryDirectory();
173#endif
174   CFStringGetCString(path, s, len, kCFStringEncodingUTF8);
175}
176
177#if defined(IOS)
178void get_ios_version(int *major, int *minor);
179#endif
180
181#if defined(OSX)
182
183#define PMGMT_STRMATCH(a,b) (CFStringCompare(a, b, 0) == kCFCompareEqualTo)
184#define PMGMT_GETVAL(k,v)   CFDictionaryGetValueIfPresent(dict, CFSTR(k), (const void **) v)
185
186/* Note that AC power sources also include a laptop battery it is charging. */
187static void checkps(CFDictionaryRef dict, bool * have_ac, bool * have_battery,
188      bool * charging, int *seconds, int *percent)
189{
190   CFStringRef strval; /* don't CFRelease() this. */
191   CFBooleanRef bval;
192   CFNumberRef numval;
193   bool charge = false;
194   bool choose = false;
195   bool  is_ac = false;
196   int    secs = -1;
197   int  maxpct = -1;
198   int     pct = -1;
199
200   if ((PMGMT_GETVAL(kIOPSIsPresentKey, &bval)) && (bval == kCFBooleanFalse))
201      return;
202
203   if (!PMGMT_GETVAL(kIOPSPowerSourceStateKey, &strval))
204      return;
205
206   if (PMGMT_STRMATCH(strval, CFSTR(kIOPSACPowerValue)))
207      is_ac = *have_ac = true;
208   else if (!PMGMT_STRMATCH(strval, CFSTR(kIOPSBatteryPowerValue)))
209      return;                 /* not a battery? */
210
211   if ((PMGMT_GETVAL(kIOPSIsChargingKey, &bval)) && (bval == kCFBooleanTrue))
212      charge = true;
213
214   if (PMGMT_GETVAL(kIOPSMaxCapacityKey, &numval))
215   {
216      SInt32 val = -1;
217      CFNumberGetValue(numval, kCFNumberSInt32Type, &val);
218      if (val > 0)
219      {
220         *have_battery = true;
221         maxpct        = (int) val;
222      }
223   }
224
225   if (PMGMT_GETVAL(kIOPSMaxCapacityKey, &numval))
226   {
227      SInt32 val = -1;
228      CFNumberGetValue(numval, kCFNumberSInt32Type, &val);
229      if (val > 0)
230      {
231         *have_battery = true;
232         maxpct        = (int) val;
233      }
234   }
235
236   if (PMGMT_GETVAL(kIOPSTimeToEmptyKey, &numval))
237   {
238      SInt32 val = -1;
239      CFNumberGetValue(numval, kCFNumberSInt32Type, &val);
240
241      /* Mac OS X reports 0 minutes until empty if you're plugged in. :( */
242      if ((val == 0) && (is_ac))
243         val = -1;           /* !!! FIXME: calc from timeToFull and capacity? */
244
245      secs = (int) val;
246      if (secs > 0)
247         secs *= 60;         /* value is in minutes, so convert to seconds. */
248   }
249
250   if (PMGMT_GETVAL(kIOPSCurrentCapacityKey, &numval))
251   {
252      SInt32 val = -1;
253      CFNumberGetValue(numval, kCFNumberSInt32Type, &val);
254      pct = (int) val;
255   }
256
257   if ((pct > 0) && (maxpct > 0))
258      pct = (int) ((((double) pct) / ((double) maxpct)) * 100.0);
259
260   if (pct > 100)
261      pct = 100;
262
263   /*
264    * We pick the battery that claims to have the most minutes left.
265    *  (failing a report of minutes, we'll take the highest percent.)
266    */
267   if ((secs < 0) && (*seconds < 0))
268   {
269      if ((pct < 0) && (*percent < 0))
270         choose = true;  /* at least we know there's a battery. */
271      if (pct > *percent)
272         choose = true;
273   }
274   else if (secs > *seconds)
275      choose = true;
276
277   if (choose)
278   {
279      *seconds  = secs;
280      *percent  = pct;
281      *charging = charge;
282   }
283}
284#endif
285
286static void frontend_darwin_get_name(char *s, size_t len)
287{
288#if defined(IOS)
289   struct utsname buffer;
290
291   if (uname(&buffer) != 0)
292      return;
293
294   strlcpy(s, buffer.machine, len);
295#elif defined(OSX)
296   size_t length = 0;
297   sysctlbyname("hw.model", NULL, &length, NULL, 0);
298    if (length)
299        sysctlbyname("hw.model", s, &length, NULL, 0);
300#endif
301}
302
303static void frontend_darwin_get_os(char *s, size_t len, int *major, int *minor)
304{
305#if defined(IOS)
306   get_ios_version(major, minor);
307   strcpy_literal(s, "iOS");
308#elif defined(OSX)
309
310#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 // MAC_OS_X_VERSION_10_13
311   NSOperatingSystemVersion version = NSProcessInfo.processInfo.operatingSystemVersion;
312   *major = (int)version.majorVersion;
313   *minor = (int)version.minorVersion;
314#else
315    /* MacOS 10.9 includes the [NSProcessInfo operatingSystemVersion] function, but it's not in the 10.9 SDK. So, call it via NSInvocation */
316    /* Credit: OpenJDK (https://github.com/openjdk/jdk/commit/d4c7db50) */
317   if ([[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)])
318   {
319      typedef struct
320      {
321         NSInteger majorVersion;
322         NSInteger minorVersion;
323         NSInteger patchVersion;
324      } NSMyOSVersion;
325       NSMyOSVersion version;
326       NSMethodSignature *sig = [[NSProcessInfo processInfo] methodSignatureForSelector:@selector(operatingSystemVersion)];
327       NSInvocation *invoke = [NSInvocation invocationWithMethodSignature:sig];
328       invoke.selector = @selector(operatingSystemVersion);
329       [invoke invokeWithTarget:[NSProcessInfo processInfo]];
330       [invoke getReturnValue:&version];
331      *major = (int)version.majorVersion;
332      *minor = (int)version.minorVersion;
333   }
334   else
335   {
336      Gestalt(gestaltSystemVersionMinor, (SInt32*)minor);
337      Gestalt(gestaltSystemVersionMajor, (SInt32*)major);
338   }
339#endif
340   strcpy_literal(s, "OSX");
341#endif
342}
343
344static void frontend_darwin_get_env(int *argc, char *argv[],
345      void *args, void *params_data)
346{
347   CFURLRef bundle_url;
348   CFStringRef bundle_path;
349   CFURLRef resource_url;
350   CFStringRef resource_path;
351#if TARGET_OS_IPHONE
352   char resolved_home_dir_buf[
353      PATH_MAX_LENGTH]                   = {0};
354   char resolved_bundle_dir_buf[
355      PATH_MAX_LENGTH]                   = {0};
356#endif
357   char temp_dir[PATH_MAX_LENGTH]        = {0};
358   char bundle_path_buf[PATH_MAX_LENGTH] = {0};
359   char resource_path_buf[PATH_MAX_LENGTH] = {0};
360   char full_resource_path_buf[PATH_MAX_LENGTH] = {0};
361   char home_dir_buf[PATH_MAX_LENGTH]    = {0};
362   CFBundleRef bundle                    = CFBundleGetMainBundle();
363
364   if (!bundle)
365      return;
366
367   bundle_url  = CFBundleCopyBundleURL(bundle);
368   bundle_path = CFURLCopyPath(bundle_url);
369
370   resource_url = CFBundleCopyResourcesDirectoryURL(bundle);
371   resource_path = CFURLCopyPath(resource_url);
372
373   CFStringGetCString(bundle_path,
374         bundle_path_buf, sizeof(bundle_path_buf), kCFStringEncodingUTF8);
375   CFStringGetCString(resource_path,
376         resource_path_buf, sizeof(resource_path_buf), kCFStringEncodingUTF8);
377   fill_pathname_join(full_resource_path_buf, bundle_path_buf, resource_path_buf, sizeof(full_resource_path_buf));
378   CFSearchPathForDirectoriesInDomains(CFDocumentDirectory,
379         CFUserDomainMask, 1, home_dir_buf, sizeof(home_dir_buf));
380
381#if TARGET_OS_IPHONE
382   if (realpath(home_dir_buf, resolved_home_dir_buf))
383   {
384      retro_assert(strlcpy(home_dir_buf,
385               resolved_home_dir_buf,
386               sizeof(home_dir_buf)) < sizeof(home_dir_buf));
387   }
388   if (realpath(bundle_path_buf, resolved_bundle_dir_buf))
389   {
390      retro_assert(strlcpy(bundle_path_buf,
391               resolved_bundle_dir_buf,
392               sizeof(bundle_path_buf)) < sizeof(bundle_path_buf));
393   }
394#endif
395
396   strlcat(home_dir_buf, "/RetroArch", sizeof(home_dir_buf));
397#ifdef HAVE_METAL
398   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SHADER],
399                      home_dir_buf, "shaders_slang",
400                      sizeof(g_defaults.dirs[DEFAULT_DIR_SHADER]));
401#else
402   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SHADER],
403         home_dir_buf, "shaders_glsl",
404         sizeof(g_defaults.dirs[DEFAULT_DIR_SHADER]));
405#endif
406#ifdef HAVE_UPDATE_CORES
407    fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE],
408		    home_dir_buf, "cores", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE]));
409#else
410    fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE],
411		    bundle_path_buf, "modules", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE]));
412#endif
413   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE_INFO], home_dir_buf, "info", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE_INFO]));
414   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_OVERLAY], home_dir_buf, "overlays", sizeof(g_defaults.dirs[DEFAULT_DIR_OVERLAY]));
415#ifdef HAVE_VIDEO_LAYOUT
416   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_VIDEO_LAYOUT], home_dir_buf, "layouts", sizeof(g_defaults.dirs[DEFAULT_DIR_VIDEO_LAYOUT]));
417#endif
418   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_AUTOCONFIG], home_dir_buf, "autoconfig", sizeof(g_defaults.dirs[DEFAULT_DIR_AUTOCONFIG]));
419   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS], home_dir_buf, "downloads", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS]));
420   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_ASSETS], home_dir_buf, "assets", sizeof(g_defaults.dirs[DEFAULT_DIR_ASSETS]));
421   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SYSTEM], home_dir_buf, "system", sizeof(g_defaults.dirs[DEFAULT_DIR_SYSTEM]));
422   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG], home_dir_buf, "config", sizeof(g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG]));
423   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_REMAP], g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG], "remaps", sizeof(g_defaults.dirs[DEFAULT_DIR_REMAP]));
424   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_DATABASE], home_dir_buf, "database/rdb", sizeof(g_defaults.dirs[DEFAULT_DIR_DATABASE]));
425   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CURSOR], home_dir_buf, "database/cursors", sizeof(g_defaults.dirs[DEFAULT_DIR_CURSOR]));
426   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CHEATS], home_dir_buf, "cht", sizeof(g_defaults.dirs[DEFAULT_DIR_CHEATS]));
427   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_THUMBNAILS], home_dir_buf, "thumbnails", sizeof(g_defaults.dirs[DEFAULT_DIR_THUMBNAILS]));
428   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SRAM], home_dir_buf, "saves", sizeof(g_defaults.dirs[DEFAULT_DIR_SRAM]));
429   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SAVESTATE], home_dir_buf, "states", sizeof(g_defaults.dirs[DEFAULT_DIR_SAVESTATE]));
430   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_RECORD_CONFIG], home_dir_buf, "records_config", sizeof(g_defaults.dirs[DEFAULT_DIR_RECORD_CONFIG]));
431   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_RECORD_OUTPUT], home_dir_buf, "records", sizeof(g_defaults.dirs[DEFAULT_DIR_RECORD_OUTPUT]));
432   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_LOGS], home_dir_buf, "logs", sizeof(g_defaults.dirs[DEFAULT_DIR_LOGS]));
433#if defined(IOS)
434   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_PLAYLIST], home_dir_buf, "playlists", sizeof(g_defaults.dirs[DEFAULT_DIR_PLAYLIST]));
435#endif
436#if defined(OSX)
437   char application_data[PATH_MAX_LENGTH];
438
439   fill_pathname_application_data(application_data, sizeof(application_data));
440
441#ifdef HAVE_CG
442   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SHADER], home_dir_buf, "shaders_cg", sizeof(g_defaults.dirs[DEFAULT_DIR_SHADER]));
443#endif
444   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_PLAYLIST], application_data, "playlists", sizeof(g_defaults.dirs[DEFAULT_DIR_PLAYLIST]));
445   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_THUMBNAILS], application_data, "thumbnails", sizeof(g_defaults.dirs[DEFAULT_DIR_THUMBNAILS]));
446   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG], application_data, "config", sizeof(g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG]));
447   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_REMAP], g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG], "remaps", sizeof(g_defaults.dirs[DEFAULT_DIR_REMAP]));
448   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS], application_data, "downloads", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS]));
449   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SCREENSHOT], application_data, "screenshots", sizeof(g_defaults.dirs[DEFAULT_DIR_SCREENSHOT]));
450#if defined(RELEASE_BUILD)
451   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SHADER], application_data, "shaders", sizeof(g_defaults.dirs[DEFAULT_DIR_SHADER]));
452   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE], application_data, "cores", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE]));
453   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE_INFO], application_data, "info", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE_INFO]));
454   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_OVERLAY], application_data, "overlays", sizeof(g_defaults.dirs[DEFAULT_DIR_OVERLAY]));
455#ifdef HAVE_VIDEO_LAYOUT
456   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_VIDEO_LAYOUT], application_data, "layouts", sizeof(g_defaults.dirs[DEFAULT_DIR_VIDEO_LAYOUT]));
457#endif
458   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_AUTOCONFIG], application_data, "autoconfig", sizeof(g_defaults.dirs[DEFAULT_DIR_AUTOCONFIG]));
459   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_ASSETS], application_data, "assets", sizeof(g_defaults.dirs[DEFAULT_DIR_ASSETS]));
460   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_DATABASE], application_data, "database/rdb", sizeof(g_defaults.dirs[DEFAULT_DIR_DATABASE]));
461   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CURSOR], application_data, "database/cursors", sizeof(g_defaults.dirs[DEFAULT_DIR_CURSOR]));
462   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CHEATS], application_data, "cht", sizeof(g_defaults.dirs[DEFAULT_DIR_CHEATS]));
463   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_AUDIO_FILTER], application_data, "audio_filters", sizeof(g_defaults.dirs[DEFAULT_DIR_AUDIO_FILTER]));
464   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_VIDEO_FILTER], application_data, "video_filters", sizeof(g_defaults.dirs[DEFAULT_DIR_VIDEO_FILTER]));
465#else
466   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_AUDIO_FILTER], home_dir_buf, "audio_filters", sizeof(g_defaults.dirs[DEFAULT_DIR_AUDIO_FILTER]));
467   fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_VIDEO_FILTER], home_dir_buf, "video_filters", sizeof(g_defaults.dirs[DEFAULT_DIR_VIDEO_FILTER]));
468#endif
469
470#endif
471
472    char assets_zip_path[PATH_MAX_LENGTH];
473#if TARGET_OS_IOS
474    {
475       int major, minor;
476       get_ios_version(&major, &minor);
477       if (major > 8)
478          strcpy_literal(g_defaults.path_buildbot_server_url, "http://buildbot.libretro.com/nightly/apple/ios9/latest/");
479    }
480#endif
481
482#if TARGET_OS_IOS
483    fill_pathname_join(assets_zip_path, bundle_path_buf, "assets.zip", sizeof(assets_zip_path));
484#else
485    fill_pathname_join(assets_zip_path, full_resource_path_buf, "assets.zip", sizeof(assets_zip_path));
486#endif
487
488    if (path_is_valid(assets_zip_path))
489    {
490       settings_t *settings = config_get_ptr();
491
492       RARCH_LOG("Assets ZIP found at [%s], setting up bundle assets extraction...\n", assets_zip_path);
493       RARCH_LOG("Extraction dir will be: %s\n", home_dir_buf);
494       configuration_set_string(settings,
495             settings->arrays.bundle_assets_src,
496             assets_zip_path);
497       configuration_set_string(settings,
498             settings->arrays.bundle_assets_dst,
499#if TARGET_OS_IOS || TARGET_OS_TV
500             home_dir_buf
501#else
502             application_data
503#endif
504       );
505       /* TODO/FIXME: Just hardcode this for now */
506       configuration_set_uint(settings, settings->uints.bundle_assets_extract_version_current, 1);
507    }
508
509   CFTemporaryDirectory(temp_dir, sizeof(temp_dir));
510   strlcpy(g_defaults.dirs[DEFAULT_DIR_CACHE],
511         temp_dir,
512         sizeof(g_defaults.dirs[DEFAULT_DIR_CACHE]));
513
514   path_mkdir(bundle_path_buf);
515
516   if (access(bundle_path_buf, 0755) != 0)
517      RARCH_ERR("Failed to create or access base directory: %s\n", bundle_path_buf);
518   else
519   {
520      path_mkdir(g_defaults.dirs[DEFAULT_DIR_SYSTEM]);
521
522      if (access(g_defaults.dirs[DEFAULT_DIR_SYSTEM], 0755) != 0)
523         RARCH_ERR("Failed to create or access system directory: %s.\n", g_defaults.dirs[DEFAULT_DIR_SYSTEM]);
524   }
525
526   CFRelease(bundle_path);
527   CFRelease(bundle_url);
528
529#ifndef IS_SALAMANDER
530   dir_check_defaults("custom.ini");
531#endif
532}
533
534static void frontend_darwin_content_loaded(void)
535{
536   ui_companion_driver_notify_content_loaded();
537}
538
539static int frontend_darwin_get_rating(void)
540{
541   char model[PATH_MAX_LENGTH] = {0};
542
543   frontend_darwin_get_name(model, sizeof(model));
544
545   /* iPhone 4 */
546#if 0
547   if (strstr(model, "iPhone3"))
548      return -1;
549#endif
550
551   /* iPad 1 */
552#if 0
553   if (strstr(model, "iPad1,1"))
554      return -1;
555#endif
556
557   /* iPhone 4S */
558   if (strstr(model, "iPhone4,1"))
559      return 8;
560
561   /* iPad 2/iPad Mini 1 */
562   if (strstr(model, "iPad2"))
563      return 9;
564
565   /* iPhone 5/5C */
566   if (strstr(model, "iPhone5"))
567      return 13;
568
569   /* iPhone 5S */
570   if (strstr(model, "iPhone6,1") || strstr(model, "iPhone6,2"))
571      return 14;
572
573   /* iPad Mini 2/3 */
574   if (     strstr(model, "iPad4,4")
575         || strstr(model, "iPad4,5")
576         || strstr(model, "iPad4,6")
577         || strstr(model, "iPad4,7")
578         || strstr(model, "iPad4,8")
579         || strstr(model, "iPad4,9")
580      )
581      return 15;
582
583   /* iPad Air */
584   if (     strstr(model, "iPad4,1")
585         || strstr(model, "iPad4,2")
586         || strstr(model, "iPad4,3")
587      )
588      return 16;
589
590   /* iPhone 6, iPhone 6 Plus */
591   if (strstr(model, "iPhone7"))
592      return 17;
593
594   /* iPad Air 2 */
595   if (strstr(model, "iPad5,3") || strstr(model, "iPad5,4"))
596      return 18;
597
598   /* iPad Pro (12.9 Inch) */
599   if (strstr(model, "iPad6,7") || strstr(model, "iPad6,8"))
600     return 19;
601
602   /* iPad Pro (9.7 Inch) */
603   if (strstr(model, "iPad6,3") || strstr(model, "iPad6,4"))
604     return 19;
605
606   /* iPad 5th Generation */
607   if (strstr(model, "iPad6,11") || strstr(model, "iPad6,12"))
608     return 19;
609
610   /* iPad Pro (12.9 Inch 2nd Generation) */
611   if (strstr(model, "iPad7,1") || strstr(model, "iPad7,2"))
612     return 19;
613
614   /* iPad Pro (10.5 Inch) */
615   if (strstr(model, "iPad7,3") || strstr(model, "iPad7,4"))
616     return 19;
617
618   /* iPad Pro 6th Generation) */
619   if (strstr(model, "iPad7,5") || strstr(model, "iPad7,6"))
620     return 19;
621
622   /* iPad Pro (11 Inch) */
623   if (     strstr(model, "iPad8,1")
624         || strstr(model, "iPad8,2")
625         || strstr(model, "iPad8,3")
626         || strstr(model, "iPad8,4")
627      )
628      return 19;
629
630   /* iPad Pro (12.9 3rd Generation) */
631    if (   strstr(model, "iPad8,5")
632        || strstr(model, "iPad8,6")
633        || strstr(model, "iPad8,7")
634        || strstr(model, "iPad8,8")
635       )
636       return 19;
637
638   /* iPad Air 3rd Generation) */
639    if (   strstr(model, "iPad11,3")
640        || strstr(model, "iPad11,4"))
641       return 19;
642
643   /* TODO/FIXME -
644      - more ratings for more systems
645      - determine rating more intelligently*/
646   return -1;
647}
648
649static enum frontend_powerstate frontend_darwin_get_powerstate(int *seconds, int *percent)
650{
651   enum frontend_powerstate ret = FRONTEND_POWERSTATE_NONE;
652#if defined(OSX)
653   CFIndex i, total;
654   CFArrayRef list;
655   bool have_ac, have_battery, charging;
656   CFTypeRef blob  = IOPSCopyPowerSourcesInfo();
657
658   *seconds        = -1;
659   *percent        = -1;
660
661   if (!blob)
662      goto end;
663
664   list = IOPSCopyPowerSourcesList(blob);
665
666   if (!list)
667      goto end;
668
669   /* don't CFRelease() the list items, or dictionaries! */
670   have_ac         = false;
671   have_battery    = false;
672   charging        = false;
673   total           = CFArrayGetCount(list);
674
675   for (i = 0; i < total; i++)
676   {
677      CFTypeRef ps = (CFTypeRef)CFArrayGetValueAtIndex(list, i);
678      CFDictionaryRef dict = IOPSGetPowerSourceDescription(blob, ps);
679      if (dict)
680         checkps(dict, &have_ac, &have_battery, &charging,
681               seconds, percent);
682   }
683
684   if (!have_battery)
685      ret = FRONTEND_POWERSTATE_NO_SOURCE;
686   else if (charging)
687      ret = FRONTEND_POWERSTATE_CHARGING;
688   else if (have_ac)
689      ret = FRONTEND_POWERSTATE_CHARGED;
690   else
691      ret = FRONTEND_POWERSTATE_ON_POWER_SOURCE;
692
693   CFRelease(list);
694end:
695   if (blob)
696      CFRelease(blob);
697#elif TARGET_OS_IOS
698   float level;
699   UIDevice *uidev = [UIDevice currentDevice];
700
701   if (!uidev)
702	   return ret;
703
704   [uidev setBatteryMonitoringEnabled:true];
705
706   switch (uidev.batteryState)
707   {
708	   case UIDeviceBatteryStateCharging:
709		   ret = FRONTEND_POWERSTATE_CHARGING;
710		   break;
711	   case UIDeviceBatteryStateFull:
712		   ret = FRONTEND_POWERSTATE_CHARGED;
713		   break;
714	   case UIDeviceBatteryStateUnplugged:
715		   ret = FRONTEND_POWERSTATE_ON_POWER_SOURCE;
716		   break;
717	   case UIDeviceBatteryStateUnknown:
718		   break;
719   }
720
721   level = uidev.batteryLevel;
722
723   *percent = ((level < 0.0f) ? -1 : ((int)((level * 100) + 0.5f)));
724
725   [uidev setBatteryMonitoringEnabled:false];
726#endif
727   return ret;
728}
729
730#ifndef OSX
731#ifndef CPU_ARCH_ABI64
732#define CPU_ARCH_ABI64          0x01000000
733#endif
734
735#ifndef CPU_TYPE_ARM64
736#define CPU_TYPE_ARM64          (CPU_TYPE_ARM | CPU_ARCH_ABI64)
737#endif
738#endif
739
740static enum frontend_architecture frontend_darwin_get_arch(void)
741{
742#ifdef OSX
743    struct utsname buffer;
744
745    if (uname(&buffer) != 0)
746       return FRONTEND_ARCH_NONE;
747
748   if (string_is_equal(buffer.machine, "x86_64"))
749      return FRONTEND_ARCH_X86_64;
750   if (string_is_equal(buffer.machine, "x86"))
751      return FRONTEND_ARCH_X86;
752   if (string_is_equal(buffer.machine, "Power Macintosh"))
753      return FRONTEND_ARCH_PPC;
754   if (string_is_equal(buffer.machine, "arm64"))
755      return FRONTEND_ARCH_ARMV8;
756#else
757   cpu_type_t type;
758   size_t size = sizeof(type);
759
760   sysctlbyname("hw.cputype", &type, &size, NULL, 0);
761
762   if (type == CPU_TYPE_X86_64)
763      return FRONTEND_ARCH_X86_64;
764   else if (type == CPU_TYPE_X86)
765      return FRONTEND_ARCH_X86;
766   else if (type == CPU_TYPE_ARM64)
767      return FRONTEND_ARCH_ARMV8;
768   else if (type == CPU_TYPE_ARM)
769      return FRONTEND_ARCH_ARMV7;
770#endif
771    return FRONTEND_ARCH_NONE;
772}
773
774static int frontend_darwin_parse_drive_list(void *data, bool load_content)
775{
776   int ret = -1;
777#if TARGET_OS_IPHONE
778#ifdef HAVE_MENU
779   file_list_t *list                     = (file_list_t*)data;
780   char bundle_path_buf[PATH_MAX_LENGTH] = {0};
781   char home_dir_buf[PATH_MAX_LENGTH]    = {0};
782   CFBundleRef bundle                    = CFBundleGetMainBundle();
783   enum msg_hash_enums enum_idx          = load_content
784      ? MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR
785      : MENU_ENUM_LABEL_FILE_BROWSER_DIRECTORY;
786   CFURLRef bundle_url                   = CFBundleCopyBundleURL(bundle);
787   CFStringRef bundle_path               = CFURLCopyPath(bundle_url);
788
789   CFStringGetCString(bundle_path, bundle_path_buf,
790         sizeof(bundle_path_buf), kCFStringEncodingUTF8);
791
792   CFSearchPathForDirectoriesInDomains(CFDocumentDirectory,
793         CFUserDomainMask, 1, home_dir_buf, sizeof(home_dir_buf));
794
795   menu_entries_append_enum(list,
796         home_dir_buf,
797         msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR),
798         enum_idx,
799         FILE_TYPE_DIRECTORY, 0, 0);
800   menu_entries_append_enum(list, "/",
801         msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR),
802         enum_idx,
803        FILE_TYPE_DIRECTORY, 0, 0);
804
805   ret = 0;
806
807   CFRelease(bundle_path);
808   CFRelease(bundle_url);
809#endif
810#endif
811
812   return ret;
813}
814
815static uint64_t frontend_darwin_get_total_mem(void)
816{
817#if defined(OSX)
818    uint64_t size;
819    int mib[2]     = { CTL_HW, HW_MEMSIZE };
820    u_int namelen  = sizeof(mib) / sizeof(mib[0]);
821    size_t len     = sizeof(size);
822
823    if (sysctl(mib, namelen, &size, &len, NULL, 0) < 0)
824        return 0;
825    return size;
826#else
827    return 0;
828#endif
829}
830
831static uint64_t frontend_darwin_get_free_mem(void)
832{
833#if (defined(OSX) && !(defined(__ppc__) || defined(__ppc64__)))
834    vm_size_t page_size;
835    vm_statistics64_data_t vm_stats;
836    mach_port_t mach_port        = mach_host_self();
837    mach_msg_type_number_t count = sizeof(vm_stats) / sizeof(natural_t);
838
839    if (KERN_SUCCESS == host_page_size(mach_port, &page_size) &&
840        KERN_SUCCESS == host_statistics64(mach_port, HOST_VM_INFO,
841                                          (host_info64_t)&vm_stats, &count))
842    {
843
844        long long used_memory = ((int64_t)vm_stats.active_count +
845                                 (int64_t)vm_stats.inactive_count +
846                                 (int64_t)vm_stats.wire_count) *  (int64_t)page_size;
847        return used_memory;
848    }
849#endif
850    return 0;
851}
852
853static const char* frontend_darwin_get_cpu_model_name(void)
854{
855   cpu_features_get_model_name(darwin_cpu_model_name, sizeof(darwin_cpu_model_name));
856   return darwin_cpu_model_name;
857}
858
859#if (defined(OSX) && !(defined(__ppc__) || defined(__ppc64__)))
860static char* accessibility_mac_language_code(const char* language)
861{
862   if (string_is_equal(language,"en"))
863      return "Alex";
864   else if (string_is_equal(language,"it"))
865      return "Alice";
866   else if (string_is_equal(language,"sv"))
867      return "Alva";
868   else if (string_is_equal(language,"fr"))
869      return "Amelie";
870   else if (string_is_equal(language,"de"))
871      return "Anna";
872   else if (string_is_equal(language,"he"))
873      return "Carmit";
874   else if (string_is_equal(language,"id"))
875      return "Damayanti";
876   else if (string_is_equal(language,"es"))
877      return "Diego";
878   else if (string_is_equal(language,"nl"))
879      return "Ellen";
880   else if (string_is_equal(language,"ro"))
881      return "Ioana";
882   else if (string_is_equal(language,"pt_pt"))
883      return "Joana";
884   else if (string_is_equal(language,"pt_bt") || string_is_equal(language,"pt"))
885      return "Luciana";
886   else if (string_is_equal(language,"th"))
887      return "Kanya";
888   else if (string_is_equal(language,"ja"))
889      return "Kyoko";
890   else if (string_is_equal(language,"sk"))
891      return "Laura";
892   else if (string_is_equal(language,"hi"))
893      return "Lekha";
894   else if (string_is_equal(language,"ar"))
895      return "Maged";
896   else if (string_is_equal(language,"hu"))
897      return "Mariska";
898   else if (string_is_equal(language,"zh_tw") || string_is_equal(language,"zh"))
899      return "Mei-Jia";
900   else if (string_is_equal(language,"el"))
901      return "Melina";
902   else if (string_is_equal(language,"ru"))
903      return "Milena";
904   else if (string_is_equal(language,"nb"))
905      return "Nora";
906   else if (string_is_equal(language,"da"))
907      return "Sara";
908   else if (string_is_equal(language,"fi"))
909      return "Satu";
910   else if (string_is_equal(language,"zh_hk"))
911      return "Sin-ji";
912   else if (string_is_equal(language,"zh_cn"))
913      return "Ting-Ting";
914   else if (string_is_equal(language,"tr"))
915      return "Yelda";
916   else if (string_is_equal(language,"ko"))
917      return "Yuna";
918   else if (string_is_equal(language,"pl"))
919      return "Zosia";
920   else if (string_is_equal(language,"cs"))
921      return "Zuzana";
922   else
923      return "";
924}
925
926static bool is_narrator_running_macos(void)
927{
928   return (kill(speak_pid, 0) == 0);
929}
930
931static bool accessibility_speak_macos(int speed,
932      const char* speak_text, int priority)
933{
934   int pid;
935   const char *voice      = get_user_language_iso639_1(false);
936   char* language_speaker = accessibility_mac_language_code(voice);
937   char* speeds[10]       = {"80", "100", "125", "150", "170", "210", "260", "310", "380", "450"};
938
939   if (speed < 1)
940      speed = 1;
941   else if (speed > 10)
942      speed = 10;
943
944   if (priority < 10 && speak_pid > 0)
945   {
946      /* check if old pid is running */
947      if (is_narrator_running_macos())
948         return true;
949   }
950
951   if (speak_pid > 0)
952   {
953      /* Kill the running say */
954      kill(speak_pid, SIGTERM);
955      speak_pid = 0;
956   }
957
958   pid = fork();
959   if (pid < 0)
960   {
961      /* error */
962      RARCH_LOG("ERROR: could not fork for say command.\n");
963   }
964   else if (pid > 0)
965   {
966      /* parent process */
967      speak_pid = pid;
968
969      /* Tell the system that we'll ignore the exit status of the child
970       * process.  This prevents zombie processes. */
971      signal(SIGCHLD,SIG_IGN);
972   }
973   else
974   {
975      /* child process: replace process with the say command */
976      if (strlen(language_speaker)> 0)
977      {
978         char* cmd[] = {"say", "-v", NULL,
979                        NULL, "-r", NULL, NULL};
980         cmd[2] = language_speaker;
981         cmd[3] = (char *) speak_text;
982         cmd[5] = speeds[speed-1];
983         execvp("say", cmd);
984      }
985      else
986      {
987         char* cmd[] = {"say", NULL, "-r", NULL,  NULL};
988         cmd[1] = (char*) speak_text;
989         cmd[3] = speeds[speed-1];
990         execvp("say",cmd);
991      }
992   }
993   return true;
994}
995#endif
996
997frontend_ctx_driver_t frontend_ctx_darwin = {
998   frontend_darwin_get_env,         /* get_env */
999   NULL,                            /* init */
1000   NULL,                            /* deinit */
1001   NULL,                            /* exitspawn */
1002   NULL,                            /* process_args */
1003   NULL,                            /* exec */
1004   NULL,                            /* set_fork */
1005   NULL,                            /* shutdown */
1006   frontend_darwin_get_name,        /* get_name */
1007   frontend_darwin_get_os,          /* get_os               */
1008   frontend_darwin_get_rating,      /* get_rating           */
1009   frontend_darwin_content_loaded,  /* content_loaded       */
1010   frontend_darwin_get_arch,        /* get_architecture     */
1011   frontend_darwin_get_powerstate,  /* get_powerstate       */
1012   frontend_darwin_parse_drive_list,/* parse_drive_list     */
1013   frontend_darwin_get_total_mem,   /* get_total_mem        */
1014   frontend_darwin_get_free_mem,    /* get_free_mem         */
1015   NULL,                            /* install_signal_handler */
1016   NULL,                            /* get_sighandler_state */
1017   NULL,                            /* set_sighandler_state */
1018   NULL,                            /* destroy_signal_handler_state */
1019   NULL,                            /* attach_console */
1020   NULL,                            /* detach_console */
1021   NULL,                            /* get_lakka_version */
1022   NULL,                            /* set_screen_brightness */
1023   NULL,                            /* watch_path_for_changes */
1024   NULL,                            /* check_for_path_changes */
1025   NULL,                            /* set_sustained_performance_mode */
1026#if (defined(OSX) && !(defined(__ppc__) || defined(__ppc64__)))
1027    frontend_darwin_get_cpu_model_name, /* get_cpu_model_name */
1028#else
1029   NULL,                            /* get_cpu_model_name */
1030#endif
1031   NULL,                            /* get_user_language   */
1032#if (defined(OSX) && !(defined(__ppc__) || defined(__ppc64__)))
1033   is_narrator_running_macos,       /* is_narrator_running */
1034   accessibility_speak_macos,       /* accessibility_speak */
1035#else
1036   NULL,                            /* is_narrator_running */
1037   NULL,                            /* accessibility_speak */
1038#endif
1039   "darwin",                        /* ident               */
1040   NULL                             /* get_video_driver    */
1041};
1042