1 /*
   2  * Copyright (c) 2014, 2016, Oracle and/or its affiliates.
   3  * All rights reserved. Use is subject to license terms.
   4  *
   5  * This file is available and licensed under the following license:
   6  *
   7  * Redistribution and use in source and binary forms, with or without
   8  * modification, are permitted provided that the following conditions
   9  * are met:
  10  *
  11  *  - Redistributions of source code must retain the above copyright
  12  *    notice, this list of conditions and the following disclaimer.
  13  *  - Redistributions in binary form must reproduce the above copyright
  14  *    notice, this list of conditions and the following disclaimer in
  15  *    the documentation and/or other materials provided with the distribution.
  16  *  - Neither the name of Oracle Corporation nor the names of its
  17  *    contributors may be used to endorse or promote products derived
  18  *    from this software without specific prior written permission.
  19  *
  20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31  */
  32 
  33 
  34 #include "Platform.h"
  35 
  36 #ifdef MAC
  37 
  38 #include "MacPlatform.h"
  39 #include "Helpers.h"
  40 #include "Package.h"
  41 #include "PropertyFile.h"
  42 #include "IniFile.h"
  43 
  44 #include <sys/sysctl.h>
  45 #include <pthread.h>
  46 #include <vector>
  47 
  48 #import <Foundation/Foundation.h>
  49 
  50 #include <CoreFoundation/CoreFoundation.h>
  51 #include <CoreFoundation/CFString.h>
  52 
  53 #ifdef __OBJC__
  54 #import <Cocoa/Cocoa.h>
  55 #endif //__OBJC__
  56 
  57 //--------------------------------------------------------------------------------------------------
  58 
  59 NSString* StringToNSString(TString Value) {
  60     NSString* result = [NSString stringWithCString:Value.c_str()
  61                                           encoding:[NSString defaultCStringEncoding]];
  62     return result;
  63 }
  64 
  65 //--------------------------------------------------------------------------------------------------
  66 
  67 MacPlatform::MacPlatform(void) : Platform(), GenericPlatform(), PosixPlatform() {
  68 }
  69 
  70 MacPlatform::~MacPlatform(void) {
  71 }
  72 
  73 bool MacPlatform::UsePListForConfigFile() {
  74     return FilePath::FileExists(GetConfigFileName()) == false;
  75 }
  76 
  77 void MacPlatform::ShowMessage(TString Title, TString Description) {
  78     NSString *ltitle = StringToNSString(Title);
  79     NSString *ldescription = StringToNSString(Description);
  80 
  81     NSLog(@"%@:%@", ltitle, ldescription);
  82 }
  83 
  84 void MacPlatform::ShowMessage(TString Description) {
  85     TString appname = GetModuleFileName();
  86     appname = FilePath::ExtractFileName(appname);
  87     ShowMessage(appname, Description);
  88 }
  89 
  90 
  91 TCHAR* MacPlatform::ConvertStringToFileSystemString(TCHAR* Source, bool &release) {
  92     TCHAR* result = NULL;
  93     release = false;
  94     CFStringRef StringRef = CFStringCreateWithCString(kCFAllocatorDefault, Source, kCFStringEncodingUTF8);
  95 
  96     if (StringRef != NULL) {
  97         @try {
  98             CFIndex length = CFStringGetMaximumSizeOfFileSystemRepresentation(StringRef);
  99             result = new char[length + 1];
 100 
 101             if (CFStringGetFileSystemRepresentation(StringRef, result, length)) {
 102                 release = true;
 103             }
 104             else {
 105                 delete[] result;
 106                 result = NULL;
 107             }
 108         }
 109         @finally {
 110             CFRelease(StringRef);
 111         }
 112     }
 113 
 114     return result;
 115 }
 116 
 117 TCHAR* MacPlatform::ConvertFileSystemStringToString(TCHAR* Source, bool &release) {
 118     TCHAR* result = NULL;
 119     release = false;
 120     CFStringRef StringRef = CFStringCreateWithFileSystemRepresentation(kCFAllocatorDefault, Source);
 121 
 122     if (StringRef != NULL) {
 123         @try {
 124             CFIndex length = CFStringGetLength(StringRef);
 125 
 126             if (length > 0) {
 127                 CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8);
 128 
 129                 result = new char[maxSize + 1];
 130 
 131                 if (CFStringGetCString(StringRef, result, maxSize, kCFStringEncodingUTF8) == true) {
 132                     release = true;
 133                 }
 134                 else {
 135                     delete[] result;
 136                     result = NULL;
 137                 }
 138             }
 139         }
 140         @finally {
 141             CFRelease(StringRef);
 142         }
 143     }
 144 
 145     return result;
 146 }
 147 
 148 void MacPlatform::SetCurrentDirectory(TString Value) {
 149     chdir(PlatformString(Value).toPlatformString());
 150 }
 151 
 152 TString MacPlatform::GetPackageRootDirectory() {
 153     NSBundle *mainBundle = [NSBundle mainBundle];
 154     NSString *mainBundlePath = [mainBundle bundlePath];
 155     NSString *contentsPath = [mainBundlePath stringByAppendingString:@"/Contents"];
 156     TString result = [contentsPath UTF8String];
 157     return result;
 158 }
 159 
 160 TString MacPlatform::GetAppDataDirectory() {
 161     TString result;
 162     NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
 163     NSString *applicationSupportDirectory = [paths firstObject];
 164     result = [applicationSupportDirectory UTF8String];
 165     return result;
 166 }
 167 
 168 TString MacPlatform::GetBundledJVMLibraryFileName(TString RuntimePath) {
 169     TString result;
 170 
 171     result = FilePath::IncludeTrailingSeparater(RuntimePath) + _T("Contents/Home/jre/lib/jli/libjli.dylib");
 172 
 173     if (FilePath::FileExists(result) == false) {
 174         result = FilePath::IncludeTrailingSeparater(RuntimePath) + _T("Contents/Home/lib/jli/libjli.dylib");
 175 
 176         if (FilePath::FileExists(result) == false) {
 177             result = _T("");
 178         }
 179     }
 180 
 181     return result;
 182 }
 183 
 184 
 185 TString MacPlatform::GetSystemJRE() {
 186     if (GetAppCDSState() != cdsDisabled) {
 187         //TODO throw exception
 188         return _T("");
 189     }
 190 
 191     return _T("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/lib/jli/libjli.dylib");
 192 }
 193 
 194 TString MacPlatform::GetSystemJVMLibraryFileName() {
 195     TString result = GetSystemJRE();
 196 
 197     if (FilePath::FileExists(result) == false) {
 198         result = _T("");
 199     }
 200 
 201     return result;
 202 }
 203 
 204 TString MacPlatform::GetAppName() {
 205     NSString *appName = [[NSProcessInfo processInfo] processName];
 206     TString result = [appName UTF8String];
 207     return result;
 208 }
 209 
 210 void AppendPListArrayToIniFile(NSDictionary *infoDictionary, IniFile *result, TString Section) {
 211     NSString *sectionKey = [NSString stringWithUTF8String:PlatformString(Section).toMultibyte()];
 212     NSDictionary *array = [infoDictionary objectForKey:sectionKey];
 213 
 214     for (id option in array) {
 215         if ([option isKindOfClass:[NSString class]]) {
 216             TString arg = [option UTF8String];
 217 
 218             TString name;
 219             TString value;
 220 
 221             if (Helpers::SplitOptionIntoNameValue(arg, name, value) == true) {
 222                 result->Append(Section, name, value);
 223             }
 224         }
 225     }
 226 }
 227 
 228 void AppendPListDictionaryToIniFile(NSDictionary *infoDictionary, IniFile *result, TString Section, bool FollowSection = true) {
 229     NSDictionary *dictionary = NULL;
 230 
 231     if (FollowSection == true) {
 232         NSString *sectionKey = [NSString stringWithUTF8String:PlatformString(Section).toMultibyte()];
 233         dictionary = [infoDictionary objectForKey:sectionKey];
 234     }
 235     else {
 236         dictionary = infoDictionary;
 237     }
 238 
 239     for (id key in dictionary) {
 240         id option = [dictionary valueForKey:key];
 241 
 242         if ([key isKindOfClass:[NSString class]] && [option isKindOfClass:[NSString class]]) {
 243             TString name = [key UTF8String];
 244             TString value = [option UTF8String];
 245             result->Append(Section, name, value);
 246         }
 247     }
 248 }
 249 
 250 // Convert parts of the info.plist to the INI format the rest of the packager uses unless
 251 // a packager config file exists.
 252 ISectionalPropertyContainer* MacPlatform::GetConfigFile(TString FileName) {
 253     IniFile* result = new IniFile();
 254 
 255     if (UsePListForConfigFile() == false) {
 256         if (result->LoadFromFile(FileName) == false) {
 257             // New property file format was not found, attempt to load old property file format.
 258             Helpers::LoadOldConfigFile(FileName, result);
 259         }
 260     }
 261     else {
 262         NSBundle *mainBundle = [NSBundle mainBundle];
 263         NSDictionary *infoDictionary = [mainBundle infoDictionary];
 264         std::map<TString, TString> keys = GetKeys();
 265 
 266         // Packager options.
 267         AppendPListDictionaryToIniFile(infoDictionary, result, keys[CONFIG_SECTION_APPLICATION], false);
 268 
 269         // jvmargs
 270         AppendPListArrayToIniFile(infoDictionary, result, keys[CONFIG_SECTION_JVMOPTIONS]);
 271 
 272         // jvmuserargs
 273         AppendPListDictionaryToIniFile(infoDictionary, result, keys[CONFIG_SECTION_JVMUSEROPTIONS]);
 274 
 275         // Generate AppCDS Cache
 276         AppendPListDictionaryToIniFile(infoDictionary, result, keys[CONFIG_SECTION_APPCDSJVMOPTIONS]);
 277         AppendPListDictionaryToIniFile(infoDictionary, result, keys[CONFIG_SECTION_APPCDSGENERATECACHEJVMOPTIONS]);
 278 
 279         // args
 280         AppendPListArrayToIniFile(infoDictionary, result, keys[CONFIG_SECTION_ARGOPTIONS]);
 281     }
 282 
 283     return result;
 284 }
 285 
 286 TString GetModuleFileNameOSX() {
 287     Dl_info module_info;
 288     if (dladdr(reinterpret_cast<void*>(GetModuleFileNameOSX), &module_info) == 0) {
 289         // Failed to find the symbol we asked for.
 290         return std::string();
 291     }
 292     return TString(module_info.dli_fname);
 293 }
 294 
 295 #include <mach-o/dyld.h>
 296 
 297 TString MacPlatform::GetModuleFileName() {
 298     //return GetModuleFileNameOSX();
 299 
 300     TString result;
 301     DynamicBuffer<TCHAR> buffer(MAX_PATH);
 302     uint32_t size = buffer.GetSize();
 303 
 304     if (_NSGetExecutablePath(buffer.GetData(), &size) == 0) {
 305         result = FileSystemStringToString(buffer.GetData());
 306     }
 307 
 308     return result;
 309 }
 310 
 311 bool MacPlatform::IsMainThread() {
 312     bool result = (pthread_main_np() == 1);
 313     return result;
 314 }
 315 
 316 TPlatformNumber MacPlatform::GetMemorySize() {
 317     unsigned long long memory = [[NSProcessInfo processInfo] physicalMemory];
 318     TPlatformNumber result = memory / 1048576; // Convert from bytes to megabytes.
 319     return result;
 320 }
 321 
 322 std::map<TString, TString> MacPlatform::GetKeys() {
 323     std::map<TString, TString> keys;
 324 
 325     if (UsePListForConfigFile() == false) {
 326         return GenericPlatform::GetKeys();
 327     }
 328     else {
 329         keys.insert(std::map<TString, TString>::value_type(CONFIG_VERSION,            _T("app.version")));
 330         keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINJAR_KEY,        _T("JVMMainJarName")));
 331         keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINMODULE_KEY,     _T("JVMMainModuleName")));
 332         keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINCLASSNAME_KEY,  _T("JVMMainClassName")));
 333         keys.insert(std::map<TString, TString>::value_type(CONFIG_CLASSPATH_KEY,      _T("JVMAppClasspath")));
 334         keys.insert(std::map<TString, TString>::value_type(APP_NAME_KEY,              _T("CFBundleName")));
 335         keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_ID_KEY,         _T("JVMPreferencesID")));
 336         keys.insert(std::map<TString, TString>::value_type(JVM_RUNTIME_KEY,           _T("JVMRuntime")));
 337         keys.insert(std::map<TString, TString>::value_type(PACKAGER_APP_DATA_DIR,     _T("CFBundleIdentifier")));
 338 
 339         keys.insert(std::map<TString, TString>::value_type(CONFIG_SPLASH_KEY,         _T("app.splash")));
 340         keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_MEMORY,         _T("app.memory")));
 341         keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_DEBUG,          _T("app.debug")));
 342 
 343         keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_APPLICATION,    _T("Application")));
 344         keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_JVMOPTIONS,     _T("JVMOptions")));
 345         keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_JVMUSEROPTIONS, _T("JVMUserOptions")));
 346         keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_JVMUSEROVERRIDESOPTIONS, _T("JVMUserOverrideOptions")));
 347         keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_APPCDSJVMOPTIONS, _T("AppCDSJVMOptions")));
 348         keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_APPCDSGENERATECACHEJVMOPTIONS, _T("AppCDSGenerateCacheJVMOptions")));
 349         keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_ARGOPTIONS,     _T("ArgOptions")));
 350     }
 351 
 352     return keys;
 353 }
 354 
 355 #ifdef DEBUG
 356 bool MacPlatform::IsNativeDebuggerPresent() {
 357     int state;
 358     int mib[4];
 359     struct kinfo_proc info;
 360     size_t size;
 361 
 362     info.kp_proc.p_flag = 0;
 363 
 364     mib[0] = CTL_KERN;
 365     mib[1] = KERN_PROC;
 366     mib[2] = KERN_PROC_PID;
 367     mib[3] = getpid();
 368 
 369     size = sizeof(info);
 370     state = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
 371     assert(state == 0);
 372     return ((info.kp_proc.p_flag & P_TRACED) != 0);
 373 }
 374 
 375 int MacPlatform::GetProcessID() {
 376     int pid = [[NSProcessInfo processInfo] processIdentifier];
 377     return pid;
 378 }
 379 #endif //DEBUG
 380 
 381 //--------------------------------------------------------------------------------------------------
 382 
 383 class UserDefaults {
 384 private:
 385     OrderedMap<TString, TString> FData;
 386     TString FDomainName;
 387 
 388     bool ReadDictionary(NSDictionary *Items, OrderedMap<TString, TString> &Data) {
 389         bool result = false;
 390 
 391         for (id key in Items) {
 392             id option = [Items valueForKey:key];
 393 
 394             if ([key isKindOfClass:[NSString class]] && [option isKindOfClass:[NSString class]]) {
 395                 TString name = [key UTF8String];
 396                 TString value = [option UTF8String];
 397 
 398                 if (name.empty() == false) {
 399                     Data.Append(name, value);
 400                 }
 401             }
 402         }
 403 
 404         return result;
 405     }
 406 
 407     // Open and read the defaults file specified by domain.
 408     bool ReadPreferences(NSDictionary *Defaults, std::list<TString> Keys, OrderedMap<TString, TString> &Data) {
 409         bool result = false;
 410 
 411         if (Keys.size() > 0 && Defaults != NULL) {
 412             NSDictionary *node = Defaults;
 413 
 414             while (Keys.size() > 0 && node != NULL) {
 415                 TString key = Keys.front();
 416                 Keys.pop_front();
 417                 NSString *tempKey = StringToNSString(key);
 418                 node = [node valueForKey:tempKey];
 419 
 420                 if (Keys.size() == 0) {
 421                     break;
 422                 }
 423             }
 424 
 425             if (node != NULL) {
 426                 result = ReadDictionary(node, Data);
 427             }
 428         }
 429 
 430         return result;
 431     }
 432 
 433     NSDictionary* LoadPreferences(TString DomainName) {
 434         NSDictionary *result = NULL;
 435 
 436         if (DomainName.empty() == false) {
 437             NSUserDefaults *prefs = [[NSUserDefaults alloc] init];
 438 
 439             if (prefs != NULL) {
 440                 NSString *lDomainName = StringToNSString(DomainName);
 441                 result = [prefs persistentDomainForName: lDomainName];
 442             }
 443         }
 444 
 445         return result;
 446     }
 447 
 448 public:
 449     UserDefaults(TString DomainName) {
 450         FDomainName = DomainName;
 451     }
 452 
 453     bool Read(std::list<TString> Keys) {
 454         NSDictionary *defaults = LoadPreferences(FDomainName);
 455         return ReadPreferences(defaults, Keys, FData);
 456     }
 457 
 458     OrderedMap<TString, TString> GetData() {
 459         return FData;
 460     }
 461 };
 462 
 463 //--------------------------------------------------------------------------------------------------
 464 
 465 MacJavaUserPreferences::MacJavaUserPreferences(void) : JavaUserPreferences() {
 466 }
 467 
 468 TString toLowerCase(TString Value) {
 469     // Use Cocoa's lowercase method because it is better than the ones provided by C/C++.
 470     NSString *temp = StringToNSString(Value);
 471     temp = [temp lowercaseString];
 472     TString result = [temp UTF8String];
 473     return result;
 474 }
 475 
 476 // Split the string Value into using Delimiter.
 477 std::list<TString> Split(TString Value, TString Delimiter) {
 478     std::list<TString> result;
 479     std::vector<char> buffer(Value.c_str(), Value.c_str() + Value.size() + 1);
 480     char *p = strtok(&buffer[0], Delimiter.data());
 481 
 482     while (p != NULL) {
 483         TString token = p;
 484         result.push_back(token);
 485         p = strtok(NULL, Delimiter.data());
 486     }
 487 
 488     return result;
 489 }
 490 
 491 // 1. If the path is fewer than three components (Example: one/two/three) then the domain is the
 492 //    default domain "com.apple.java.util.prefs" stored in the plist file
 493 //    ~/Library/Preferences/com.apple.java.util.prefs.plist
 494 //
 495 //    For example: If AppID = "hello", the path is "hello/JVMUserOptions and the
 496 //    plist file is ~/Library/Preferences/com.apple.java.util.prefs.plist containing the contents:
 497 //
 498 //    <?xml version="1.0" encoding="UTF-8"?>
 499 //    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 500 //    <plist version="1.0">
 501 //    <dict>
 502 //      <key>/</key>
 503 //      <dict>
 504 //        <key>hello/</key>
 505 //        <dict>
 506 //          <key>JVMUserOptions/</key>
 507 //          <dict>
 508 //            <key>-DXmx</key>
 509 //            <string>512m</string>
 510 //          </dict>
 511 //        </dict>
 512 //      </dict>
 513 //    </dict>
 514 //    </plist>
 515 //
 516 // 2. If the path is three or more, the first three become the domain name (even
 517 //    if shared across applicaitons) and the remaining become individual keys.
 518 //
 519 //    For example: If AppID = "com/hello/foo", the path is "hello/JVMUserOptions and the
 520 //    domain is "com.hello.foo" stored in the plist file ~/Library/Preferences/com.hello.foo.plist
 521 //    containing the contents:
 522 //
 523 //    <?xml version="1.0" encoding="UTF-8"?>
 524 //    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 525 //    <plist version="1.0">
 526 //    <dict>
 527 //      <key>/com/hello/foo/</key>
 528 //      <dict>
 529 //        <key>JVMUserOptions/</key>
 530 //        <dict>
 531 //          <key>-DXmx</key>
 532 //          <string>512m</string>
 533 //        </dict>
 534 //      </dict>
 535 //    </dict>
 536 //    </plist>
 537 //
 538 // NOTE: To change these values use the command line utility "defaults":
 539 // Example: defaults read com.apple.java.util.prefs /
 540 // Since OS 10.9 Mavericks the defaults are cashed so directly modifying the files is not recommended.
 541 bool MacJavaUserPreferences::Load(TString Appid) {
 542     bool result = false;
 543 
 544     if (Appid.empty() == false) {
 545         // This is for backwards compatability. Older packaged applications have an
 546         // app.preferences.id that is delimited by period (".") rather than
 547         // slash ("/") so convert to newer style.
 548         TString path = Helpers::ReplaceString(Appid, _T("."), _T("/"));
 549 
 550         path = path + _T("/JVMUserOptions");
 551         TString domainName;
 552         std::list<TString> keys = Split(path, _T("/"));
 553 
 554         // If there are less than three parts to the path then use the default preferences file.
 555         if (keys.size() < 3) {
 556             domainName = _T("com.apple.java.util.prefs");
 557 
 558             // Append slash to the end of each key.
 559             for (std::list<TString>::iterator iterator = keys.begin(); iterator != keys.end(); iterator++) {
 560                 TString item = *iterator;
 561                 item = item + _T("/");
 562                 *iterator = item;
 563             }
 564 
 565             // The root key is /.
 566             keys.push_front(_T("/"));
 567         }
 568         else {
 569             // Remove the first three keys and use them for the root key and the preferencesID.
 570             TString one = keys.front();
 571             keys.pop_front();
 572             TString two = keys.front();
 573             keys.pop_front();
 574             TString three = keys.front();
 575             keys.pop_front();
 576             domainName = one + TString(".") + two + TString(".") + three;
 577             domainName = toLowerCase(domainName);
 578 
 579             // Append slash to the end of each key.
 580             for (std::list<TString>::iterator iterator = keys.begin(); iterator != keys.end(); iterator++) {
 581                 TString item = *iterator;
 582                 item = item + _T("/");
 583                 *iterator = item;
 584             }
 585 
 586             // The root key is /one/two/three/
 587             TString key = TString("/") + one + TString("/") + two + TString("/") + three + TString("/");
 588             keys.push_front(key);
 589         }
 590 
 591         UserDefaults userDefaults(domainName);
 592 
 593         if (userDefaults.Read(keys) == true) {
 594             result = true;
 595             FMap = userDefaults.GetData();
 596         }
 597     }
 598 
 599     return result;
 600 }
 601 
 602 //--------------------------------------------------------------------------------------------------
 603 
 604 #endif //MAC