LCOV - code coverage report
Current view: top level - Core - ResourceManager.m (source / functions) Hit Total Coverage
Test: coverxygen.info Lines: 0 61 0.0 %
Date: 2026-01-23 10:53:51 Functions: 0 0 -

          Line data    Source code
       1           0 : /*
       2             : 
       3             : ResourceManager.m
       4             : 
       5             : Oolite
       6             : Copyright (C) 2004-2013 Giles C Williams and contributors
       7             : 
       8             : This program is free software; you can redistribute it and/or
       9             : modify it under the terms of the GNU General Public License
      10             : as published by the Free Software Foundation; either version 2
      11             : of the License, or (at your option) any later version.
      12             : 
      13             : This program is distributed in the hope that it will be useful,
      14             : but WITHOUT ANY WARRANTY; without even the implied warranty of
      15             : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      16             : GNU General Public License for more details.
      17             : 
      18             : You should have received a copy of the GNU General Public License
      19             : along with this program; if not, write to the Free Software
      20             : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
      21             : MA 02110-1301, USA.
      22             : 
      23             : */
      24             : 
      25             : #import "ResourceManager.h"
      26             : #import "NSScannerOOExtensions.h"
      27             : #import "NSMutableDictionaryOOExtensions.h"
      28             : #import "NSStringOOExtensions.h"
      29             : #import "OOSound.h"
      30             : #import "OOCacheManager.h"
      31             : #import "Universe.h"
      32             : #import "OOStringParsing.h"
      33             : #import "OOPListParsing.h"
      34             : #import "MyOpenGLView.h"
      35             : #import "OOCollectionExtractors.h"
      36             : #import "OOLogOutputHandler.h"
      37             : #import "NSFileManagerOOExtensions.h"
      38             : #import "OldSchoolPropertyListWriting.h"
      39             : #import "OOOXZManager.h"
      40             : #import "unzip.h"
      41             : #import "HeadUpDisplay.h"
      42             : #import "OODebugStandards.h"
      43             : #import "OOSystemDescriptionManager.h"
      44             : 
      45             : #import "OOJSScript.h"
      46             : #import "OOPListScript.h"
      47             : 
      48             : #import "OOManifestProperties.h"
      49             : 
      50           0 : static NSString * const kOOLogCacheUpToDate                             = @"dataCache.upToDate";
      51           0 : static NSString * const kOOLogCacheExplicitFlush                = @"dataCache.rebuild.explicitFlush";
      52           0 : static NSString * const kOOLogCacheStalePaths                   = @"dataCache.rebuild.pathsChanged";
      53           0 : static NSString * const kOOLogCacheStaleDates                   = @"dataCache.rebuild.datesChanged";
      54           0 : static NSString * const kOOCacheSearchPathModDates              = @"search path modification dates";
      55           0 : static NSString * const kOOCacheKeySearchPaths                  = @"search paths";
      56           0 : static NSString * const kOOCacheKeyModificationDates    = @"modification dates";
      57             : 
      58             : 
      59             : 
      60           0 : extern NSDictionary* ParseOOSScripts(NSString* script);
      61             : 
      62             : 
      63             : @interface ResourceManager (OOPrivate)
      64             : 
      65           0 : + (void) checkOXPMessagesInPath:(NSString *)path;
      66           0 : + (void) checkPotentialPath:(NSString *)path :(NSMutableArray *)searchPaths;
      67           0 : + (BOOL) validateManifest:(NSDictionary*)manifest forOXP:(NSString *)path;
      68           0 : + (BOOL) areRequirementsFulfilled:(NSDictionary*)requirements forOXP:(NSString *)path andFile:(NSString *)file;
      69           0 : + (void) filterSearchPathsForConflicts:(NSMutableArray *)searchPaths;
      70           0 : + (BOOL) filterSearchPathsForRequirements:(NSMutableArray *)searchPaths;
      71           0 : + (void) filterSearchPathsToExcludeScenarioOnlyPaths:(NSMutableArray *)searchPaths;
      72           0 : + (void) filterSearchPathsByScenario:(NSMutableArray *)searchPaths;
      73           0 : + (BOOL) manifestAllowedByScenario:(NSDictionary *)manifest;
      74           0 : + (BOOL) manifestAllowedByScenario:(NSDictionary *)manifest withIdentifier:(NSString *)identifier;
      75           0 : + (BOOL) manifestAllowedByScenario:(NSDictionary *)manifest withTag:(NSString *)tag;
      76             : 
      77           0 : + (void) addErrorWithKey:(NSString *)descriptionKey param1:(id)param1 param2:(id)param2;
      78           0 : + (BOOL) checkCacheUpToDateForPaths:(NSArray *)searchPaths;
      79           0 : + (void) logPaths;
      80           0 : + (void) mergeRoleCategories:(NSDictionary *)catData intoDictionary:(NSMutableDictionary *)category;
      81           0 : + (void) preloadFileLists;
      82           0 : + (void) preloadFileListFromOXZ:(NSString *)path forFolders:(NSArray *)folders;
      83           0 : + (void) preloadFileListFromFolder:(NSString *)path forFolders:(NSArray *)folders;
      84           0 : + (void) preloadFilePathFor:(NSString *)fileName inFolder:(NSString *)subFolder atPath:(NSString *)path;
      85             : 
      86             : @end
      87             : 
      88             : 
      89           0 : static NSMutableArray   *sSearchPaths;
      90           0 : static NSString                 *sUseAddOns;
      91           0 : static NSArray                  *sUseAddOnsParts;
      92           0 : static BOOL                             sFirstRun = YES;
      93           0 : static BOOL                             sAllMet = NO;
      94           0 : static NSMutableArray   *sOXPsWithMessagesFound;
      95           0 : static NSMutableArray   *sExternalPaths;
      96           0 : static NSMutableArray   *sErrors;
      97           0 : static NSMutableDictionary *sOXPManifests;
      98             : 
      99             : 
     100             : 
     101             : // caches allow us to load any given file once only
     102             : //
     103           0 : static NSMutableDictionary *sSoundCache;
     104           0 : static NSMutableDictionary *sStringCache;
     105             : 
     106             : 
     107             : 
     108             : @implementation ResourceManager
     109             : 
     110             : + (void) reset
     111             : {
     112             :         sFirstRun = YES;
     113             :         DESTROY(sUseAddOns);
     114             :         DESTROY(sUseAddOnsParts);
     115             :         DESTROY(sSearchPaths);
     116             :         DESTROY(sOXPsWithMessagesFound);
     117             :         DESTROY(sExternalPaths);
     118             :         DESTROY(sErrors);
     119             :         DESTROY(sOXPManifests);
     120             : }
     121             : 
     122             : 
     123             : + (void) resetManifestKnowledgeForOXZManager
     124             : {
     125             :         DESTROY(sUseAddOns);
     126             :         DESTROY(sUseAddOnsParts);
     127             :         DESTROY(sSearchPaths);
     128             :         DESTROY(sOXPManifests);
     129             :         [ResourceManager pathsWithAddOns];
     130             : }
     131             : 
     132             : 
     133             : + (NSString *) errors
     134             : {
     135             :         NSArray                                 *error = nil;
     136             :         NSUInteger                              i, count;
     137             :         NSMutableArray                  *result = nil;
     138             :         NSString                                *errStr = nil;
     139             :         
     140             :         count = [sErrors count];
     141             :         if (count == 0)  return nil;
     142             :         
     143             :         // Expand error messages. This is deferred for localizability.
     144             :         result = [NSMutableArray arrayWithCapacity:count];
     145             :         for (i = 0; i != count; ++i)
     146             :         {
     147             :                 error = [sErrors objectAtIndex:i];
     148             :                 errStr = [UNIVERSE descriptionForKey:[error oo_stringAtIndex:0]];
     149             :                 if (errStr != nil)
     150             :                 {
     151             :                         errStr = [NSString stringWithFormat:errStr, [error objectAtIndex:1], [error objectAtIndex:2]];
     152             :                         [result addObject:errStr];
     153             :                 }
     154             :         }
     155             :         
     156             :         [sErrors release];
     157             :         sErrors = nil;
     158             :         
     159             :         return [result componentsJoinedByString:@"\n"];
     160             : }
     161             : 
     162             : 
     163             : + (NSArray *)rootPaths
     164             : {
     165             :         static NSArray *sRootPaths = nil;
     166             :         if (sRootPaths == nil) {
     167             :                 /* Built-in data, then managed OXZs, then manually installed ones,
     168             :                  * which may be useful for debugging/testing purposes. */
     169             :                 sRootPaths = [NSArray arrayWithObjects:[self builtInPath], [[OOOXZManager sharedManager] installPath], nil];
     170             :                 sRootPaths = [[sRootPaths arrayByAddingObjectsFromArray:[self userRootPaths]] retain];
     171             :         }
     172             :         return sRootPaths;
     173             : }
     174             : 
     175             : 
     176             : + (NSArray *)userRootPaths
     177             : {
     178             :         static NSArray                  *sUserRootPaths = nil;
     179             :         
     180             :         if (sUserRootPaths == nil)
     181             :         {
     182             :                 // the paths are now in order of preference as per yesterday's talk. -- Kaks 2010-05-05
     183             :                 NSArray *defaultAddOnsPaths = [NSArray arrayWithObjects:
     184             : #if OOLITE_MAC_OS_X
     185             :                                           [[[[NSHomeDirectory() stringByAppendingPathComponent:@"Library"]
     186             :                                                  stringByAppendingPathComponent:@"Application Support"]
     187             :                                                  stringByAppendingPathComponent:@"Oolite"]
     188             :                                             stringByAppendingPathComponent:@"AddOns"],
     189             :                                           [[[[NSBundle mainBundle] bundlePath]
     190             :                                                  stringByDeletingLastPathComponent]
     191             :                                             stringByAppendingPathComponent:@"AddOns"],
     192             : #endif
     193             : #if !OOLITE_WINDOWS
     194             :                       @"AddOns",
     195             : #endif
     196             :                                           [[OOOXZManager sharedManager] extractAddOnsPath],
     197             :                                           nil];
     198             :         sUserRootPaths = [[[[OOOXZManager sharedManager] additionalAddOnsPaths] arrayByAddingObjectsFromArray:defaultAddOnsPaths] retain];
     199             :         }
     200             :         OOLog(@"searchPaths.debug",@"%@",sUserRootPaths);
     201             :         return sUserRootPaths;
     202             : }
     203             : 
     204             : 
     205             : + (NSString *)builtInPath
     206             : {
     207             : #if OOLITE_WINDOWS
     208             :         /*      [[NSBundle mainBundle] resourcePath] causes complaints under Windows,
     209             :                 because we don't have a properly-built bundle.
     210             :         */
     211             :         return @"Resources";
     212             : #else
     213             :         return [[NSBundle mainBundle] resourcePath];
     214             : #endif
     215             : }
     216             : 
     217             : 
     218             : + (NSArray *)pathsWithAddOns
     219             : {
     220             :         if ([sSearchPaths count] > 0)  return sSearchPaths;
     221             : 
     222             :         if (sUseAddOns == nil)
     223             :         {
     224             :                 sUseAddOns = [[NSString alloc] initWithString:SCENARIO_OXP_DEFINITION_ALL];
     225             :                 sUseAddOnsParts = [[sUseAddOns componentsSeparatedByString:@";"] retain];
     226             :         }
     227             :         
     228             :         /* Handle special case of 'strict mode' efficiently */
     229             :         // testing actual string
     230             :         if ([sUseAddOns isEqualToString:SCENARIO_OXP_DEFINITION_NONE])
     231             :         {
     232             :                 return (NSArray *)[NSArray arrayWithObject:[self builtInPath]];
     233             :         }
     234             : 
     235             :         [sErrors release];
     236             :         sErrors = nil;
     237             :         
     238             :         NSFileManager                   *fmgr = [NSFileManager defaultManager];
     239             :         NSArray                                 *rootPaths = nil;
     240             :         NSMutableArray                  *existingRootPaths = nil;
     241             :         NSEnumerator                    *pathEnum = nil;
     242             :         NSString                                *root = nil;
     243             :         NSDirectoryEnumerator   *dirEnum = nil;
     244             :         NSString                                *subPath = nil;
     245             :         NSString                                *path = nil;
     246             :         BOOL                                    isDirectory;
     247             :         
     248             :         // Copy those root paths that actually exist to search paths.
     249             :         rootPaths = [self rootPaths];
     250             :         existingRootPaths = [NSMutableArray arrayWithCapacity:[rootPaths count]];
     251             :         for (pathEnum = [rootPaths objectEnumerator]; (root = [pathEnum nextObject]); )
     252             :         {
     253             :                 if ([fmgr fileExistsAtPath:root isDirectory:&isDirectory] && isDirectory)
     254             :                 {
     255             :                         [existingRootPaths addObject:root];
     256             :                 }
     257             :         }
     258             :         
     259             :         // validate default search paths
     260             :         DESTROY(sSearchPaths);
     261             :         sSearchPaths = [NSMutableArray new];
     262             :         foreach(path, existingRootPaths)
     263             :         {
     264             :                 [self checkPotentialPath:path :sSearchPaths];
     265             :         }
     266             :         
     267             :         // Iterate over root paths.
     268             :         for (pathEnum = [existingRootPaths objectEnumerator]; (root = [pathEnum nextObject]); )
     269             :         {
     270             :                 // Iterate over each root path's contents.
     271             :                 if ([fmgr fileExistsAtPath:root isDirectory:&isDirectory] && isDirectory)
     272             :                 {
     273             :                         for (dirEnum = [fmgr enumeratorAtPath:root]; (subPath = [dirEnum nextObject]); )
     274             :                         {
     275             :                                 // Check if it's a directory.
     276             :                                 path = [root stringByAppendingPathComponent:subPath];
     277             :                                 if ([fmgr fileExistsAtPath:path isDirectory:&isDirectory])
     278             :                                 {
     279             :                                         if (isDirectory)
     280             :                                         {
     281             :                                                 // If it is, is it an OXP?.
     282             :                                                 if ([[[path pathExtension] lowercaseString] isEqualToString:@"oxp"])
     283             :                                                 {
     284             :                                                         [self checkPotentialPath:path :sSearchPaths];
     285             :                                                         if ([sSearchPaths containsObject:path])  [self checkOXPMessagesInPath:path];
     286             :                                                 }
     287             :                                                 else
     288             :                                                 {
     289             :                                                         // If not, don't search subdirectories.
     290             :                                                         [dirEnum skipDescendents];
     291             :                                                 }
     292             :                                         }
     293             :                                         else
     294             :                                         {
     295             :                                                 // If not a directory, is it an OXZ?
     296             :                                                 if ([[[path pathExtension] lowercaseString] isEqualToString:@"oxz"])
     297             :                                                 {
     298             :                                                         [self checkPotentialPath:path :sSearchPaths];
     299             :                                                         if ([sSearchPaths containsObject:path])  [self checkOXPMessagesInPath:path];
     300             :                                                 }
     301             :                                         }
     302             :                                 }
     303             :                         }
     304             :                 }
     305             :         }
     306             :         
     307             :         for (pathEnum = [sExternalPaths objectEnumerator]; (path = [pathEnum nextObject]); )
     308             :         {
     309             :                 [self checkPotentialPath:path :sSearchPaths];
     310             :                 if ([sSearchPaths containsObject:path])  [self checkOXPMessagesInPath:path];
     311             :         }
     312             : 
     313             :         /* If a scenario restriction is *not* in place, remove
     314             :          * scenario-only OXPs. */
     315             :         // test string
     316             :         if ([sUseAddOns isEqualToString:SCENARIO_OXP_DEFINITION_ALL])
     317             :         {
     318             :                 [self filterSearchPathsToExcludeScenarioOnlyPaths:sSearchPaths];
     319             :         }
     320             : 
     321             :         /* This is a conservative filter. It probably gets rid of more
     322             :          * OXPs than it technically needs to in certain situations with
     323             :          * dependency chains, but really any conflict here needs to be
     324             :          * resolved by the user rather than Oolite. The point is to avoid
     325             :          * loading OXPs which we shouldn't; if doing so takes out other
     326             :          * OXPs which would have been safe, that's not important. */
     327             :         [self filterSearchPathsForConflicts:sSearchPaths];
     328             : 
     329             :         /* This one needs to be run repeatedly to be sure. Take the chain
     330             :          * A depends on B depends on C. A and B are installed. A is
     331             :          * checked first, and depends on B, which is thought to be
     332             :          * okay. So A is kept. Then B is checked and removed. A must then
     333             :          * be rechecked. This function therefore is run repeatedly until a
     334             :          * run of it removes no further items.
     335             :          *
     336             :          * There may well be more elegant and efficient ways to do this
     337             :          * but this is already fast enough for most purposes.
     338             :          */
     339             :         while (![self filterSearchPathsForRequirements:sSearchPaths]) {}
     340             : 
     341             :         /* If a scenario restriction is in place, restrict OXPs to the
     342             :          * ones valid for the scenario only. */
     343             :         // test string
     344             :         if (![sUseAddOns isEqualToString:SCENARIO_OXP_DEFINITION_ALL])
     345             :         {
     346             :                 [self filterSearchPathsByScenario:sSearchPaths];
     347             :         }
     348             : 
     349             :         [self checkCacheUpToDateForPaths:sSearchPaths];
     350             :         
     351             :         return sSearchPaths;
     352             : }
     353             : 
     354             : 
     355           0 : + (void) preloadFileLists
     356             : {
     357             :         NSString                 *path = nil;
     358             :         NSEnumerator *pathEnum = nil;
     359             : 
     360             :         // folders which may contain files to be cached
     361             :         NSArray *folders = [NSArray arrayWithObjects:@"AIs",@"Images",@"Models",@"Music",@"Scenarios",@"Scripts",@"Shaders",@"Sounds",@"Textures",nil];
     362             : 
     363             :         for (pathEnum = [[ResourceManager paths] reverseObjectEnumerator]; (path = [pathEnum nextObject]); )
     364             :         {
     365             :                 if ([path hasSuffix:@".oxz"])
     366             :                 {
     367             :                         [self preloadFileListFromOXZ:path forFolders:folders];
     368             :                 }
     369             :                 else
     370             :                 {
     371             :                         [self preloadFileListFromFolder:path forFolders:folders];
     372             :                 }
     373             :         }
     374             : }
     375             : 
     376             : 
     377           0 : + (void) preloadFileListFromOXZ:(NSString *)path forFolders:(NSArray *)folders
     378             : {
     379             :         unzFile uf = NULL;
     380             :         const char* zipname = [path UTF8String];
     381             :         char componentName[512];
     382             : 
     383             :         if (zipname != NULL)
     384             :         {
     385             :                 uf = unzOpen64(zipname);
     386             :         }
     387             :         if (uf == NULL)
     388             :         {
     389             :                 OOLog(@"resourceManager.error",@"Could not open .oxz at %@ as zip file",path);
     390             :                 return;
     391             :         }
     392             :         if (unzGoToFirstFile(uf) == UNZ_OK)
     393             :         {
     394             :                 do 
     395             :                 {
     396             :                         unzGetCurrentFileInfo64(uf, NULL,
     397             :                                                                         componentName, 512,
     398             :                                                                         NULL, 0,
     399             :                                                                         NULL, 0);
     400             :                         NSString *zipEntry = [NSString stringWithUTF8String:componentName];
     401             :                         NSArray *pathBits = [zipEntry pathComponents];
     402             :                         if ([pathBits count] >= 2)
     403             :                         {
     404             :                                 NSString *folder = [pathBits oo_stringAtIndex:0];
     405             :                                 if ([folders containsObject:folder])
     406             :                                 {
     407             :                                         NSRange bitRange;
     408             :                                         bitRange.location = 1;
     409             :                                         bitRange.length = [pathBits count]-1;
     410             :                                         NSString *file = [NSString pathWithComponents:[pathBits subarrayWithRange:bitRange]];
     411             :                                         NSString *fullPath = [[path stringByAppendingPathComponent:folder] stringByAppendingPathComponent:file];
     412             :                                         
     413             :                                         [self preloadFilePathFor:file inFolder:folder atPath:fullPath];
     414             :                                 }
     415             :                         }
     416             : 
     417             :                 } 
     418             :                 while (unzGoToNextFile(uf) == UNZ_OK);
     419             :         }
     420             :         unzClose(uf);
     421             : }
     422             : 
     423             : 
     424           0 : + (void) preloadFileListFromFolder:(NSString *)path forFolders:(NSArray *)folders
     425             : {
     426             :         NSFileManager *fmgr             = [NSFileManager defaultManager];
     427             :         NSString *subFolder             = nil;
     428             :         NSString *subFolderPath         = nil;
     429             :         NSArray *fileList                       = nil;
     430             :         NSString *fileName                      = nil;
     431             : 
     432             :         // search each subfolder for files
     433             :         foreach (subFolder, folders)
     434             :         {
     435             :                 subFolderPath = [path stringByAppendingPathComponent:subFolder];
     436             :                 fileList = [fmgr oo_directoryContentsAtPath:subFolderPath];
     437             :                 foreach (fileName, fileList)
     438             :                 {
     439             :                         [self preloadFilePathFor:fileName inFolder:subFolder atPath:[subFolderPath stringByAppendingPathComponent:fileName]];
     440             :                 }
     441             :         }
     442             : }
     443             : 
     444             : 
     445           0 : + (void) preloadFilePathFor:(NSString *)fileName inFolder:(NSString *)subFolder atPath:(NSString *)path
     446             : {
     447             :         OOCacheManager  *cache = [OOCacheManager sharedCache];
     448             :         NSString *cacheKey = [NSString stringWithFormat:@"%@/%@", subFolder, fileName];
     449             :         NSString *result = [cache objectForKey:cacheKey inCache:@"resolved paths"];
     450             :         // if nil, not found in another OXP already
     451             :         if (result == nil)
     452             :         {
     453             :                 OOLog(@"resourceManager.foundFile.preLoad", @"Found %@/%@ at %@", subFolder, fileName, path);
     454             :                 [cache setObject:path forKey:cacheKey inCache:@"resolved paths"];
     455             :         }
     456             : }
     457             : 
     458             : 
     459             : + (NSArray *)paths
     460             : {
     461             :         if (EXPECT_NOT(sSearchPaths == nil))
     462             :         {
     463             :                 sSearchPaths = [[NSMutableArray alloc] init];
     464             :         }
     465             :         return [self pathsWithAddOns];
     466             : }
     467             : 
     468             : 
     469             : + (NSArray *)maskUserNameInPathArray:(NSArray *)inputPathArray
     470             : {
     471             :         NSString *path = nil;
     472             :         NSMutableArray *maskedArray = [NSMutableArray arrayWithCapacity:[inputPathArray count]];
     473             :         char *userNamePathEnvVar = 
     474             : #if OOLITE_WINDOWS
     475             :                 SDL_getenv("USERPROFILE");
     476             : #else
     477             :                 SDL_getenv("HOME");
     478             : #endif
     479             :         NSString *userName = [[NSString stringWithFormat:@"%s", userNamePathEnvVar] lastPathComponent];
     480             :         foreach (path, inputPathArray)
     481             :         {
     482             :                 path = [self maskUserName:userName inPath:path];
     483             :                 [maskedArray addObject:path];
     484             :         }
     485             :         return maskedArray;
     486             : }
     487             : 
     488             : 
     489             : + (NSString *)maskUserName:(NSString *)name inPath:(NSString *)path
     490             : {
     491             :         NSMutableString *result = [NSMutableString stringWithString:path];
     492             :         if (result && name)  [result replaceOccurrencesOfString:name withString:@"*"
     493             :                                                         options:NSLiteralSearch
     494             :                                                         range:NSMakeRange(0, [result length])
     495             :                                                  ];
     496             :         return [NSString stringWithString:result];
     497             : }
     498             : 
     499             : 
     500             : + (NSString *)useAddOns
     501             : {
     502             :         return sUseAddOns;
     503             : }
     504             : 
     505             : 
     506             : + (void)setUseAddOns:(NSString *)useAddOns
     507             : {
     508             :         if (sFirstRun || ![useAddOns isEqualToString:sUseAddOns])
     509             :         {
     510             :                 [self reset];
     511             :                 sFirstRun = NO;
     512             :                 DESTROY(sUseAddOnsParts);
     513             :                 DESTROY(sUseAddOns);
     514             :                 sUseAddOns = [useAddOns retain];
     515             :                 sUseAddOnsParts = [[sUseAddOns componentsSeparatedByString:@";"] retain];
     516             : 
     517             :                 [ResourceManager clearCaches];
     518             :                 OOHUDResetTextEngine();
     519             : 
     520             :                 OOCacheManager *cmgr = [OOCacheManager sharedCache];
     521             :                 /* only allow cache writes for the "all OXPs" default
     522             :                  *
     523             :                  * cache should be less necessary for restricted sets anyway */
     524             :                 // testing the actual string here
     525             :                 if ([sUseAddOns isEqualToString:SCENARIO_OXP_DEFINITION_ALL])
     526             :                 {
     527             :                         [cmgr reloadAllCaches];
     528             :                         [cmgr setAllowCacheWrites:YES];
     529             :                 }
     530             :                 else
     531             :                 {
     532             :                         [cmgr clearAllCaches];
     533             :                         [cmgr setAllowCacheWrites:NO];
     534             :                 }
     535             :                 
     536             :                 [self checkCacheUpToDateForPaths:[self paths]];
     537             :                 [self logPaths];
     538             :                 /* preloading the file lists at this stage helps efficiency a
     539             :                  * lot when many OXZs are installed */
     540             :                 [self preloadFileLists];
     541             : 
     542             :         }
     543             : }
     544             : 
     545             : 
     546             : + (void) addExternalPath:(NSString *)path
     547             : {
     548             :         if (sSearchPaths == nil)  sSearchPaths = [[NSMutableArray alloc] init];
     549             :         if (![sSearchPaths containsObject:path])
     550             :         {
     551             :                 [sSearchPaths addObject:path];
     552             :                 
     553             :                 if (sExternalPaths == nil)  sExternalPaths = [[NSMutableArray alloc] init];
     554             :                 [sExternalPaths addObject:path];
     555             :         }
     556             : }
     557             : 
     558             : 
     559             : + (NSEnumerator *)pathEnumerator
     560             : {
     561             :         return [[self paths] objectEnumerator];
     562             : }
     563             : 
     564             : 
     565             : + (NSEnumerator *)reversePathEnumerator
     566             : {
     567             :         return [[self paths] reverseObjectEnumerator];
     568             : }
     569             : 
     570             : 
     571             : + (NSArray *)OXPsWithMessagesFound
     572             : {
     573             :         return [[sOXPsWithMessagesFound copy] autorelease];
     574             : }
     575             : 
     576             : 
     577             : + (NSDictionary *)manifestForIdentifier:(NSString *)identifier
     578             : {
     579             :         return [sOXPManifests objectForKey:identifier];
     580             : }
     581             : 
     582             : 
     583           0 : + (void) checkOXPMessagesInPath:(NSString *)path
     584             : {
     585             :         NSArray *OXPMessageArray = OOArrayFromFile([path stringByAppendingPathComponent:@"OXPMessages.plist"]);
     586             :         
     587             :         if ([OXPMessageArray count] > 0)
     588             :         {
     589             :                 unsigned i;
     590             :                 for (i = 0; i < [OXPMessageArray count]; i++)
     591             :                 {
     592             :                         NSString *oxpMessage = [OXPMessageArray oo_stringAtIndex:i];
     593             :                         if (oxpMessage)
     594             :                         {
     595             :                                 OOLog(@"oxp.message", @"%@: %@", path, oxpMessage);
     596             :                         }
     597             :                 }
     598             :                 if (sOXPsWithMessagesFound == nil)  sOXPsWithMessagesFound = [[NSMutableArray alloc] init];
     599             :                 [sOXPsWithMessagesFound addObject:[path lastPathComponent]];
     600             :         }
     601             : }
     602             : 
     603             : 
     604             : // Given a path to an assumed OXP (or other location where files are permissible), check for a requires.plist or manifest.plist and add to search paths if acceptable.
     605           0 : + (void)checkPotentialPath:(NSString *)path :(NSMutableArray *)searchPaths
     606             : {
     607             :         NSDictionary                    *requirements = nil;
     608             :         NSDictionary                    *manifest = nil;
     609             :         BOOL                                    requirementsMet = YES;
     610             : 
     611             :         if (![[[path pathExtension] lowercaseString] isEqualToString:@"oxz"])
     612             :         {
     613             :                 // OXZ format ignores requires.plist
     614             :                 requirements = OODictionaryFromFile([path stringByAppendingPathComponent:@"requires.plist"]);
     615             :                 requirementsMet = [self areRequirementsFulfilled:requirements forOXP:path andFile:@"requires.plist"];
     616             :         }
     617             :         if (!requirementsMet)
     618             :         {
     619             :                 NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
     620             :                 OOLog(@"oxp.versionMismatch", @"OXP %@ is incompatible with version %@ of Oolite.", path, version);
     621             :                 [self addErrorWithKey:@"oxp-is-incompatible" param1:[path lastPathComponent] param2:version];
     622             :                 return;
     623             :         }
     624             :         
     625             :         manifest = OODictionaryFromFile([path stringByAppendingPathComponent:@"manifest.plist"]);
     626             :         if (manifest == nil)
     627             :         {
     628             :                 if ([[[path pathExtension] lowercaseString] isEqualToString:@"oxz"])
     629             :                 {
     630             :                         OOLog(@"oxp.noManifest", @"OXZ %@ has no manifest.plist", path);
     631             :                         [self addErrorWithKey:@"oxz-lacks-manifest" param1:[path lastPathComponent] param2:nil];
     632             :                         return;
     633             :                 }
     634             :                 else
     635             :                 {
     636             :                         if ([[[path pathExtension] lowercaseString] isEqualToString:@"oxp"])
     637             :                         {
     638             :                                 OOStandardsError([NSString stringWithFormat:@"OXP %@ has no manifest.plist", path]);
     639             :                                 if (OOEnforceStandards())
     640             :                                 {
     641             :                                         [self addErrorWithKey:@"oxp-lacks-manifest" param1:[path lastPathComponent] param2:nil];
     642             :                                         return;
     643             :                                 }
     644             :                         }
     645             :                         // make up a basic manifest in relaxed mode or for base folders
     646             :                         manifest = [NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"__oolite.tmp.%@",path],kOOManifestIdentifier,@"1",kOOManifestVersion,@"OXP without manifest",kOOManifestTitle,@"1",kOOManifestRequiredOoliteVersion,nil];
     647             :                 }
     648             :         }
     649             :         
     650             :         requirementsMet = [self validateManifest:manifest forOXP:path];
     651             : 
     652             : 
     653             :         if (requirementsMet) 
     654             :         {
     655             :                 [searchPaths addObject:path];
     656             :         }
     657             : }
     658             : 
     659             : 
     660           0 : + (BOOL) validateManifest:(NSDictionary*)manifest forOXP:(NSString *)path
     661             : {
     662             :         if (EXPECT_NOT(sOXPManifests == nil))
     663             :         {
     664             :                 sOXPManifests = [[NSMutableDictionary alloc] initWithCapacity:32];
     665             :         }
     666             :         
     667             :         BOOL            OK = YES;
     668             :         NSString        *identifier = [manifest oo_stringForKey:kOOManifestIdentifier defaultValue:nil];
     669             :         NSString        *version = [manifest oo_stringForKey:kOOManifestVersion defaultValue:nil];
     670             :         NSString        *required = [manifest oo_stringForKey:kOOManifestRequiredOoliteVersion defaultValue:nil];
     671             :         NSString        *title = [manifest oo_stringForKey:kOOManifestTitle defaultValue:nil];
     672             : 
     673             :         if (identifier == nil)
     674             :         {
     675             :                 OOLog(@"oxp.noManifest", @"OXZ %@ manifest.plist has no '%@' field.", path, kOOManifestIdentifier);
     676             :                 [self addErrorWithKey:@"oxp-manifest-incomplete" param1:title param2:kOOManifestIdentifier];
     677             :                 OK = NO;
     678             :         }
     679             :         if (version == nil)
     680             :         {
     681             :                 OOLog(@"oxp.noManifest", @"OXZ %@ manifest.plist has no '%@' field.", path, kOOManifestVersion);
     682             :                 [self addErrorWithKey:@"oxp-manifest-incomplete" param1:title param2:kOOManifestVersion];
     683             :                 OK = NO;
     684             :         }
     685             :         if (required == nil)
     686             :         {
     687             :                 OOLog(@"oxp.noManifest", @"OXZ %@ manifest.plist has no '%@' field.", path, kOOManifestRequiredOoliteVersion);
     688             :                 [self addErrorWithKey:@"oxp-manifest-incomplete" param1:title param2:kOOManifestRequiredOoliteVersion];
     689             :                 OK = NO;
     690             :         }
     691             :         if (title == nil)
     692             :         {
     693             :                 OOLog(@"oxp.noManifest", @"OXZ %@ manifest.plist has no '%@' field.", path, kOOManifestTitle);
     694             :                 [self addErrorWithKey:@"oxp-manifest-incomplete" param1:title param2:kOOManifestTitle];
     695             :                 OK = NO;
     696             :         }
     697             :         if (!OK)
     698             :         {
     699             :                 return NO;
     700             :         }
     701             :         OK = [self checkVersionCompatibility:manifest forOXP:title];
     702             : 
     703             :         if (!OK)
     704             :         {
     705             :                 NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
     706             :                 OOLog(@"oxp.versionMismatch", @"OXP %@ is incompatible with version %@ of Oolite.", path, version);
     707             :                 [self addErrorWithKey:@"oxp-is-incompatible" param1:[path lastPathComponent] param2:version];
     708             :                 return NO;
     709             :         }
     710             : 
     711             :         NSDictionary *duplicate = [sOXPManifests objectForKey:identifier];
     712             :         if (duplicate != nil)
     713             :         {
     714             :                 OOLog(@"oxp.duplicate", @"OXP %@ has the same identifier (%@) as %@ which has already been loaded.",path,identifier,[duplicate oo_stringForKey:kOOManifestFilePath]);
     715             :                 [self addErrorWithKey:@"oxp-manifest-duplicate" param1:path param2:[duplicate oo_stringForKey:kOOManifestFilePath]];
     716             :                 return NO;
     717             :         }
     718             :         NSMutableDictionary *mData = [NSMutableDictionary dictionaryWithDictionary:manifest];
     719             :         [mData setObject:path forKey:kOOManifestFilePath];
     720             :         // add an extra key
     721             :         [sOXPManifests setObject:mData forKey:identifier];
     722             :         return YES;
     723             : }
     724             : 
     725             : 
     726             : + (BOOL) checkVersionCompatibility:(NSDictionary *)manifest forOXP:(NSString *)title
     727             : {
     728             :         NSString        *required = [manifest oo_stringForKey:kOOManifestRequiredOoliteVersion defaultValue:nil];
     729             :         NSString *maxRequired = [manifest oo_stringForKey:kOOManifestMaximumOoliteVersion defaultValue:nil];
     730             :         // ignore empty max version string rather than treating as "version 0"
     731             :         if (maxRequired == nil || [maxRequired length] == 0)
     732             :         {
     733             :                 return [self areRequirementsFulfilled:[NSDictionary dictionaryWithObjectsAndKeys:required, @"version", nil] forOXP:title andFile:@"manifest.plist"];
     734             :         }
     735             :         else
     736             :         {
     737             :                 return [self areRequirementsFulfilled:[NSDictionary dictionaryWithObjectsAndKeys:required, @"version", maxRequired, @"max_version", nil] forOXP:title andFile:@"manifest.plist"];
     738             :         }
     739             : }
     740             : 
     741             : 
     742           0 : + (BOOL) areRequirementsFulfilled:(NSDictionary*)requirements forOXP:(NSString *)path andFile:(NSString *)file
     743             : {
     744             :         BOOL                            OK = YES;
     745             :         NSString                        *requiredVersion = nil;
     746             :         NSString                        *maxVersion = nil;
     747             :         unsigned                        conditionsHandled = 0;
     748             :         static NSArray          *ooVersionComponents = nil;
     749             :         NSArray                         *oxpVersionComponents = nil;
     750             :         
     751             :         if (requirements == nil)  return YES;
     752             :         
     753             :         if (ooVersionComponents == nil)
     754             :         {
     755             :                 ooVersionComponents = ComponentsFromVersionString([[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]);
     756             :                 [ooVersionComponents retain];
     757             :         }
     758             :         
     759             :         // Check "version" (minimum version)
     760             :         if (OK)
     761             :         {
     762             :                 // Not oo_stringForKey:, because we need to be able to complain about non-strings.
     763             :                 requiredVersion = [requirements objectForKey:@"version"];
     764             :                 if (requiredVersion != nil)
     765             :                 {
     766             :                         ++conditionsHandled;
     767             :                         if ([requiredVersion isKindOfClass:[NSString class]])
     768             :                         {
     769             :                                 oxpVersionComponents = ComponentsFromVersionString(requiredVersion);
     770             :                                 if (NSOrderedAscending == CompareVersions(ooVersionComponents, oxpVersionComponents))  OK = NO;
     771             :                         }
     772             :                         else
     773             :                         {
     774             :                                 OOLog(@"requirements.wrongType", @"Expected %@ entry \"%@\" to be string, but got %@ in OXP %@.", file, @"version", [requirements class], [path lastPathComponent]);
     775             :                                 OK = NO;
     776             :                         }
     777             :                 }
     778             :         }
     779             :         
     780             :         // Check "max_version" (minimum max_version)
     781             :         if (OK)
     782             :         {
     783             :                 // Not oo_stringForKey:, because we need to be able to complain about non-strings.
     784             :                 maxVersion = [requirements objectForKey:@"max_version"];
     785             :                 if (maxVersion != nil)
     786             :                 {
     787             :                         ++conditionsHandled;
     788             :                         if ([maxVersion isKindOfClass:[NSString class]])
     789             :                         {
     790             :                                 oxpVersionComponents = ComponentsFromVersionString(maxVersion);
     791             :                                 if (NSOrderedDescending == CompareVersions(ooVersionComponents, oxpVersionComponents))  OK = NO;
     792             :                         }
     793             :                         else
     794             :                         {
     795             :                                 OOLog(@"requirements.wrongType", @"Expected %@ entry \"%@\" to be string, but got %@ in OXP %@.", file, @"max_version", [requirements class], [path lastPathComponent]);
     796             :                                 OK = NO;
     797             :                         }
     798             :                 }
     799             :         }
     800             :         
     801             :         if (OK && conditionsHandled < [requirements count])
     802             :         {
     803             :                 // There are unknown requirement keys - don't support. NOTE: this check was not made pre 1.69!
     804             :                 OOLog(@"requirements.unknown", @"requires.plist for OXP %@ contains unknown keys, rejecting.", [path lastPathComponent]);
     805             :                 OK = NO;
     806             :         }
     807             :         
     808             :         return OK;
     809             : }
     810             : 
     811             : 
     812             : + (BOOL) manifestHasConflicts:(NSDictionary *)manifest logErrors:(BOOL)logErrors
     813             : {
     814             :         NSDictionary    *conflicting = nil;
     815             :         NSDictionary    *conflictManifest = nil;
     816             :         NSString                *conflictID = nil;
     817             :         NSArray                 *conflicts = nil;
     818             :         
     819             :         conflicts = [manifest oo_arrayForKey:kOOManifestConflictOXPs defaultValue:nil];
     820             :         // if it has a non-empty conflict_oxps list 
     821             :         if (conflicts != nil && [conflicts count] > 0)
     822             :         {
     823             :                 // iterate over that list
     824             :                 foreach (conflicting, conflicts)
     825             :                 {
     826             :                         conflictID = [conflicting oo_stringForKey:kOOManifestRelationIdentifier];
     827             :                         conflictManifest = [sOXPManifests objectForKey:conflictID];
     828             :                         // if the other OXP is in the list
     829             :                         if (conflictManifest != nil)
     830             :                         {
     831             :                                 // then check versions
     832             :                                 if ([self matchVersions:conflicting withVersion:[conflictManifest oo_stringForKey:kOOManifestVersion]])
     833             :                                 {
     834             :                                         if (logErrors)
     835             :                                         {
     836             :                                                 [self addErrorWithKey:@"oxp-conflict" param1:[manifest oo_stringForKey:kOOManifestTitle] param2:[conflictManifest oo_stringForKey:kOOManifestTitle]];
     837             :                                                 OOLog(@"oxp.conflict",@"OXP %@ conflicts with %@ and was removed from the loading list",[[manifest oo_stringForKey:kOOManifestFilePath] lastPathComponent],[[conflictManifest oo_stringForKey:kOOManifestFilePath] lastPathComponent]);
     838             :                                         }
     839             :                                         return YES;
     840             :                                 }
     841             :                         }
     842             :                 }
     843             :         }
     844             :         return NO;
     845             : }
     846             : 
     847             : 
     848           0 : + (void) filterSearchPathsForConflicts:(NSMutableArray *)searchPaths
     849             : {
     850             :         NSDictionary    *manifest = nil;
     851             :         NSString                *identifier = nil;
     852             :         NSArray                 *identifiers = [sOXPManifests allKeys];
     853             : 
     854             :         // take a copy because we'll mutate the original
     855             :         // foreach identified add-on
     856             :         foreach (identifier, identifiers)
     857             :         {
     858             :                 manifest = [sOXPManifests objectForKey:identifier];
     859             :                 if (manifest != nil)
     860             :                 {
     861             :                         if ([self manifestHasConflicts:manifest logErrors:YES])
     862             :                         {
     863             :                                 // then we have a conflict, so remove this path
     864             :                                 [searchPaths removeObject:[manifest oo_stringForKey:kOOManifestFilePath]];
     865             :                                 [sOXPManifests removeObjectForKey:identifier];
     866             :                         }
     867             :                 }
     868             :         }
     869             : }
     870             : 
     871             : 
     872             : + (BOOL) manifestHasMissingDependencies:(NSDictionary *)manifest logErrors:(BOOL)logErrors
     873             : {
     874             :         NSDictionary    *required = nil;
     875             :         NSArray                 *requireds = nil;
     876             : 
     877             :         requireds = [manifest oo_arrayForKey:kOOManifestRequiresOXPs defaultValue:nil];
     878             :         // if it has a non-empty required_oxps list 
     879             :         if (requireds != nil && [requireds count] > 0)
     880             :         {
     881             :                 // iterate over that list
     882             :                 foreach (required, requireds)
     883             :                 {
     884             :                         if ([ResourceManager manifest:manifest HasUnmetDependency:required logErrors:logErrors])
     885             :                         {
     886             :                                 return YES;
     887             :                         }
     888             :                 }
     889             :         }
     890             :         return NO;
     891             : }
     892             : 
     893             : 
     894             : + (BOOL) manifest:(NSDictionary *)manifest HasUnmetDependency:(NSDictionary *)required logErrors:(BOOL)logErrors
     895             : {
     896             :         NSString                *requiredID = [required oo_stringForKey:kOOManifestRelationIdentifier];
     897             :         NSMutableDictionary     *requiredManifest = [sOXPManifests objectForKey:requiredID];
     898             :         // if the other OXP is in the list
     899             :         BOOL requirementsMet = NO;
     900             :         if (requiredManifest != nil)
     901             :         {
     902             :                 // then check versions
     903             :                 if ([self matchVersions:required withVersion:[requiredManifest oo_stringForKey:kOOManifestVersion]])
     904             :                 {
     905             :                         requirementsMet = YES;
     906             :                         /* Mark the requiredManifest as a dependency of the
     907             :                          * requiring manifest */
     908             :                         NSSet *reqby = [requiredManifest oo_setForKey:kOOManifestRequiredBy defaultValue:[NSSet set]];
     909             :                         NSUInteger reqbycount = [reqby count];
     910             :                         /* then add this manifest to its required set. This is
     911             :                          * done without checking if it's already there, because
     912             :                          * the list of nested requirements may have changed. */
     913             :                         reqby = [reqby setByAddingObject:[manifest oo_stringForKey:kOOManifestIdentifier]];
     914             :                         // *and* anything that requires this OXP to be installed
     915             :                         reqby = [reqby setByAddingObjectsFromSet:[manifest oo_setForKey:kOOManifestRequiredBy]];
     916             :                         if (reqbycount < [reqby count])
     917             :                         {
     918             :                                 /* Then the set has increased in size. To handle
     919             :                                  * potential cases with nested dependencies, need to
     920             :                                  * re-run the requirement filter until all the sets
     921             :                                  * stabilise. */
     922             :                                 sAllMet = NO;
     923             :                         }
     924             :                         // and push back into the requiring manifest
     925             :                         [requiredManifest setObject:reqby forKey:kOOManifestRequiredBy];
     926             :                 }
     927             :         }
     928             :         if (!requirementsMet)
     929             :         {
     930             :                 if (logErrors)
     931             :                 {
     932             :                         [self addErrorWithKey:@"oxp-required" param1:[manifest oo_stringForKey:kOOManifestTitle] param2:[required oo_stringForKey:kOOManifestRelationDescription defaultValue:[required oo_stringForKey:kOOManifestRelationIdentifier]]];
     933             :                         OOLog(@"oxp.requirementMissing",@"OXP %@ had unmet requirements and was removed from the loading list",[[manifest oo_stringForKey:kOOManifestFilePath] lastPathComponent]);
     934             :                 }
     935             :                 return YES;
     936             :         }
     937             :         return NO;
     938             : }
     939             : 
     940             : 
     941           0 : + (BOOL) filterSearchPathsForRequirements:(NSMutableArray *)searchPaths
     942             : {
     943             :         NSDictionary    *manifest = nil;
     944             :         NSString                *identifier = nil;
     945             :         NSArray                 *identifiers = [sOXPManifests allKeys];
     946             : 
     947             :         sAllMet = YES;
     948             : 
     949             :         // take a copy because we'll mutate the original
     950             :         // foreach identified add-on
     951             :         foreach (identifier, identifiers)
     952             :         {
     953             :                 manifest = [sOXPManifests objectForKey:identifier];
     954             :                 if (manifest != nil)
     955             :                 {
     956             :                         if ([self manifestHasMissingDependencies:manifest logErrors:YES])
     957             :                         {
     958             :                                 // then we have a missing requirement, so remove this path
     959             :                                 [searchPaths removeObject:[manifest oo_stringForKey:kOOManifestFilePath]];
     960             :                                 [sOXPManifests removeObjectForKey:identifier];
     961             :                                 sAllMet = NO;
     962             :                         }
     963             :                 }
     964             :         }
     965             : 
     966             :         return sAllMet;
     967             : }
     968             : 
     969             : 
     970             : + (BOOL) matchVersions:(NSDictionary *)rangeDict withVersion:(NSString *)version
     971             : {
     972             :         NSString        *minimum = [rangeDict oo_stringForKey:kOOManifestRelationVersion defaultValue:nil];
     973             :         NSString        *maximum = [rangeDict oo_stringForKey:kOOManifestRelationMaxVersion defaultValue:nil];
     974             :         NSArray         *isVersionComponents = ComponentsFromVersionString(version);
     975             :         NSArray         *reqVersionComponents = nil;
     976             :         if (minimum != nil)
     977             :         {
     978             :                 reqVersionComponents = ComponentsFromVersionString(minimum);
     979             :                 if (NSOrderedAscending == CompareVersions(isVersionComponents, reqVersionComponents))
     980             :                 {
     981             :                         // earlier than minimum version
     982             :                         return NO;
     983             :                 }
     984             :         }
     985             :         if (maximum != nil)
     986             :         {
     987             :                 reqVersionComponents = ComponentsFromVersionString(maximum);
     988             :                 if (NSOrderedDescending == CompareVersions(isVersionComponents, reqVersionComponents))
     989             :                 {
     990             :                         // later than maximum version
     991             :                         return NO;
     992             :                 }
     993             :         }
     994             :         // either version was okay, or no version info so an unconditional match
     995             :         return YES;
     996             : }
     997             : 
     998             : 
     999           0 : + (void) filterSearchPathsToExcludeScenarioOnlyPaths:(NSMutableArray *)searchPaths
    1000             : {
    1001             :         NSDictionary    *manifest = nil;
    1002             :         NSString                *identifier = nil;
    1003             :         NSArray                 *identifiers = [sOXPManifests allKeys];
    1004             : 
    1005             :         // take a copy because we'll mutate the original
    1006             :         // foreach identified add-on
    1007             :         foreach (identifier, identifiers)
    1008             :         {
    1009             :                 manifest = [sOXPManifests objectForKey:identifier];
    1010             :                 if (manifest != nil)
    1011             :                 {
    1012             :                         if ([[manifest oo_arrayForKey:kOOManifestTags] containsObject:kOOManifestTagScenarioOnly])
    1013             :                         {
    1014             :                                 [searchPaths removeObject:[manifest oo_stringForKey:kOOManifestFilePath]];
    1015             :                                 [sOXPManifests removeObjectForKey:identifier];
    1016             :                         }
    1017             :                 }
    1018             :         }
    1019             : }
    1020             : 
    1021             : 
    1022             : 
    1023           0 : + (void) filterSearchPathsByScenario:(NSMutableArray *)searchPaths
    1024             : {
    1025             :         NSDictionary    *manifest = nil;
    1026             :         NSString                *identifier = nil;
    1027             :         NSArray                 *identifiers = [sOXPManifests allKeys];
    1028             : 
    1029             :         // take a copy because we'll mutate the original
    1030             :         // foreach identified add-on
    1031             :         foreach (identifier, identifiers)
    1032             :         {
    1033             :                 manifest = [sOXPManifests objectForKey:identifier];
    1034             :                 if (manifest != nil)
    1035             :                 {
    1036             :                         if (![ResourceManager manifestAllowedByScenario:manifest])
    1037             :                         {
    1038             :                                 // then we don't need this one
    1039             :                                 [searchPaths removeObject:[manifest oo_stringForKey:kOOManifestFilePath]];
    1040             :                                 [sOXPManifests removeObjectForKey:identifier];
    1041             :                         }
    1042             :                 }
    1043             :         }
    1044             : }
    1045             : 
    1046             : 
    1047           0 : + (BOOL) manifestAllowedByScenario:(NSDictionary *)manifest
    1048             : {
    1049             :         /* Checks for a couple of "never happens" cases */
    1050             : #ifndef NDEBUG
    1051             :         // test string
    1052             :         if ([sUseAddOns isEqualToString:SCENARIO_OXP_DEFINITION_ALL])
    1053             :         {
    1054             :                 OOLog(@"scenario.check", @"%@", @"Checked scenario allowances in all state - this is an internal error; please report this");
    1055             :                 return YES;
    1056             :         }
    1057             :         if ([sUseAddOns isEqualToString:SCENARIO_OXP_DEFINITION_NONE])
    1058             :         {
    1059             :                 OOLog(@"scenario.check", @"%@", @"Checked scenario allowances in none state - this is an internal error; please report this");
    1060             :                 return NO;
    1061             :         }
    1062             : #endif
    1063             :         if ([[manifest oo_stringForKey:kOOManifestIdentifier] isEqualToString:@"org.oolite.oolite"])
    1064             :         {
    1065             :                 // the core data is always allowed!
    1066             :                 return YES;
    1067             :         }
    1068             : 
    1069             :         NSString *uaoBit = nil;
    1070             :         BOOL result = NO;
    1071             :         foreach (uaoBit, sUseAddOnsParts)
    1072             :         {
    1073             :                 if ([uaoBit hasPrefix:SCENARIO_OXP_DEFINITION_BYID])
    1074             :                 {
    1075             :                         result |= [ResourceManager manifestAllowedByScenario:manifest withIdentifier:[uaoBit substringFromIndex:[SCENARIO_OXP_DEFINITION_BYID length]]];
    1076             :                 }
    1077             :                 else if ([uaoBit hasPrefix:SCENARIO_OXP_DEFINITION_BYTAG])
    1078             :                 {
    1079             :                         result |= [ResourceManager manifestAllowedByScenario:manifest withTag:[uaoBit substringFromIndex:[SCENARIO_OXP_DEFINITION_BYTAG length]]];
    1080             :                 }
    1081             :         }
    1082             :         return result;
    1083             : }
    1084             : 
    1085             : 
    1086           0 : + (BOOL) manifestAllowedByScenario:(NSDictionary *)manifest withIdentifier:(NSString *)identifier
    1087             : {
    1088             :         if ([[manifest oo_stringForKey:kOOManifestIdentifier] isEqualToString:identifier])
    1089             :         {
    1090             :                 // manifest has the identifier - easy
    1091             :                 return YES;
    1092             :         }
    1093             :         // manifest is also allowed if a manifest with that identifier
    1094             :         // requires it to be installed
    1095             :         if ([[manifest oo_setForKey:kOOManifestRequiredBy] containsObject:identifier])
    1096             :         {
    1097             :                 return YES;
    1098             :         }
    1099             :         // otherwise, no
    1100             :         return NO;
    1101             : }
    1102             : 
    1103             : 
    1104           0 : + (BOOL) manifestAllowedByScenario:(NSDictionary *)manifest withTag:(NSString *)tag
    1105             : {
    1106             :         if ([[manifest oo_arrayForKey:kOOManifestTags] containsObject:tag])
    1107             :         {
    1108             :                 // manifest has the tag - easy
    1109             :                 return YES;
    1110             :         }
    1111             :         // manifest is also allowed if a manifest with that tag
    1112             :         // requires it to be installed
    1113             :         NSSet *reqby = [manifest oo_setForKey:kOOManifestRequiredBy];
    1114             :         if (reqby != nil)
    1115             :         {
    1116             :                 NSString *identifier = nil;
    1117             :                 foreach (identifier, reqby)
    1118             :                 {
    1119             :                         NSDictionary *reqManifest = [sOXPManifests oo_dictionaryForKey:identifier defaultValue:nil];
    1120             :                         // need to check for nil as this one may already have been ruled out
    1121             :                         if (reqManifest != nil && [[reqManifest oo_arrayForKey:kOOManifestTags] containsObject:tag])
    1122             :                         {
    1123             :                                 return YES;
    1124             :                         }
    1125             :                 }
    1126             :         }
    1127             :         // otherwise, no
    1128             :         return NO;
    1129             : }
    1130             : 
    1131             : 
    1132             : 
    1133           0 : + (void) addErrorWithKey:(NSString *)descriptionKey param1:(id)param1 param2:(id)param2
    1134             : {
    1135             :         if (descriptionKey != nil)
    1136             :         {
    1137             :                 if (sErrors == nil)  sErrors = [[NSMutableArray alloc] init];
    1138             :                 [sErrors addObject:[NSArray arrayWithObjects:descriptionKey, param1 ?: (id)@"", param2 ?: (id)@"", nil]];
    1139             :         }
    1140             : }
    1141             : 
    1142             : 
    1143           0 : + (BOOL)checkCacheUpToDateForPaths:(NSArray *)searchPaths
    1144             : {
    1145             :         /*      Check if caches are up to date.
    1146             :                 The strategy is to use a two-entry cache. One entry is an array
    1147             :                 containing the search paths, the other an array of modification dates
    1148             :                 (in the same order). If either fails to match the correct settings,
    1149             :                 we delete both.
    1150             :         */
    1151             :         OOCacheManager          *cacheMgr = [OOCacheManager sharedCache];
    1152             :         NSFileManager           *fmgr = [NSFileManager defaultManager];
    1153             :         BOOL                            upToDate = YES;
    1154             :         id                                      oldPaths = nil;
    1155             :         NSMutableArray          *modDates = nil;
    1156             :         NSEnumerator            *pathEnum = nil;
    1157             :         NSString                        *path = nil;
    1158             :         id                                      modDate = nil;
    1159             :         
    1160             :         if (EXPECT_NOT([[NSUserDefaults standardUserDefaults] boolForKey:@"always-flush-cache"]))
    1161             :         {
    1162             :                 OOLog(kOOLogCacheExplicitFlush, @"%@", @"Cache explicitly flushed with always-flush-cache preference. Rebuilding from scratch.");
    1163             :                 upToDate = NO;
    1164             :         }
    1165             :         else if ([MyOpenGLView pollShiftKey])
    1166             :         {
    1167             :                 OOLog(kOOLogCacheExplicitFlush, @"%@", @"Cache explicitly flushed with shift key. Rebuilding from scratch.");
    1168             :                 upToDate = NO;
    1169             :         }
    1170             :         
    1171             :         oldPaths = [cacheMgr objectForKey:kOOCacheKeySearchPaths inCache:kOOCacheSearchPathModDates];
    1172             :         if (upToDate && ![oldPaths isEqual:searchPaths])
    1173             :         {
    1174             :                 // OXPs added/removed
    1175             :                 if (oldPaths != nil) OOLog(kOOLogCacheStalePaths, @"%@", @"Cache is stale (search paths have changed). Rebuilding from scratch.");
    1176             :                 upToDate = NO;
    1177             :         }
    1178             :         
    1179             :         // Build modification date list. (We need this regardless of whether the search paths matched.)
    1180             :         
    1181             :         modDates = [NSMutableArray arrayWithCapacity:[searchPaths count]];
    1182             :         for (pathEnum = [searchPaths objectEnumerator]; (path = [pathEnum nextObject]); )
    1183             :         {
    1184             :                 modDate = [[fmgr oo_fileAttributesAtPath:path traverseLink:YES] objectForKey:NSFileModificationDate];
    1185             :                 if (modDate != nil)
    1186             :                 {
    1187             :                         // Converts to double because I'm not sure the cache can deal with dates under GNUstep.
    1188             :                         modDate = [NSNumber numberWithDouble:[modDate timeIntervalSince1970]];
    1189             :                         [modDates addObject:modDate];
    1190             :                 }
    1191             :         }
    1192             :                 
    1193             :         if (upToDate && ![[cacheMgr objectForKey:kOOCacheKeyModificationDates inCache:kOOCacheSearchPathModDates] isEqual:modDates])
    1194             :         {
    1195             :                 OOLog(kOOLogCacheStaleDates, @"%@", @"Cache is stale (modification dates have changed). Rebuilding from scratch.");
    1196             :                 upToDate = NO;
    1197             :         }
    1198             :         
    1199             :         if (!upToDate)
    1200             :         {
    1201             :                 [cacheMgr clearAllCaches];
    1202             :                 [cacheMgr setObject:searchPaths forKey:kOOCacheKeySearchPaths inCache:kOOCacheSearchPathModDates];
    1203             :                 [cacheMgr setObject:modDates forKey:kOOCacheKeyModificationDates inCache:kOOCacheSearchPathModDates];
    1204             :         }
    1205             :         else OOLog(kOOLogCacheUpToDate, @"%@", @"Data cache is up to date.");
    1206             : 
    1207             :         return upToDate;
    1208             : }
    1209             : 
    1210             : 
    1211             : /* This method allows the exclusion of particular files from the plist
    1212             :  * building when they're in builtInPath. The point of this is to allow
    1213             :  * scenarios to avoid merging in core files without having to override
    1214             :  * every individual entry (which may not always be possible
    1215             :  * anyway). It only works on plists, but of course worldscripts can be
    1216             :  * excluded by not including the plists which reference them, and
    1217             :  * everything else can be excluded by not referencing it from a plist.
    1218             :  */
    1219             : + (BOOL) corePlist:(NSString *)fileName excludedAt:(NSString *)path
    1220             : {
    1221             :         if (![path isEqualToString:[self builtInPath]])
    1222             :         {
    1223             :                 // non-core paths always okay
    1224             :                 return NO;
    1225             :         }
    1226             :         NSString *uaoBit = nil;
    1227             :         foreach (uaoBit, sUseAddOnsParts)
    1228             :         {
    1229             :                 if ([uaoBit hasPrefix:SCENARIO_OXP_DEFINITION_NOPLIST])
    1230             :                 {
    1231             :                         NSString *plist = [uaoBit substringFromIndex:[SCENARIO_OXP_DEFINITION_NOPLIST length]];
    1232             :                         if ([plist isEqualToString:fileName])
    1233             :                         {
    1234             :                                 // this core plist file should not be loaded at all
    1235             :                                 return YES;
    1236             :                         }
    1237             :                 }
    1238             :         }
    1239             :         // then not excluded
    1240             :         return NO;
    1241             : }
    1242             : 
    1243             : 
    1244             : + (NSDictionary *)dictionaryFromFilesNamed:(NSString *)fileName
    1245             :                                                                   inFolder:(NSString *)folderName
    1246             :                                                                   andMerge:(BOOL) mergeFiles
    1247             : {
    1248             :         return [ResourceManager dictionaryFromFilesNamed:fileName inFolder:folderName mergeMode:mergeFiles ? MERGE_BASIC : MERGE_NONE cache:YES];
    1249             : }
    1250             : 
    1251             : 
    1252             : + (NSDictionary *)dictionaryFromFilesNamed:(NSString *)fileName
    1253             :                                                                   inFolder:(NSString *)folderName
    1254             :                                                                  mergeMode:(OOResourceMergeMode)mergeMode
    1255             :                                                                          cache:(BOOL)cache
    1256             : {
    1257             :         id                              result = nil;
    1258             :         NSMutableArray  *results = nil;
    1259             :         NSString                *cacheKey = nil;
    1260             :         NSString                *mergeType = nil;
    1261             :         OOCacheManager  *cacheMgr = [OOCacheManager sharedCache];
    1262             :         NSEnumerator    *enumerator = nil;
    1263             :         NSString                *path = nil;
    1264             :         NSString                *dictPath = nil;
    1265             :         NSDictionary    *dict = nil;
    1266             :         
    1267             :         if (fileName == nil)  return nil;
    1268             :         
    1269             :         switch (mergeMode)
    1270             :         {
    1271             :                 case MERGE_NONE:
    1272             :                         mergeType = @"none";
    1273             :                         break;
    1274             :                 
    1275             :                 case MERGE_BASIC:
    1276             :                         mergeType = @"basic";
    1277             :                         break;
    1278             :                 
    1279             :                 case MERGE_SMART:
    1280             :                         mergeType = @"smart";
    1281             :                         break;
    1282             :         }
    1283             :         if (mergeType == nil)
    1284             :         {
    1285             :                 OOLog(kOOLogParameterError, @"Unknown dictionary merge mode %u for %@. (This is an internal programming error, please report it.)", mergeMode, fileName);
    1286             :                 return nil;
    1287             :         }
    1288             :         
    1289             :         if (cache)
    1290             :         {
    1291             :         
    1292             :                 if (folderName != nil)
    1293             :                 {
    1294             :                         cacheKey = [NSString stringWithFormat:@"%@/%@ merge:%@", folderName, fileName, mergeType];
    1295             :                 }
    1296             :                 else
    1297             :                 {
    1298             :                         cacheKey = [NSString stringWithFormat:@"%@ merge:%@", fileName, mergeType];
    1299             :                 }
    1300             :                 result = [cacheMgr objectForKey:cacheKey inCache:@"dictionaries"];
    1301             :                 if (result != nil)  return result;
    1302             :         }
    1303             :         
    1304             :         if (mergeMode == MERGE_NONE)
    1305             :         {
    1306             :                 // Find "last" matching dictionary
    1307             :                 for (enumerator = [ResourceManager reversePathEnumerator]; (path = [enumerator nextObject]); )
    1308             :                 {
    1309             :                         if (folderName != nil)
    1310             :                         {
    1311             :                                 dictPath = [[path stringByAppendingPathComponent:folderName] stringByAppendingPathComponent:fileName];
    1312             :                                 dict = OODictionaryFromFile(dictPath);
    1313             :                                 if (dict != nil)  break;
    1314             :                         }
    1315             :                         dictPath = [path stringByAppendingPathComponent:fileName];
    1316             :                         dict = OODictionaryFromFile(dictPath);
    1317             :                         if (dict != nil)  break;
    1318             :                 }
    1319             :                 result = dict;
    1320             :         }
    1321             :         else
    1322             :         {
    1323             :                 // Find all matching dictionaries
    1324             :                 results = [NSMutableArray array];
    1325             :                 for (enumerator = [ResourceManager pathEnumerator]; (path = [enumerator nextObject]); )
    1326             :                 {
    1327             :                         if ([ResourceManager corePlist:fileName excludedAt:path])
    1328             :                         {
    1329             :                                 continue;
    1330             :                         }
    1331             :                         dictPath = [path stringByAppendingPathComponent:fileName];
    1332             :                         dict = OODictionaryFromFile(dictPath);
    1333             :                         if (dict != nil)  [results addObject:dict];
    1334             :                         if (folderName != nil)
    1335             :                         {
    1336             :                                 dictPath = [[path stringByAppendingPathComponent:folderName] stringByAppendingPathComponent:fileName];
    1337             :                                 dict = OODictionaryFromFile(dictPath);
    1338             :                                 if (dict != nil)  [results addObject:dict];
    1339             :                         }
    1340             :                 }
    1341             :                 
    1342             :                 if ([results count] == 0)  return nil;
    1343             :                 
    1344             :                 // Merge result
    1345             :                 result = [NSMutableDictionary dictionary];
    1346             :                 
    1347             :                 for (enumerator = [results objectEnumerator]; (dict = [enumerator nextObject]); )
    1348             :                 {
    1349             :                         if (mergeMode == MERGE_SMART)  [result mergeEntriesFromDictionary:dict];
    1350             :                         else  [result addEntriesFromDictionary:dict];
    1351             :                 }
    1352             :                 result = [[result copy] autorelease];   // Make immutable
    1353             :         }
    1354             :         
    1355             :         if (cache && result != nil)  [cacheMgr setObject:result forKey:cacheKey inCache:@"dictionaries"];
    1356             :         
    1357             :         return result;
    1358             : }
    1359             : 
    1360             : 
    1361             : + (NSArray *) arrayFromFilesNamed:(NSString *)fileName inFolder:(NSString *)folderName andMerge:(BOOL) mergeFiles
    1362             : {
    1363             :         return [self arrayFromFilesNamed:fileName inFolder:folderName andMerge:mergeFiles cache:YES];
    1364             : }
    1365             : 
    1366             : 
    1367             : + (NSArray *) arrayFromFilesNamed:(NSString *)fileName inFolder:(NSString *)folderName andMerge:(BOOL) mergeFiles cache:(BOOL)useCache
    1368             : {
    1369             :         id                              result = nil;
    1370             :         NSMutableArray  *results = nil;
    1371             :         NSString                *cacheKey = nil;
    1372             :         OOCacheManager  *cache = [OOCacheManager sharedCache];
    1373             :         NSEnumerator    *enumerator = nil;
    1374             :         NSString                *path = nil;
    1375             :         NSString                *arrayPath = nil;
    1376             :         NSMutableArray  *array = nil;
    1377             :         NSArray                 *arrayNonEditable = nil;
    1378             :         
    1379             :         if (fileName == nil)  return nil;
    1380             :         
    1381             :         if (useCache)
    1382             :         {
    1383             :                 cacheKey = [NSString stringWithFormat:@"%@%@ merge:%@", (folderName != nil) ? [folderName stringByAppendingString:@"/"] : (NSString *)@"", fileName, mergeFiles ? @"yes" : @"no"];
    1384             :                 result = [cache objectForKey:cacheKey inCache:@"arrays"];
    1385             :                 if (result != nil)  return result;
    1386             :         }
    1387             :         
    1388             :         if (!mergeFiles)
    1389             :         {
    1390             :                 // Find "last" matching array
    1391             :                 for (enumerator = [ResourceManager reversePathEnumerator]; (path = [enumerator nextObject]); )
    1392             :                 {
    1393             :                         if (folderName != nil)
    1394             :                         {
    1395             :                                 arrayPath = [[path stringByAppendingPathComponent:folderName] stringByAppendingPathComponent:fileName];
    1396             :                                 arrayNonEditable = OOArrayFromFile(arrayPath);
    1397             :                                 if (arrayNonEditable != nil)  break;
    1398             :                         }
    1399             :                         arrayPath = [path stringByAppendingPathComponent:fileName];
    1400             :                         arrayNonEditable = OOArrayFromFile(arrayPath);
    1401             :                         if (arrayNonEditable != nil)  break;
    1402             :                 }
    1403             :                 result = arrayNonEditable;
    1404             :         }
    1405             :         else
    1406             :         {
    1407             :                 // Find all matching arrays
    1408             :                 results = [NSMutableArray array];
    1409             :                 for (enumerator = [ResourceManager pathEnumerator]; (path = [enumerator nextObject]); )
    1410             :                 {
    1411             :                         if ([ResourceManager corePlist:fileName excludedAt:path])
    1412             :                         {
    1413             :                                 continue;
    1414             :                         }
    1415             : 
    1416             :                         arrayPath = [path stringByAppendingPathComponent:fileName];
    1417             :                         array = [[OOArrayFromFile(arrayPath) mutableCopy] autorelease];
    1418             :                         if (array != nil) [results addObject:array];
    1419             :         
    1420             :                         // Special handling for arrays merging. Currently, only equipment.plist, nebulatextures.plist and 
    1421             :                         // startextures.plist gets their objects merged.
    1422             :                         // A lookup index is required. For the equipment.plist items, this is the index corresponding to the
    1423             :                         // EQ_* string, which describes the role of an equipment item and is unique.
    1424             :                         // For nebula and star textures, this is the texture filename, although it can be overridden with a 
    1425             :                         // "key" property
    1426             :                         if ([array count] != 0 && ([[fileName lowercaseString] isEqualToString:@"nebulatextures.plist"] || 
    1427             :                                 [[fileName lowercaseString] isEqualToString:@"startextures.plist"]))
    1428             :                                 [self handleStarNebulaListMerging:results];
    1429             : 
    1430             :                         if ([array count] != 0 && [[array objectAtIndex:0] isKindOfClass:[NSArray class]])
    1431             :                         {
    1432             :                                 if ([[fileName lowercaseString] isEqualToString:@"equipment.plist"])
    1433             :                                         [self handleEquipmentListMerging:results forLookupIndex:3]; // Index 3 is the role string (EQ_*).
    1434             :                         }
    1435             :                         if (folderName != nil)
    1436             :                         {
    1437             :                                 arrayPath = [[path stringByAppendingPathComponent:folderName] stringByAppendingPathComponent:fileName];
    1438             :                                 array = [[OOArrayFromFile(arrayPath) mutableCopy] autorelease];
    1439             :                                 if (array != nil)  [results addObject:array];
    1440             : 
    1441             :                                 if ([array count] != 0 && ([[fileName lowercaseString] isEqualToString:@"nebulatextures.plist"] || 
    1442             :                                         [[fileName lowercaseString] isEqualToString:@"startextures.plist"]))
    1443             :                                         [self handleStarNebulaListMerging:results];
    1444             : 
    1445             :                                 if ([array count] != 0 && [[array objectAtIndex:0] isKindOfClass:[NSArray class]])
    1446             :                                 {
    1447             :                                         if ([[fileName lowercaseString] isEqualToString:@"equipment.plist"])
    1448             :                                                 [self handleEquipmentListMerging:results forLookupIndex:3]; // Index 3 is the role string (EQ_*).
    1449             :                                 }
    1450             :                         }
    1451             :                 }
    1452             :                 
    1453             :                 if ([results count] == 0)  return nil;
    1454             :                 
    1455             :                 // Merge result
    1456             :                 result = [NSMutableArray array];
    1457             :                 
    1458             :                 for (enumerator = [results objectEnumerator]; (array = [enumerator nextObject]); )
    1459             :                 {
    1460             :                         [result addObjectsFromArray:array];
    1461             :                 }
    1462             :                 // if we're doing equipment.plist, do equipment overrides now, while the array is still mutable
    1463             :                 if ([[fileName lowercaseString] isEqualToString:@"equipment.plist"]) 
    1464             :                 {
    1465             :                         [self handleEquipmentOverrides:result];
    1466             :                 }
    1467             :                 result = [[result copy] autorelease];   // Make immutable
    1468             :         }
    1469             :         
    1470             :         if (useCache && result != nil)  [cache setObject:result forKey:cacheKey inCache:@"arrays"];
    1471             :         
    1472             :         return [NSArray arrayWithArray:result];
    1473             : }
    1474             : 
    1475             : 
    1476             : // A method for handling merging of arrays. Currently used with the equipment.plist entries.
    1477             : // The arrayToProcess array is scanned for repetitions of the item at lookup index location and, if found,
    1478             : // the latest entry replaces the earliest.
    1479             : + (void) handleEquipmentListMerging: (NSMutableArray *)arrayToProcess forLookupIndex:(unsigned)lookupIndex
    1480             : {
    1481             :         NSUInteger i,j,k;
    1482             :         NSMutableArray *refArray = [arrayToProcess objectAtIndex:[arrayToProcess count] - 1];
    1483             :         
    1484             :         // Any change to refArray will directly modify arrayToProcess.
    1485             :         
    1486             :         for (i = 0; i < [refArray count]; i++)
    1487             :         {
    1488             :                 for (j = 0; j < [arrayToProcess count] - 1; j++)
    1489             :                 {
    1490             :                         NSUInteger count = [[arrayToProcess oo_arrayAtIndex:j] count];
    1491             :                         if (count == 0)  continue;
    1492             :                         
    1493             :                         for (k=0; k < count; k++)
    1494             :                         {
    1495             :                                 id processValue = [[[arrayToProcess oo_arrayAtIndex:j] oo_arrayAtIndex:k] oo_objectAtIndex:lookupIndex defaultValue:nil];
    1496             :                                 id refValue = [[refArray oo_arrayAtIndex:i] oo_objectAtIndex:lookupIndex defaultValue:nil];
    1497             :                                 
    1498             :                                 if ([processValue isEqual:refValue])
    1499             :                                 {
    1500             :                                         [[arrayToProcess objectAtIndex:j] replaceObjectAtIndex:k withObject:[refArray objectAtIndex:i]];
    1501             :                                         [refArray removeObjectAtIndex:i];
    1502             :                                 }
    1503             :                         }
    1504             :                 }
    1505             :         }
    1506             :         // arrayToProcess has been processed at this point. Any necessary merging has been done.
    1507             : }
    1508             : 
    1509             : 
    1510             : // handles processing of equipment-overrides.plist files, updating the source array with values found.
    1511             : // format of file is slightly different to the standard equipment.plist file, in that it is a 
    1512             : // dictionary of dictionary objects (rather than an array of arrays). this allows properties like
    1513             : // techlevel, price, name/short_description and description/long_description to be updated via the overrides file.
    1514             : + (void) handleEquipmentOverrides: (NSMutableArray *)arrayToProcess
    1515             : {
    1516             :         NSEnumerator                    *equipKeyEnum = nil;
    1517             :         NSString                                *equipKey = nil;
    1518             :         NSDictionary                    *overrides = nil;
    1519             :         NSDictionary                    *overridesEntry = nil;
    1520             :         int                                     i;
    1521             : 
    1522             :         overrides = [ResourceManager dictionaryFromFilesNamed:@"equipment-overrides.plist"
    1523             :                                                                                                  inFolder:@"Config"
    1524             :                                                                                                 mergeMode:MERGE_SMART
    1525             :                                                                                                         cache:NO];
    1526             : 
    1527             :         // cycle through all the equipment keys found in override files
    1528             :         for (equipKeyEnum = [overrides keyEnumerator]; (equipKey = [equipKeyEnum nextObject]); )
    1529             :         {
    1530             :                 overridesEntry = [overrides objectForKey:equipKey];
    1531             :                 // loop through our data array to find a match
    1532             :                 for (i = 0; i < [arrayToProcess count]; i++)
    1533             :                 {
    1534             :                         NSMutableArray  *equipArray = [[[arrayToProcess objectAtIndex:i] mutableCopy] autorelease];
    1535             :                         id refValue = [equipArray oo_objectAtIndex:EQUIPMENT_KEY_INDEX defaultValue:nil];
    1536             :                         // does the overridden equipment item exist in the equipment array? if so, get working
    1537             :                         if ([equipKey isEqual:refValue])
    1538             :                         {
    1539             :                                 NSEnumerator                    *infoKeyEnum = nil;
    1540             :                                 NSString                                *infoKey;
    1541             :                                 // cycle through all the properties found for this equipment key in the overrides file
    1542             :                                 for (infoKeyEnum = [overridesEntry keyEnumerator]; (infoKey = [infoKeyEnum nextObject]); )
    1543             :                                 {
    1544             :                                         // special cases for the array items that don't have a direct keyname
    1545             :                                         if ([infoKey isEqualToString:@"techlevel"]) 
    1546             :                                                 [equipArray replaceObjectAtIndex:EQUIPMENT_TECH_LEVEL_INDEX withObject:[overridesEntry objectForKey:infoKey]];
    1547             :                                         else if ([infoKey isEqualToString:@"price"]) 
    1548             :                                                 [equipArray replaceObjectAtIndex:EQUIPMENT_PRICE_INDEX withObject:[overridesEntry objectForKey:infoKey]];
    1549             :                                         else if ([infoKey isEqualToString:@"short_description"]) 
    1550             :                                                 [equipArray replaceObjectAtIndex:EQUIPMENT_SHORT_DESC_INDEX withObject:[overridesEntry objectForKey:infoKey]];
    1551             :                                         else if ([infoKey isEqualToString:@"name"]) 
    1552             :                                                 [equipArray replaceObjectAtIndex:EQUIPMENT_SHORT_DESC_INDEX withObject:[overridesEntry objectForKey:infoKey]];
    1553             :                                         else if ([infoKey isEqualToString:@"long_description"]) 
    1554             :                                                 [equipArray replaceObjectAtIndex:EQUIPMENT_LONG_DESC_INDEX withObject:[overridesEntry objectForKey:infoKey]];
    1555             :                                         else if ([infoKey isEqualToString:@"description"]) 
    1556             :                                                 [equipArray replaceObjectAtIndex:EQUIPMENT_LONG_DESC_INDEX withObject:[overridesEntry objectForKey:infoKey]];
    1557             :                                         else 
    1558             :                                         {
    1559             :                                                 NSMutableDictionary             *extra = nil;
    1560             :                                                 // for everything else
    1561             :                                                 // do we actually have an extras dictionary?
    1562             :                                                 if (![equipArray oo_dictionaryAtIndex:EQUIPMENT_EXTRA_INFO_INDEX]) 
    1563             :                                                 {
    1564             :                                                         // if not, create a blank one we can add to
    1565             :                                                         extra = [NSMutableDictionary dictionary];
    1566             :                                                 }
    1567             :                                                 else
    1568             :                                                 {
    1569             :                                                         extra = [[equipArray oo_dictionaryAtIndex:EQUIPMENT_EXTRA_INFO_INDEX] mutableCopy];
    1570             :                                                 }
    1571             :                                                 // special case for weapon_info && script_info, which are child dictionaries
    1572             :                                                 if ([infoKey isEqualToString:@"weapon_info"] || [infoKey isEqualToString:@"script_info"])
    1573             :                                                 {
    1574             :                                                         NSEnumerator                    *subEnum = nil;
    1575             :                                                         NSString                                *subKey;
    1576             :                                                         NSMutableDictionary             *subInfo = nil;
    1577             :                                                         NSDictionary                    *subOverrides = nil;
    1578             :                                                         // do we actually have a weapon_info/script_info dictionary?
    1579             :                                                         if (![extra oo_dictionaryForKey:infoKey]) 
    1580             :                                                         {
    1581             :                                                                 // if not, create a blank dictionary we can add to
    1582             :                                                                 subInfo = [NSMutableDictionary dictionary];
    1583             :                                                         } 
    1584             :                                                         else 
    1585             :                                                         {
    1586             :                                                                 subInfo = [[extra oo_dictionaryForKey:infoKey] mutableCopy];
    1587             :                                                         }
    1588             :                                                         subOverrides = [overridesEntry oo_dictionaryForKey:infoKey];
    1589             :                                                         // cycle through all the sub keys found in the overrides file for this equipment key item
    1590             :                                                         for (subEnum = [subOverrides keyEnumerator]; (subKey = [subEnum nextObject]); )
    1591             :                                                         {
    1592             :                                                                 [subInfo setObject:[subOverrides objectForKey:subKey] forKey:subKey];
    1593             :                                                         }
    1594             :                                                         [extra setObject:[[subInfo copy] autorelease] forKey:infoKey];
    1595             :                                                 }
    1596             :                                                 else
    1597             :                                                 {
    1598             :                                                         // for all other keys in the extras dictionary
    1599             :                                                         [extra setObject:[overridesEntry objectForKey:infoKey] forKey:infoKey];
    1600             :                                                 }
    1601             :                                                 [equipArray replaceObjectAtIndex:EQUIPMENT_EXTRA_INFO_INDEX withObject:[[extra copy] autorelease]];
    1602             :                                         }
    1603             :                                 }
    1604             :                                 [arrayToProcess replaceObjectAtIndex:i withObject:equipArray];
    1605             :                         }
    1606             :                 }
    1607             :         }
    1608             : }
    1609             : 
    1610             : 
    1611             : // A method for handling merging of arrays. Currently used with the nebulatextures.plist and startextures.plist entries.
    1612             : // uses the "texture" filename as the key value, or "key" if found
    1613             : + (void) handleStarNebulaListMerging: (NSMutableArray *)arrayToProcess
    1614             : {
    1615             :         NSUInteger i,j,k;
    1616             :         NSMutableArray *refArray = [arrayToProcess objectAtIndex:[arrayToProcess count] - 1];
    1617             : 
    1618             :         // Any change to refArray will directly modify arrayToProcess.  
    1619             :         for (i = 0; i < [refArray count]; i++)
    1620             :         {
    1621             :                 for (j = 0; j < [arrayToProcess count] - 1; j++)
    1622             :                 {
    1623             :                         NSUInteger count = [[arrayToProcess oo_arrayAtIndex:j] count];
    1624             :                         if (count == 0)  continue;
    1625             : 
    1626             :                         for (k = 0; k < count; k++) 
    1627             :                         {
    1628             :                                 id processValue = [[arrayToProcess oo_arrayAtIndex:j] oo_objectAtIndex:k defaultValue:nil];
    1629             :                                 id refValue = [refArray oo_objectAtIndex:i defaultValue:nil];
    1630             :                                 NSString *key1 = nil;
    1631             :                                 NSString *key2 = nil;
    1632             : 
    1633             :                                 if ([processValue isKindOfClass:[NSString class]]) 
    1634             :                                 {
    1635             :                                         key1 = processValue;
    1636             :                                 }
    1637             :                                 else if ([processValue isKindOfClass:[NSDictionary class]])
    1638             :                                 {
    1639             :                                         if (![processValue objectForKey:@"key"]) 
    1640             :                                         {
    1641             :                                                 key1 = [processValue objectForKey:@"texture"];
    1642             :                                         }
    1643             :                                         else
    1644             :                                         {
    1645             :                                                 key1 = [processValue objectForKey:@"key"];
    1646             :                                         }
    1647             :                                 }
    1648             :                                 if (!key1) continue;
    1649             : 
    1650             :                                 if ([refValue isKindOfClass:[NSString class]]) 
    1651             :                                 {
    1652             :                                         key2 = refValue;
    1653             :                                 }
    1654             :                                 else if ([refValue isKindOfClass:[NSDictionary class]])
    1655             :                                 {
    1656             :                                         if (![refValue objectForKey:@"key"])
    1657             :                                         {
    1658             :                                                 key2 = [refValue objectForKey:@"texture"];
    1659             :                                         }
    1660             :                                         else
    1661             :                                         {
    1662             :                                                 key2 = [refValue objectForKey:@"key"];
    1663             :                                         }
    1664             :                                 }
    1665             :                                 if (!key2) continue;
    1666             : 
    1667             :                                 if ([key1 isEqual:key2])
    1668             :                                 {
    1669             :                                         [[arrayToProcess objectAtIndex:j] replaceObjectAtIndex:k withObject:[refArray objectAtIndex:i]];
    1670             :                                         [refArray removeObjectAtIndex:i];
    1671             :                                 }
    1672             : 
    1673             :                         }
    1674             :                 }
    1675             :         }
    1676             :         // arrayToProcess has been processed at this point. Any necessary merging has been done.
    1677             : }
    1678             : 
    1679             : 
    1680             : + (NSDictionary *) whitelistDictionary
    1681             : {
    1682             :         static id whitelistDictionary = nil;
    1683             :         
    1684             :         if (whitelistDictionary == nil)
    1685             :         {
    1686             :                 NSString *path = [[[ResourceManager builtInPath] stringByAppendingPathComponent:@"Config"] stringByAppendingPathComponent:@"whitelist.plist"];
    1687             :                 whitelistDictionary = [NSDictionary dictionaryWithContentsOfFile:path];
    1688             :                 if (whitelistDictionary == nil)  whitelistDictionary = [NSNull null];
    1689             :                 
    1690             :                 [whitelistDictionary retain];
    1691             :         }
    1692             :         
    1693             :         if (whitelistDictionary == [NSNull null])  return nil;
    1694             :         return whitelistDictionary;
    1695             : }
    1696             : 
    1697             : 
    1698           0 : static NSString *LogClassKeyRoot(NSString *key)
    1699             : {
    1700             :         NSRange dot = [key rangeOfString:@"."];
    1701             :         if (dot.location != NSNotFound)
    1702             :         {
    1703             :                 return [key substringToIndex:dot.location];
    1704             :         }
    1705             :         else
    1706             :         {
    1707             :                 return key;
    1708             :         }
    1709             : }
    1710             : 
    1711             : 
    1712             : + (NSDictionary *) logControlDictionary
    1713             : {
    1714             :         // Load built-in copy of logcontrol.plist.
    1715             :         NSString *path = [[[ResourceManager builtInPath] stringByAppendingPathComponent:@"Config"]
    1716             :                                           stringByAppendingPathComponent:@"logcontrol.plist"];
    1717             :         NSMutableDictionary *logControl = [NSMutableDictionary dictionaryWithDictionary:OODictionaryFromFile(path)];
    1718             :         if (logControl == nil)  logControl = [NSMutableDictionary dictionary];
    1719             :         
    1720             :         // Build list of root log message classes that appear in the built-in list.
    1721             :         NSMutableSet *coreRoots = [NSMutableSet set];
    1722             :         NSString *key = nil;
    1723             :         foreachkey(key, logControl)
    1724             :         {
    1725             :                 [coreRoots addObject:LogClassKeyRoot(key)];
    1726             :         }
    1727             :         
    1728             :         NSArray *rootPaths = [self rootPaths];
    1729             :         NSString *configPath = nil;
    1730             :         NSDictionary *dict = nil;
    1731             :         
    1732             :         // Look for logcontrol.plists inside OXPs (but not in root paths). These are not allowed to define keys in hierarchies used by the build-in one.
    1733             :         NSEnumerator *pathEnum = [self pathEnumerator];
    1734             :         while ((path = [pathEnum nextObject]))
    1735             :         {
    1736             :                 if ([rootPaths containsObject:path])  continue;
    1737             :                 
    1738             :                 configPath = [[path stringByAppendingPathComponent:@"Config"]
    1739             :                                           stringByAppendingPathComponent:@"logcontrol.plist"];
    1740             :                 dict = OODictionaryFromFile(configPath);
    1741             :                 if (dict == nil)
    1742             :                 {
    1743             :                         configPath = [path stringByAppendingPathComponent:@"logcontrol.plist"];
    1744             :                         dict = OODictionaryFromFile(configPath);
    1745             :                 }
    1746             :                 foreachkey (key, dict)
    1747             :                 {
    1748             :                         if (![coreRoots containsObject:LogClassKeyRoot(key)])
    1749             :                         {
    1750             :                                 [logControl setObject:[dict objectForKey:key] forKey:key];
    1751             :                         }
    1752             :                 }
    1753             :         }
    1754             :         
    1755             :         // Now, look for logcontrol.plists in root paths, i.e. not within OXPs. These are allowed to override the built-in copy.
    1756             :         pathEnum = [rootPaths objectEnumerator];
    1757             :         while ((path = [pathEnum nextObject]))
    1758             :         {
    1759             :                 configPath = [[path stringByAppendingPathComponent:@"Config"]
    1760             :                                           stringByAppendingPathComponent:@"logcontrol.plist"];
    1761             :                 dict = OODictionaryFromFile(configPath);
    1762             :                 if (dict == nil)
    1763             :                 {
    1764             :                         configPath = [path stringByAppendingPathComponent:@"logcontrol.plist"];
    1765             :                         dict = OODictionaryFromFile(configPath);
    1766             :                 }
    1767             :                 foreachkey (key, dict)
    1768             :                 {
    1769             :                         [logControl setObject:[dict objectForKey:key] forKey:key];
    1770             :                 }
    1771             :         }
    1772             :         
    1773             :         // Finally, look in preferences, which can override all of the above.
    1774             :         dict = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"logging-enable"];
    1775             :         if (dict != nil)  [logControl addEntriesFromDictionary:dict];
    1776             :         
    1777             :         return logControl;
    1778             : }
    1779             : 
    1780             : 
    1781             : + (NSDictionary *) roleCategoriesDictionary
    1782             : {
    1783             :         NSMutableDictionary *roleCategories = [NSMutableDictionary dictionaryWithCapacity:16];
    1784             : 
    1785             :         NSString *path = nil;
    1786             :         NSString *configPath = nil;
    1787             :         NSDictionary *categories = nil;
    1788             :         
    1789             :         NSEnumerator *pathEnum = [self pathEnumerator];
    1790             :         while ((path = [pathEnum nextObject]))
    1791             :         {
    1792             :                 if ([ResourceManager corePlist:@"role-categories.plist" excludedAt:path])
    1793             :                 {
    1794             :                         continue;
    1795             :                 }
    1796             : 
    1797             :                 configPath = [[path stringByAppendingPathComponent:@"Config"]
    1798             :                                           stringByAppendingPathComponent:@"role-categories.plist"];
    1799             :                 categories = OODictionaryFromFile(configPath);
    1800             :                 if (categories != nil)
    1801             :                 {
    1802             :                         [ResourceManager mergeRoleCategories:categories intoDictionary:roleCategories];
    1803             :                 }
    1804             :         }
    1805             :         
    1806             :         /* If the old pirate-victim-roles files exist, merge them in */
    1807             :         NSArray *pirateVictims = [ResourceManager arrayFromFilesNamed:@"pirate-victim-roles.plist" inFolder:@"Config" andMerge:YES];
    1808             :         if (OOEnforceStandards() && [pirateVictims count] > 0)
    1809             :         {
    1810             :                 OOStandardsDeprecated(@"pirate-victim-roles.plist is still being used.");
    1811             :         }
    1812             :         [ResourceManager mergeRoleCategories:[NSDictionary dictionaryWithObject:pirateVictims forKey:@"oolite-pirate-victim"] intoDictionary:roleCategories];
    1813             : 
    1814             :         return [[roleCategories copy] autorelease];
    1815             : }
    1816             : 
    1817             : 
    1818           0 : + (void) mergeRoleCategories:(NSDictionary *)catData intoDictionary:(NSMutableDictionary *)categories
    1819             : {
    1820             :         NSMutableSet *contents = nil;
    1821             :         NSArray *catDataEntry = nil;
    1822             :         NSString *key;
    1823             :         foreachkey(key, catData)
    1824             :         {
    1825             :                 contents = [categories objectForKey:key];
    1826             :                 if (contents == nil)
    1827             :                 {
    1828             :                         contents = [NSMutableSet setWithCapacity:16];
    1829             :                         [categories setObject:contents forKey:key];
    1830             :                 }
    1831             :                 catDataEntry = [catData oo_arrayForKey:key];
    1832             :                 OOLog(@"shipData.load.roleCategories", @"Adding %ld entries for category %@", (unsigned long)[catDataEntry count], key);
    1833             :                 [contents addObjectsFromArray:catDataEntry];
    1834             :         }
    1835             : }
    1836             : 
    1837             : 
    1838             : + (OOSystemDescriptionManager *) systemDescriptionManager
    1839             : {
    1840             :         OOLog(@"resourceManager.planetinfo.load", @"%@", @"Initialising manager");
    1841             :         OOSystemDescriptionManager *manager = [[OOSystemDescriptionManager alloc] init];
    1842             :         
    1843             :         NSString *path = nil;
    1844             :         NSString *configPath = nil;
    1845             :         NSDictionary *categories = nil;
    1846             :         NSString *systemKey = nil;
    1847             : 
    1848             :         NSEnumerator *pathEnum = [self pathEnumerator]; 
    1849             :         while ((path = [pathEnum nextObject]))
    1850             :         {
    1851             :                 if ([ResourceManager corePlist:@"planetinfo.plist" excludedAt:path])
    1852             :                 {
    1853             :                         continue;
    1854             :                 }
    1855             :                 configPath = [[path stringByAppendingPathComponent:@"Config"]
    1856             :                                           stringByAppendingPathComponent:@"planetinfo.plist"];
    1857             :                 categories = OODictionaryFromFile(configPath);
    1858             :                 if (categories != nil)
    1859             :                 {
    1860             :                         foreachkey(systemKey,categories)
    1861             :                         {
    1862             :                                 NSDictionary *values = [categories oo_dictionaryForKey:systemKey defaultValue:nil];
    1863             :                                 if (values != nil)
    1864             :                                 {
    1865             :                                         if ([systemKey isEqualToString:PLANETINFO_UNIVERSAL_KEY])
    1866             :                                         {
    1867             :                                                 [manager setUniversalProperties:values];
    1868             :                                         }
    1869             :                                         else if ([systemKey isEqualToString:PLANETINFO_INTERSTELLAR_KEY])
    1870             :                                         {
    1871             :                                                 [manager setInterstellarProperties:values];
    1872             :                                         }
    1873             :                                         else
    1874             :                                         {
    1875             :                                                 [manager setProperties:values forSystemKey:systemKey];
    1876             :                                         }
    1877             :                                 }
    1878             :                         }
    1879             :                 }
    1880             :         }
    1881             :         OOLog(@"resourceManager.planetinfo.load", @"%@", @"Caching routes");
    1882             :         [manager buildRouteCache];
    1883             :         OOLog(@"resourceManager.planetinfo.load", @"%@", @"Initialised manager");
    1884             :         return [manager autorelease];
    1885             : }
    1886             : 
    1887             : 
    1888             : 
    1889             : + (NSDictionary *) shaderBindingTypesDictionary
    1890             : {
    1891             :         static id shaderBindingTypesDictionary = nil;
    1892             :         
    1893             :         if (shaderBindingTypesDictionary == nil)
    1894             :         {
    1895             :                 NSAutoreleasePool *pool = [NSAutoreleasePool new];
    1896             :                 
    1897             :                 NSString *path = [[[ResourceManager builtInPath] stringByAppendingPathComponent:@"Config"] stringByAppendingPathComponent:@"shader-uniform-bindings.plist"];
    1898             :                 NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:path];
    1899             :                 NSArray *keys = [dict allKeys];
    1900             :                 
    1901             :                 // Resolve all $inherit keys.
    1902             :                 unsigned changeCount = 0;
    1903             :                 do {
    1904             :                         changeCount = 0;
    1905             :                         NSString *key = nil;
    1906             :                         foreach (key, keys)
    1907             :                         {
    1908             :                                 NSDictionary *value = [dict oo_dictionaryForKey:key];
    1909             :                                 NSString *inheritKey = [value oo_stringForKey:@"$inherit"];
    1910             :                                 if (inheritKey != nil)
    1911             :                                 {
    1912             :                                         changeCount++;
    1913             :                                         NSMutableDictionary *mutableValue = [[value mutableCopy] autorelease];
    1914             :                                         [mutableValue removeObjectForKey:@"$inherit"];
    1915             :                                         NSDictionary *inherited = [dict oo_dictionaryForKey:inheritKey];
    1916             :                                         if (inherited != nil)
    1917             :                                         {
    1918             :                                                 [mutableValue addEntriesFromDictionary:inherited];
    1919             :                                         }
    1920             :                                         
    1921             :                                         [dict setObject:[[mutableValue copy] autorelease] forKey:key];
    1922             :                                 }
    1923             :                         }
    1924             :                 } while (changeCount != 0);
    1925             :                 
    1926             :                 shaderBindingTypesDictionary = [dict copy];
    1927             :                 
    1928             :                 [pool release];
    1929             :         }
    1930             :         
    1931             :         return shaderBindingTypesDictionary;
    1932             : }
    1933             : 
    1934             : 
    1935             : + (NSString *) pathForFileNamed:(NSString *)fileName inFolder:(NSString *)folderName
    1936             : {
    1937             :         return [self pathForFileNamed:fileName inFolder:folderName cache:YES];
    1938             : }
    1939             : 
    1940             : 
    1941             : /* This is extremely expensive to call with useCache:NO */
    1942             : + (NSString *) pathForFileNamed:(NSString *)fileName inFolder:(NSString *)folderName cache:(BOOL)useCache
    1943             : {
    1944             :         NSString                *result = nil;
    1945             :         NSString                *cacheKey = nil;
    1946             :         OOCacheManager  *cache = [OOCacheManager sharedCache];
    1947             :         NSEnumerator    *pathEnum = nil;
    1948             :         NSString                *path = nil;
    1949             :         NSString                *filePath = nil;
    1950             :         NSFileManager   *fmgr = nil;
    1951             :         
    1952             :         if (fileName == nil)  return nil;
    1953             :         
    1954             :         if (cache)
    1955             :         {
    1956             :                 if (folderName != nil)  cacheKey = [NSString stringWithFormat:@"%@/%@", folderName, fileName];
    1957             :                 else  cacheKey = fileName;
    1958             :                 result = [cache objectForKey:cacheKey inCache:@"resolved paths"];
    1959             :                 if (result != nil)  return result;
    1960             :         }
    1961             :         
    1962             :         // Search for file
    1963             :         fmgr = [NSFileManager defaultManager];
    1964             :         // reverse object enumerator allows OXPs to override core
    1965             :         for (pathEnum = [[ResourceManager paths] reverseObjectEnumerator]; (path = [pathEnum nextObject]); )
    1966             :         {
    1967             :                 filePath = [[path stringByAppendingPathComponent:folderName] stringByAppendingPathComponent:fileName];
    1968             :                 if ([fmgr oo_oxzFileExistsAtPath:filePath])
    1969             :                 {
    1970             :                         result = filePath;
    1971             :                         break;
    1972             :                 }
    1973             :                 
    1974             :                 filePath = [path stringByAppendingPathComponent:fileName];
    1975             :                 if ([fmgr oo_oxzFileExistsAtPath:filePath])
    1976             :                 {
    1977             :                         result = filePath;
    1978             :                         break;
    1979             :                 }
    1980             :         }
    1981             :         
    1982             :         if (result != nil)
    1983             :         {
    1984             :                 OOLog(@"resourceManager.foundFile", @"Found %@/%@ at %@", folderName, fileName, filePath);
    1985             :                 if (useCache)
    1986             :                 {
    1987             :                         [cache setObject:result forKey:cacheKey inCache:@"resolved paths"];
    1988             :                 }
    1989             :         }
    1990             :         return result;
    1991             : }
    1992             : 
    1993             : 
    1994             : /* use extreme caution in calling with usePathCache:NO - this can be
    1995             :  * an extremely expensive operation */
    1996           0 : + (id) retrieveFileNamed:(NSString *)fileName
    1997             :                                 inFolder:(NSString *)folderName
    1998             :                                    cache:(NSMutableDictionary **)ioCache
    1999             :                                          key:(NSString *)key
    2000             :                                    class:(Class)class
    2001             :                         usePathCache:(BOOL)useCache
    2002             : {
    2003             :         id                              result = nil;
    2004             :         NSString                *path = nil;
    2005             :         
    2006             :         if (ioCache)
    2007             :         {
    2008             :                 if (key == nil)  key = [NSString stringWithFormat:@"%@:%@", folderName, fileName];
    2009             :                 if (*ioCache != nil)
    2010             :                 {
    2011             :                         // return the cached object, if any
    2012             :                         result = [*ioCache objectForKey:key];
    2013             :                         if (result)  return result;
    2014             :                 }
    2015             :         }
    2016             :         
    2017             :         path = [self pathForFileNamed:fileName inFolder:folderName cache:useCache];
    2018             :         if (path != nil)  result = [[[class alloc] initWithContentsOfFile:path] autorelease];
    2019             :         
    2020             :         if (result != nil && ioCache != NULL)
    2021             :         {
    2022             :                 if (*ioCache == nil)  *ioCache = [[NSMutableDictionary alloc] init];
    2023             :                 [*ioCache setObject:result forKey:key];
    2024             :         }
    2025             :         
    2026             :         return result;
    2027             : }
    2028             : 
    2029             : 
    2030             : + (OOMusic *) ooMusicNamed:(NSString *)fileName inFolder:(NSString *)folderName
    2031             : {
    2032             :         return [self retrieveFileNamed:fileName
    2033             :                                                   inFolder:folderName
    2034             :                                                          cache:NULL     // Don't cache music objects; minimizing latency isn't really important.
    2035             :                                                            key:[NSString stringWithFormat:@"OOMusic:%@:%@", folderName, fileName]
    2036             :                                                          class:[OOMusic class]
    2037             :                                           usePathCache:YES];
    2038             : }
    2039             : 
    2040             : 
    2041             : + (OOSound *) ooSoundNamed:(NSString *)fileName inFolder:(NSString *)folderName
    2042             : {
    2043             :         return [self retrieveFileNamed:fileName
    2044             :                                                   inFolder:folderName
    2045             :                                                          cache:&sSoundCache
    2046             :                                                            key:[NSString stringWithFormat:@"OOSound:%@:%@", folderName, fileName]
    2047             :                                                          class:[OOSound class]
    2048             :                                           usePathCache:YES];
    2049             : }
    2050             : 
    2051             : 
    2052             : + (NSString *) stringFromFilesNamed:(NSString *)fileName inFolder:(NSString *)folderName
    2053             : {
    2054             :         return [self stringFromFilesNamed:fileName inFolder:folderName cache:YES];
    2055             : }
    2056             : 
    2057             : 
    2058             : + (NSString *) stringFromFilesNamed:(NSString *)fileName inFolder:(NSString *)folderName cache:(BOOL)useCache
    2059             : {
    2060             :         id                              result = nil;
    2061             :         NSString                *path = nil;
    2062             :         NSString                *key = nil;
    2063             :         
    2064             :         if (useCache)
    2065             :         {
    2066             :                 key = [NSString stringWithFormat:@"%@:%@", folderName, fileName];
    2067             :                 if (sStringCache != nil)
    2068             :                 {
    2069             :                         // return the cached object, if any
    2070             :                         result = [sStringCache objectForKey:key];
    2071             :                         if (result)  return result;
    2072             :                 }
    2073             :         }
    2074             :         
    2075             :         path = [self pathForFileNamed:fileName inFolder:folderName cache:YES];
    2076             :         if (path != nil)  result = [NSString stringWithContentsOfUnicodeFile:path];
    2077             :         
    2078             :         if (result != nil && useCache)
    2079             :         {
    2080             :                 if (sStringCache == nil)  sStringCache = [[NSMutableDictionary alloc] init];
    2081             :                 [sStringCache setObject:result forKey:key];
    2082             :         }
    2083             :         
    2084             :         return result;
    2085             : }
    2086             : 
    2087             : 
    2088             : + (NSDictionary *)loadScripts
    2089             : {
    2090             :         NSMutableDictionary                     *loadedScripts = nil;
    2091             :         NSArray                                         *results = nil;
    2092             :         NSArray                                         *paths = nil;
    2093             :         NSEnumerator                            *pathEnum = nil;
    2094             :         NSString                                        *path = nil;
    2095             :         NSEnumerator                            *scriptEnum = nil;
    2096             :         OOScript                                        *script = nil;
    2097             :         NSString                                        *name = nil;
    2098             :         NSAutoreleasePool                       *pool = nil;
    2099             :         
    2100             :         OOLog(@"script.load.world.begin", @"%@", @"Loading world scripts...");
    2101             :         
    2102             :         loadedScripts = [NSMutableDictionary dictionary];
    2103             :         paths = [ResourceManager paths];
    2104             :         for (pathEnum = [paths objectEnumerator]; (path = [pathEnum nextObject]); )
    2105             :         {
    2106             :                 // excluding world-scripts.plist also excludes script.js / script.plist
    2107             :                 // though as those core files don't and won't exist this is not
    2108             :                 // a problem.
    2109             :                 if (![ResourceManager corePlist:@"world-scripts.plist" excludedAt:path])
    2110             :                 {
    2111             :                         pool = [[NSAutoreleasePool alloc] init];
    2112             :                 
    2113             :                         @try
    2114             :                         {
    2115             :                                 results = [OOScript worldScriptsAtPath:[path stringByAppendingPathComponent:@"Config"]];
    2116             :                                 if (results == nil) results = [OOScript worldScriptsAtPath:path];
    2117             :                                 if (results != nil)
    2118             :                                 {
    2119             :                                         for (scriptEnum = [results objectEnumerator]; (script = [scriptEnum nextObject]); )
    2120             :                                         {
    2121             :                                                 name = [script name];
    2122             :                                                 if (name != nil)  [loadedScripts setObject:script forKey:name];
    2123             :                                                 else  OOLog(@"script.load.unnamed", @"Discarding anonymous script %@", script);
    2124             :                                         }
    2125             :                                 }
    2126             :                         }
    2127             :                         @catch (NSException *exception)
    2128             :                         {
    2129             :                                 OOLog(@"script.load.exception", @"***** %s encountered exception %@ (%@) while trying to load script from %@ -- ignoring this location.", __PRETTY_FUNCTION__, [exception name], [exception reason], path);
    2130             :                                 // Ignore exception and keep loading other scripts.
    2131             :                         }
    2132             :                 
    2133             :                         [pool release];
    2134             :                 }
    2135             :         }
    2136             :         
    2137             :         if (OOLogWillDisplayMessagesInClass(@"script.load.world.listAll"))
    2138             :         {
    2139             :                 NSUInteger count = [loadedScripts count];
    2140             :                 if (count != 0)
    2141             :                 {
    2142             :                         NSMutableArray          *displayNames = nil;
    2143             :                         NSEnumerator            *scriptEnum = nil;
    2144             :                         OOScript                        *script = nil;
    2145             :                         NSString                        *displayString = nil;
    2146             :                         
    2147             :                         displayNames = [NSMutableArray arrayWithCapacity:count];
    2148             :                         
    2149             :                         for (scriptEnum = [loadedScripts objectEnumerator]; (script = [scriptEnum nextObject]); )
    2150             :                         {
    2151             :                                 [displayNames addObject:[script displayName]];
    2152             :                         }
    2153             :                         
    2154             :                         displayString = [[displayNames sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] componentsJoinedByString:@"\n    "];
    2155             :                         OOLog(@"script.load.world.listAll", @"Loaded %llu world scripts:\n    %@", count, displayString);
    2156             :                 }
    2157             :                 else
    2158             :                 {
    2159             :                         OOLog(@"script.load.world.listAll", @"%@", @"*** No world scripts loaded.");
    2160             :                 }
    2161             :         }
    2162             :         
    2163             :         return loadedScripts;
    2164             : }
    2165             : 
    2166             : 
    2167             : + (BOOL) writeDiagnosticData:(NSData *)data toFileNamed:(NSString *)name
    2168             : {
    2169             :         if (data == nil || name == nil)  return NO;
    2170             :         
    2171             :         NSString *directory = [self diagnosticFileLocation];
    2172             :         if (directory == nil)  return NO;
    2173             :         
    2174             :         NSArray *nameComponents = [name componentsSeparatedByString:@"/"];
    2175             :         NSUInteger count = [nameComponents count];
    2176             :         if (count > 1)
    2177             :         {
    2178             :                 name = [nameComponents lastObject];
    2179             :                 
    2180             :                 for (NSUInteger i = 0; i < count - 1; i++)
    2181             :                 {
    2182             :                         NSString *component = [nameComponents objectAtIndex:i];
    2183             :                         if ([component hasPrefix:@"."])
    2184             :                         {
    2185             :                                 component = [@"!" stringByAppendingString:[component substringFromIndex:1]];
    2186             :                         }
    2187             :                         directory = [directory stringByAppendingPathComponent:component];
    2188             :                         [[NSFileManager defaultManager] oo_createDirectoryAtPath:directory attributes:nil];
    2189             :                 }
    2190             :         }
    2191             :         
    2192             :         return [data writeToFile:[directory stringByAppendingPathComponent:name] atomically:YES];
    2193             : }
    2194             : 
    2195             : 
    2196             : + (BOOL) writeDiagnosticString:(NSString *)string toFileNamed:(NSString *)name
    2197             : {
    2198             :         return [self writeDiagnosticData:[string dataUsingEncoding:NSUTF8StringEncoding] toFileNamed:name];
    2199             : }
    2200             : 
    2201             : 
    2202             : + (BOOL) writeDiagnosticPList:(id)plist toFileNamed:(NSString *)name
    2203             : {
    2204             :         NSData *data = [plist oldSchoolPListFormatWithErrorDescription:NULL];
    2205             :         if (data == nil)  [NSPropertyListSerialization dataFromPropertyList:plist format:NSPropertyListXMLFormat_v1_0 errorDescription:NULL];
    2206             :         if (data == nil)  return NO;
    2207             :         
    2208             :         return [self writeDiagnosticData:data toFileNamed:name];
    2209             : }
    2210             : 
    2211             : 
    2212             : + (NSDictionary *) materialDefaults
    2213             : {
    2214             :         return [self dictionaryFromFilesNamed:@"material-defaults.plist" inFolder:@"Config" andMerge:YES];
    2215             : }
    2216             : 
    2217             : 
    2218           0 : + (BOOL)directoryExists:(NSString *)inPath create:(BOOL)inCreate
    2219             : {
    2220             :         BOOL                            exists, directory;
    2221             :         NSFileManager           *fmgr =  [NSFileManager defaultManager];
    2222             :         
    2223             :         exists = [fmgr fileExistsAtPath:inPath isDirectory:&directory];
    2224             :         
    2225             :         if (exists && !directory)
    2226             :         {
    2227             :                 OOLog(@"resourceManager.write.buildPath.failed", @"Expected %@ to be a folder, but it is a file.", inPath);
    2228             :                 return NO;
    2229             :         }
    2230             :         if (!exists)
    2231             :         {
    2232             :                 if (!inCreate) return NO;
    2233             :                 if (![fmgr oo_createDirectoryAtPath:inPath attributes:nil])
    2234             :                 {
    2235             :                         OOLog(@"resourceManager.write.buildPath.failed", @"Could not create folder %@.", inPath);
    2236             :                         return NO;
    2237             :                 }
    2238             :         }
    2239             :         
    2240             :         return YES;
    2241             : }
    2242             : 
    2243             : 
    2244             : + (NSString *) diagnosticFileLocation
    2245             : {
    2246             :         return OOLogHandlerGetLogBasePath();
    2247             : }
    2248             : 
    2249             : 
    2250           0 : + (void) logPaths
    2251             : {
    2252             :         NSMutableArray                  *displayPaths = nil;
    2253             :         NSEnumerator                    *pathEnum = nil;
    2254             :         NSString                                *path = nil;
    2255             : 
    2256             :         // Prettify paths for logging.
    2257             :         displayPaths = [NSMutableArray arrayWithCapacity:[sSearchPaths count]];
    2258             :         for (pathEnum = [sSearchPaths objectEnumerator]; (path = [pathEnum nextObject]); )
    2259             :         {
    2260             :                 [displayPaths addObject:[[path stringByStandardizingPath] stringByAbbreviatingWithTildeInPath]];
    2261             :         }
    2262             :         
    2263             :         OOLog(@"searchPaths.dumpAll", @"Resource paths: %@\n    %@", sUseAddOns, [displayPaths componentsJoinedByString:@"\n    "]);
    2264             : 
    2265             : }
    2266             : 
    2267             : 
    2268             : + (void) clearCaches
    2269             : {
    2270             :         [sSoundCache release];
    2271             :         sSoundCache = nil;
    2272             :         [sStringCache release];
    2273             :         sStringCache = nil;
    2274             : }
    2275             : 
    2276             : @end

Generated by: LCOV version 1.14