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

Generated by: LCOV version 1.14