Launcher.m 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  1. #import "Launcher.h"
  2. #import "ConsoleView.h"
  3. #include <stdlib.h>
  4. #include <unistd.h> /* _exit() */
  5. #include <util.h> /* forkpty() */
  6. // User default keys
  7. #define dkVERSION @"version"
  8. #define dkFULLSCREEN @"fullscreen"
  9. #define dkFSAA @"fsaa"
  10. #define dkRESOLUTION @"resolution"
  11. #define dkADVANCEDOPTS @"advancedOptions"
  12. #define dkSERVEROPTS @"server_options"
  13. #define dkDESCRIPTION @"server_description"
  14. #define dkPASSWORD @"server_password"
  15. #define dkMAXCLIENTS @"server_maxclients"
  16. #define kMaxDisplays 16
  17. //If you make a MOD then please change this, the bundle indentifier, the file extensions (.cgz, .dmo), and the url registration.
  18. #define kASSAULTCUBE @"assaultcube"
  19. #define kUSERCONFGIDIR @"assaultcube/v1.2next"
  20. //tab names, i.e. image names (text is localised)
  21. #define tkMAIN @"Main"
  22. #define tkKEYS @"Keys"
  23. #define tkSERVER @"Server"
  24. @interface NSString(Extras)
  25. @end
  26. @implementation NSString(Extras)
  27. - (NSString*)expand {
  28. NSMutableString *str = [NSMutableString string];
  29. [str setString:self];
  30. [str replaceOccurrencesOfString:@":s" withString:kASSAULTCUBE options:0 range:NSMakeRange(0, [str length])];
  31. return str;
  32. }
  33. @end
  34. @interface NSUserDefaults(Extras) // unless you want strings with "(null)" in them :-/
  35. - (NSString*)nonNullStringForKey:(NSString*)key;
  36. @end
  37. @implementation NSUserDefaults(Extras)
  38. - (NSString*)nonNullStringForKey:(NSString*)key {
  39. NSString *result = [self stringForKey:key];
  40. return (result ? result : @"");
  41. }
  42. @end
  43. @interface Map : NSObject {
  44. NSString *path;
  45. BOOL demo, user;
  46. }
  47. @end
  48. @implementation Map
  49. - (id)initWithPath:(NSString*)aPath user:(BOOL)aUser demo:(BOOL)aDemo
  50. {
  51. if((self = [super init]))
  52. {
  53. path = [[aPath stringByDeletingPathExtension] retain];
  54. user = aUser;
  55. demo = aDemo;
  56. }
  57. return self;
  58. }
  59. - (void)dealloc
  60. {
  61. [path release];
  62. [super dealloc];
  63. }
  64. - (NSString*)path { return (demo ? [NSString stringWithFormat:@"--loaddemo=%@", path] : path); } // minor hack
  65. - (NSString*)name { return [path lastPathComponent]; }
  66. - (NSImage*)image
  67. {
  68. NSImage *image = [[NSImage alloc] initWithContentsOfFile:[path stringByAppendingString:@".jpg"]];
  69. if(!image && demo) image = [NSImage imageNamed:tkMAIN];
  70. if(!image) image = [NSImage imageNamed:@"Nomap"];
  71. return image;
  72. }
  73. - (NSString*)text
  74. {
  75. NSString *text = [NSString alloc];
  76. NSError *error;
  77. if([text respondsToSelector:@selector(initWithContentsOfFile:encoding:error:)])
  78. text = [text initWithContentsOfFile:[path stringByAppendingString:@".txt"] encoding:NSASCIIStringEncoding error:&error];
  79. else
  80. text = [text initWithContentsOfFile:[path stringByAppendingString:@".txt"]]; //deprecated in 10.4
  81. if(!text) text = (demo)?@"Recorded demo data":@"";
  82. return text;
  83. }
  84. - (void)setText:(NSString*)text { } // wtf? - damn textfield believes it's editable
  85. - (NSString*)tickIfExists:(NSString*)ext
  86. {
  87. unichar tickCh = 0x2713;
  88. return ([[NSFileManager defaultManager] fileExistsAtPath:[path stringByAppendingString:ext]] ? [NSString stringWithCharacters:&tickCh length:1] : @"");
  89. }
  90. - (NSString*)hasImage { return [self tickIfExists:@".jpg"]; }
  91. - (NSString*)hasText { return [self tickIfExists:@".txt"]; }
  92. - (NSString*)hasCfg { return [self tickIfExists:@".cfg"]; }
  93. - (NSString*)user {
  94. unichar tickCh = 0x2713;
  95. return (user ? [NSString stringWithCharacters:&tickCh length:1] : @"");
  96. }
  97. @end
  98. static int numberForKey(CFDictionaryRef desc, CFStringRef key)
  99. {
  100. CFNumberRef value;
  101. int num = 0;
  102. if ((value = CFDictionaryGetValue(desc, key)) == NULL)
  103. return 0;
  104. CFNumberGetValue(value, kCFNumberIntType, &num);
  105. return num;
  106. }
  107. @interface Launcher(ToolBar)
  108. @end
  109. @implementation Launcher(ToolBar)
  110. - (void)switchViews:(NSToolbarItem *)item
  111. {
  112. //20/7/13: RR Removing keys submenu
  113. // NSView *views[] = {view1, view3, view4};
  114. NSView *views[] = {view1, view4};
  115. NSView *prefsView = views[[item tag]-1];
  116. //to stop flicker, we make a temp blank view.
  117. NSView *tempView = [[NSView alloc] initWithFrame:[[window contentView] frame]];
  118. [window setContentView:tempView];
  119. [tempView release];
  120. //mojo to get the right frame for the new window.
  121. NSRect newFrame = [window frame];
  122. newFrame.size.height = [prefsView frame].size.height + ([window frame].size.height - [[window contentView] frame].size.height);
  123. newFrame.size.width = [prefsView frame].size.width;
  124. newFrame.origin.y += ([[window contentView] frame].size.height - [prefsView frame].size.height);
  125. //set the frame to newFrame and animate it.
  126. [window setFrame:newFrame display:YES animate:YES];
  127. //set the main content view to the new view we have picked through.
  128. [window setContentView:prefsView];
  129. [window setContentMinSize:[prefsView bounds].size];
  130. }
  131. - (void)initToolBar
  132. {
  133. toolBarItems = [[NSMutableDictionary alloc] init];
  134. NSEnumerator *e = [[self toolbarDefaultItemIdentifiers:nil] objectEnumerator];
  135. NSString *identifier;
  136. while(identifier = [e nextObject])
  137. {
  138. NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:identifier];
  139. int tag = [identifier intValue];
  140. NSString *name = identifier;
  141. SEL action = @selector(displayHelp:);
  142. id target = self;
  143. if(tag) {
  144. //20/7/13: RR Removing keys submenu
  145. // NSString *names[] = {tkMAIN, tkKEYS, tkSERVER};
  146. NSString *names[] = {tkMAIN, tkSERVER};
  147. name = names[tag-1];
  148. action = @selector(switchViews:);
  149. target = self;
  150. }
  151. [item setTag:tag];
  152. [item setTarget:target];
  153. [item setAction:action];
  154. [item setLabel:NSLocalizedString(name, @"")];
  155. [item setImage:[NSImage imageNamed:name]];
  156. [toolBarItems setObject:item forKey:identifier];
  157. [item release];
  158. }
  159. NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@""];
  160. [toolbar setDelegate:self];
  161. [toolbar setAllowsUserCustomization:NO];
  162. [toolbar setAutosavesConfiguration:NO];
  163. [window setToolbar:toolbar];
  164. [toolbar release];
  165. if([window respondsToSelector:@selector(setShowsToolbarButton:)]) [window setShowsToolbarButton:NO]; //10.4+
  166. //select the first by default
  167. NSToolbarItem *first = [toolBarItems objectForKey:[[self toolbarDefaultItemIdentifiers:nil] objectAtIndex:0]];
  168. [toolbar setSelectedItemIdentifier:[first itemIdentifier]];
  169. [self switchViews:first];
  170. }
  171. #pragma mark toolbar delegate methods
  172. - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
  173. {
  174. return [toolBarItems objectForKey:itemIdentifier];
  175. }
  176. - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)theToolbar
  177. {
  178. return [self toolbarDefaultItemIdentifiers:theToolbar];
  179. }
  180. - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar
  181. {
  182. NSMutableArray *array = (NSMutableArray *)[self toolbarSelectableItemIdentifiers:toolbar];
  183. [array addObject:NSToolbarFlexibleSpaceItemIdentifier];
  184. [array addObject:@"Help"];
  185. return array;
  186. }
  187. - (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar
  188. {
  189. NSMutableArray *array = [NSMutableArray array];
  190. //20/7/13: RR Removing keys submenu
  191. // NSView *views[] = {view1, view3, view4};
  192. NSView *views[] = {view1, view4};
  193. int i;
  194. for(i = 0; i < sizeof(views)/sizeof(NSView*); i++) if(views[i]) [array addObject:[NSString stringWithFormat:@"%d", i+1]];
  195. return array;
  196. }
  197. - (void)displayHelp:(id)sender
  198. {
  199. NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/help/README.html"];
  200. if (![[NSWorkspace sharedWorkspace] openFile:path])
  201. NSLog(@"Warning: [[NSWorkspace sharedWorkspace] openFile:path] failed");
  202. }
  203. @end
  204. @implementation Launcher
  205. /* directory where the executable lives */
  206. + (NSString *)cwd
  207. {
  208. return [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Contents/gamedata"];
  209. }
  210. /* directory where user files are kept - typically /Users/<name>/Application Support/assaultcube_major.minor */
  211. + (NSString*)userdir
  212. {
  213. FSRef folder;
  214. NSString *path = nil;
  215. if(FSFindFolder(kUserDomain, kApplicationSupportFolderType, NO, &folder) == noErr) {
  216. CFURLRef url = CFURLCreateFromFSRef(kCFAllocatorDefault, &folder);
  217. path = [(NSURL *)url path];
  218. CFRelease(url);
  219. path = [path stringByAppendingPathComponent:kUSERCONFGIDIR];
  220. NSFileManager *fm = [NSFileManager defaultManager];
  221. if(![fm fileExistsAtPath:path]) [fm createDirectoryAtPath:path attributes:nil]; //ensure it exists
  222. }
  223. return path;
  224. }
  225. - (void)addResolutionsForDisplay:(CGDirectDisplayID)dspy
  226. {
  227. CFIndex i, cnt;
  228. CFArrayRef modeList = CGDisplayAvailableModes(dspy);
  229. if(modeList == NULL) return;
  230. cnt = CFArrayGetCount(modeList);
  231. for(i = 0; i < cnt; i++) {
  232. CFDictionaryRef mode = CFArrayGetValueAtIndex(modeList, i);
  233. NSString *title = [NSString stringWithFormat:@"%i x %i", numberForKey(mode, kCGDisplayWidth), numberForKey(mode, kCGDisplayHeight)];
  234. if(![resolutions itemWithTitle:title]) [resolutions addItemWithTitle:title];
  235. }
  236. }
  237. - (void)initResolutions
  238. {
  239. CGDirectDisplayID display[kMaxDisplays];
  240. CGDisplayCount numDisplays;
  241. [resolutions removeAllItems];
  242. if(CGGetActiveDisplayList(kMaxDisplays, display, &numDisplays) == CGDisplayNoErr)
  243. {
  244. CGDisplayCount i;
  245. for (i = 0; i < numDisplays; i++)
  246. [self addResolutionsForDisplay:display[i]];
  247. }
  248. [resolutions selectItemAtIndex: [[NSUserDefaults standardUserDefaults] integerForKey:dkRESOLUTION]];
  249. }
  250. /* build key array from config data */
  251. -(NSArray *)getKeys:(NSDictionary *)dict
  252. {
  253. NSMutableArray *arr = [NSMutableArray array];
  254. NSEnumerator *e = [dict keyEnumerator];
  255. NSString *key;
  256. while ((key = [e nextObject]))
  257. {
  258. int pos = [key rangeOfString:@"bind."].location;
  259. if(pos == NSNotFound || pos > 5) continue;
  260. [arr addObject:[NSDictionary dictionaryWithObjectsAndKeys: //keys used in nib
  261. [key substringFromIndex:pos+5], @"key",
  262. [key substringToIndex:pos], @"mode",
  263. [dict objectForKey:key], @"action",
  264. nil]];
  265. }
  266. return arr;
  267. }
  268. /*
  269. * extract a dictionary from the config files containing:
  270. * - name, team, gamma strings
  271. * - bind/editbind '.' key strings
  272. */
  273. -(NSDictionary *)readConfigFiles
  274. {
  275. NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
  276. [dict setObject:@"" forKey:@"name"]; //ensure these entries are never nil
  277. [dict setObject:@"" forKey:@"team"];
  278. NSMutableArray *lines = [NSMutableArray array];
  279. NSString *files[] = {@"config/saved.cfg", @"config/autoexec.cfg"};
  280. int i;
  281. for(i = 0; i < sizeof(files)/sizeof(NSString*); i++)
  282. {
  283. NSString *file = [Launcher userdir];
  284. file = [file stringByAppendingPathComponent:files[i]];
  285. [lines addObjectsFromArray:[[NSString stringWithContentsOfFile:file usedEncoding:NULL error:NULL] componentsSeparatedByString:@"\n"]]; // 10.4+
  286. if(i==0 && ![lines count]) // ugh - special case when first run...
  287. {
  288. NSString *defaultfiles[] = {@"config/defaults.cfg", @"config/resetbinds.cfg"};
  289. int j;
  290. for(j = 0; j < sizeof(defaultfiles)/sizeof(NSString*); j++)
  291. {
  292. file = [[Launcher cwd] stringByAppendingPathComponent:defaultfiles[j]];
  293. [lines addObjectsFromArray:[[NSString stringWithContentsOfFile:file usedEncoding:NULL error:NULL] componentsSeparatedByString:@"\n"]]; // 10.4+
  294. }
  295. }
  296. NSString *line;
  297. NSEnumerator *e = [lines objectEnumerator];
  298. while(line = [e nextObject])
  299. {
  300. NSRange r; // more flexible to do this manually rather than via NSScanner...
  301. int j = 0;
  302. while(j < [line length] && [line characterAtIndex:j] <= ' ') j++; //skip white
  303. if(j != 0) continue; // shouldn't be indented
  304. r.location = j;
  305. while(j < [line length] && [line characterAtIndex:j] > ' ') j++; //until white
  306. r.length = j - r.location;
  307. NSString *type = [line substringWithRange:r];
  308. while(j < [line length] && [line characterAtIndex:j] <= ' ') j++; //skip white
  309. if(j < [line length] && [line characterAtIndex:j] == '"')
  310. {
  311. r.location = ++j;
  312. while(j < [line length] && [line characterAtIndex:j] != '"') j++; //until close quote
  313. r.length = (j++) - r.location;
  314. } else {
  315. r.location = j;
  316. while(j < [line length] && [line characterAtIndex:j] > ' ') j++; //until white
  317. r.length = j - r.location;
  318. }
  319. if(r.location+r.length >= [line length]) continue; //missing value
  320. NSString *value = [line substringWithRange:r];
  321. while(j < [line length] && [line characterAtIndex:j] <= ' ') j++; //skip white
  322. NSString *remainder = [line substringFromIndex:j];
  323. if([type isEqual:@"name"] || [type isEqual:@"team"] || [type isEqual:@"gamma"])
  324. [dict setObject:value forKey:type];
  325. else if([type isEqual:@"bind"] || [type isEqual:@"editbind"] || [type isEqual:@"specbind"])
  326. [dict setObject:remainder forKey:[NSString stringWithFormat:@"%@.%@", type,value]];
  327. }
  328. }
  329. return dict;
  330. }
  331. - (void)killServer {
  332. if(server > 0) kill(server, SIGKILL); //@WARNING - you do NOT want a 0 or -1 to be accidentally sent a kill!
  333. server = -1;
  334. [multiplayer setTitle:NSLocalizedString(@"Start", @"")];
  335. [console appendText:@"\n \n"];
  336. }
  337. - (void)serverDataAvailable:(NSNotification *)note
  338. {
  339. NSFileHandle *taskOutput = [note object];
  340. NSData *data = [[note userInfo] objectForKey:NSFileHandleNotificationDataItem];
  341. if (data && [data length])
  342. {
  343. NSString *text = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
  344. [console appendText:text];
  345. [text release];
  346. [taskOutput readInBackgroundAndNotify]; //wait for more data
  347. }
  348. else
  349. {
  350. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  351. [nc removeObserver:self name:NSFileHandleReadCompletionNotification object:taskOutput];
  352. close([taskOutput fileDescriptor]);
  353. [self killServer];
  354. }
  355. }
  356. - (BOOL)launchGame:(NSArray *)args {
  357. NSString *cwd = [Launcher cwd];
  358. NSString *exe = [[NSBundle bundleWithPath:[cwd stringByAppendingPathComponent:[@":s.app" expand]]] executablePath];
  359. BOOL okay = YES;
  360. if([args containsObject:@"-d"])
  361. {
  362. if(server != -1) return NO; // server is already running
  363. const char **argv = (const char**)malloc(sizeof(char*)*([args count] + 2)); //{path, <args>, NULL};
  364. argv[0] = [exe fileSystemRepresentation];
  365. argv[[args count]+1] = NULL;
  366. int i;
  367. for(i = 0; i < [args count]; i++) argv[i+1] = [[args objectAtIndex:i] UTF8String];
  368. int fdm;
  369. NSString *fail = [NSLocalizedString(@"ServerAlertMesg", nil) expand];
  370. switch ( (server = forkpty(&fdm, NULL, NULL, NULL)) ) // forkpty so we can reliably grab SDL console
  371. {
  372. case -1:
  373. [console appendLine:fail];
  374. [self killServer];
  375. okay = NO;
  376. break;
  377. case 0: // child
  378. chdir([cwd fileSystemRepresentation]);
  379. if(execv([exe fileSystemRepresentation], (char*const*)argv) == -1) fprintf(stderr, "%s\n", [fail UTF8String]);
  380. _exit(0);
  381. default: // parent
  382. [multiplayer setTitle:NSLocalizedString(@"Stop", @"")];
  383. NSFileHandle *taskOutput = [[NSFileHandle alloc] initWithFileDescriptor:fdm];
  384. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  385. [nc addObserver:self selector:@selector(serverDataAvailable:) name:NSFileHandleReadCompletionNotification object:taskOutput];
  386. [taskOutput readInBackgroundAndNotify];
  387. break;
  388. }
  389. free(argv);
  390. }
  391. else
  392. {
  393. NS_DURING
  394. NSTask *task = [[NSTask alloc] init];
  395. [task setCurrentDirectoryPath:cwd];
  396. [task setLaunchPath:exe];
  397. [task setArguments:args]; NSLog(@"%@",[args description]);
  398. [task setEnvironment:[NSDictionary dictionaryWithObjectsAndKeys:
  399. @"1", @"SDL_SINGLEDISPLAY",
  400. @"1", @"SDL_ENABLEAPPEVENTS", nil
  401. ]]; // makes Command-H, Command-M and Command-Q work at least when not in fullscreen
  402. [task launch];
  403. if(server == -1) [NSApp terminate:self]; //if there is a server then don't exit!
  404. NS_HANDLER
  405. //NSLog(@"%@", localException);
  406. NSBeginCriticalAlertSheet(
  407. [NSLocalizedString(@"ClientAlertTitle", @"") expand] , nil, nil, nil,
  408. window, nil, nil, nil, nil,
  409. @"%@", [NSLocalizedString(@"ClientAlertMesg", @"") expand]);
  410. okay = NO;
  411. NS_ENDHANDLER
  412. }
  413. return okay;
  414. }
  415. /*
  416. * nil will just launch the fps game
  417. * "-e.." will launch and run commands
  418. * otherwise we are specifying a map to play
  419. */
  420. - (BOOL)playFile:(id)filename
  421. {
  422. NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
  423. NSArray *res = [[resolutions titleOfSelectedItem] componentsSeparatedByString:@" x "];
  424. NSMutableArray *args = [NSMutableArray array];
  425. [args addObject:[NSString stringWithFormat:@"--home=%@", [Launcher userdir]]];
  426. [args addObject:@"--init"];
  427. [args addObject:[NSString stringWithFormat:@"-w%@", [res objectAtIndex:0]]];
  428. [args addObject:[NSString stringWithFormat:@"-h%@", [res objectAtIndex:1]]];
  429. [args addObject:@"-z32"]; //otherwise seems to have a fondness to use -z16 which looks crap
  430. [args addObject:[NSString stringWithFormat:@"-a%d", [defs integerForKey:dkFSAA]]];
  431. [args addObject:[NSString stringWithFormat:@"-t%d", [defs integerForKey:dkFULLSCREEN]]];
  432. if ([stencil state] == NSOnState)
  433. [args addObject:@"-s8"];
  434. NSMutableArray *cmds = [NSMutableArray array];
  435. if(filename)
  436. {
  437. if([filename hasPrefix:@"-e"])
  438. [cmds addObject:[filename substringFromIndex:2]];
  439. if([filename hasPrefix:[NSString stringWithFormat:@"%@://", kASSAULTCUBE]])
  440. [args addObject:filename];
  441. else
  442. {
  443. if([filename hasPrefix:@"--loaddemo"])
  444. [args addObject:filename];
  445. else
  446. [args addObject:[NSString stringWithFormat:@"--loadmap=%@", filename]];
  447. }
  448. }
  449. if([cmds count] > 0)
  450. {
  451. NSString *script = [cmds objectAtIndex:0];
  452. int i;
  453. for(i = 1; i < [cmds count]; i++) script = [NSString stringWithFormat:@"%@;%@", script, [cmds objectAtIndex:i]];
  454. [args addObject:[NSString stringWithFormat:@"-e%@", script]];
  455. }
  456. NSEnumerator *e = [[[defs nonNullStringForKey:dkADVANCEDOPTS] componentsSeparatedByString:@" "] objectEnumerator];
  457. NSString *opt;
  458. while(opt = [e nextObject]) if([opt length] != 0) [args addObject:opt]; //skip empty ones
  459. return [self launchGame:args];
  460. }
  461. - (void)scanMaps:(id)obj //@note threaded!
  462. {
  463. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  464. int i;
  465. for(i = 0; i < 2; i++)
  466. {
  467. NSString *dir = (i==0) ? [Launcher cwd] : [Launcher userdir];
  468. NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtPath:dir];
  469. NSString *file;
  470. while(file = [enumerator nextObject])
  471. {
  472. NSString *role = [fileRoles objectForKey:[file pathExtension]];
  473. if(role)
  474. {
  475. Map *map = [[Map alloc] initWithPath:[dir stringByAppendingPathComponent:file] user:(i==1) demo:[role isEqual:@"Viewer"]];
  476. [maps performSelectorOnMainThread:@selector(addObject:) withObject:map waitUntilDone:NO];
  477. }
  478. }
  479. }
  480. [prog performSelectorOnMainThread:@selector(stopAnimation:) withObject:nil waitUntilDone:NO];
  481. [pool release];
  482. }
  483. - (void)initMaps
  484. {
  485. [prog startAnimation:nil];
  486. [maps removeObjects:[maps arrangedObjects]];
  487. [NSThread detachNewThreadSelector: @selector(scanMaps:) toTarget:self withObject:nil];
  488. }
  489. - (void)awakeFromNib
  490. {
  491. //generate some pretty icons if they are missing
  492. NSRect region = NSMakeRect(0, 0, 64, 64);
  493. NSImage *image = [NSImage imageNamed:tkMAIN];
  494. if(!image) {
  495. image = [[NSImage imageNamed:@"NSApplicationIcon"] copy];
  496. [image setSize:region.size];
  497. [image setName:tkMAIN]; //one less image to include
  498. }
  499. [self initToolBar];
  500. [window setBackgroundColor:[NSColor colorWithDeviceRed:0.90 green:0.90 blue:0.90 alpha:1.0]]; //Apples 'mercury' crayon color
  501. //from the plist we determine that dmo->Viewer, and cgz->Editor
  502. fileRoles = [[NSMutableDictionary dictionary] retain];
  503. NSEnumerator *types = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDocumentTypes"] objectEnumerator];
  504. NSDictionary *type;
  505. while((type = [types nextObject])) {
  506. NSString *role = [type objectForKey:@"CFBundleTypeRole"];
  507. NSEnumerator *exts = [[type objectForKey:@"CFBundleTypeExtensions"] objectEnumerator];
  508. NSString *ext;
  509. while((ext = [exts nextObject])) [fileRoles setObject:role forKey:ext];
  510. }
  511. NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
  512. NSFileManager *fm = [NSFileManager defaultManager];
  513. NSString *appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
  514. NSString *version = [defs stringForKey:dkVERSION];
  515. if(!version || ![version isEqual:appVersion])
  516. {
  517. NSLog(@"Upgraded Version...");
  518. //need to flush lurking config files - they're automatically generated, so no big deal...
  519. NSString *dir = [Launcher userdir];
  520. [fm removeFileAtPath:[dir stringByAppendingPathComponent:@"init.cfg"] handler:nil];
  521. [fm removeFileAtPath:[dir stringByAppendingPathComponent:@"saved.cfg"] handler:nil];
  522. }
  523. [defs setObject:appVersion forKey:dkVERSION];
  524. NSDictionary *dict = [self readConfigFiles];
  525. [keys addObjects:[self getKeys:dict]];
  526. [self initMaps];
  527. [self initResolutions];
  528. server = -1;
  529. [NSApp setDelegate:self]; //so can catch the double-click, dropped files, termination
  530. [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self andSelector:@selector(getUrl:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL];
  531. }
  532. #pragma mark -
  533. #pragma mark application delegate
  534. - (void)applicationDidFinishLaunching:(NSNotification *)note {
  535. NSFileManager *fm = [NSFileManager defaultManager];
  536. NSString *dir = [Launcher cwd];
  537. if(![fm fileExistsAtPath:dir])
  538. NSBeginCriticalAlertSheet(
  539. [NSLocalizedString(@"InitAlertTitle", @"") expand], nil, nil, nil,
  540. window, self, nil, nil, nil,
  541. @"%@", [NSLocalizedString(@"InitAlertMesg", @"") expand]);
  542. }
  543. -(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
  544. return YES;
  545. }
  546. - (void)applicationWillTerminate: (NSNotification *)note {
  547. [self killServer];
  548. }
  549. //we register 'cgz' and 'dmo' as doc types
  550. - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
  551. {
  552. NSString *role = [fileRoles objectForKey:[filename pathExtension]];
  553. if(!role) return NO;
  554. BOOL demo = [role isEqual:@"Viewer"];
  555. filename = [filename stringByDeletingPathExtension]; //chop off extension
  556. int i;
  557. for(i = 0; i < 2; i++) {
  558. NSString *pkg = (i == 0) ? [Launcher cwd] : [Launcher userdir];
  559. if(!demo) pkg = [pkg stringByAppendingPathComponent:@"packages/maps/"];
  560. if([filename hasPrefix:pkg])
  561. {
  562. filename = [filename lastPathComponent]; //chop off extension
  563. return [self playFile:(demo ? [NSString stringWithFormat:@"--loaddemo=%@", filename] : filename)];
  564. }
  565. }
  566. NSBeginCriticalAlertSheet(
  567. [NSLocalizedString(@"FileAlertTitle", @"") expand], NSLocalizedString(@"Ok", @""), NSLocalizedString(@"Cancel", @""), nil,
  568. window, self, @selector(openPackageFolder:returnCode:contextInfo:), nil, nil,
  569. @"%@", [NSLocalizedString(@"FileAlertMesg", @"") expand]);
  570. return NO;
  571. }
  572. - (void)openPackageFolder:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
  573. {
  574. if(returnCode == 0) return;
  575. [self openUserdir:nil];
  576. }
  577. //we register 'assaultcube' as a url scheme
  578. - (void)getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
  579. {
  580. NSURL *url = [NSURL URLWithString:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]];
  581. if(!url) return;
  582. [self playFile:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]]; //use the game internal parser
  583. //[self playFile:[NSString stringWithFormat:@"-econnect %@", [url host]]];
  584. }
  585. #pragma mark interface actions
  586. - (IBAction)multiplayerAction:(id)sender
  587. {
  588. [window makeFirstResponder:window]; //ensure fields are exited and committed
  589. if(server != -1)
  590. {
  591. [self killServer];
  592. }
  593. else
  594. {
  595. NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
  596. NSMutableArray *args = [NSMutableArray arrayWithObject:@"-d"];
  597. NSEnumerator *e = [[[defs nonNullStringForKey:dkSERVEROPTS] componentsSeparatedByString:@" "] objectEnumerator];
  598. NSString *opt;
  599. while(opt = [e nextObject]) if([opt length] != 0) [args addObject:opt]; //skip empty ones
  600. NSString *desc = [defs nonNullStringForKey:dkDESCRIPTION];
  601. if (![desc isEqual:@""]) [args addObject:[NSString stringWithFormat:@"-n%@", desc]];
  602. NSString *pass = [defs nonNullStringForKey:dkPASSWORD];
  603. if (![pass isEqual:@""]) [args addObject:[NSString stringWithFormat:@"-p%@", pass]];
  604. if (![[admin_password stringValue] isEqual:@""]) [args addObject:[NSString stringWithFormat:@"-x%@", pass]];
  605. int clients = [defs integerForKey:dkMAXCLIENTS];
  606. if (clients > 0) [args addObject:[NSString stringWithFormat:@"-c%d", clients]];
  607. //server doesn't support --home
  608. //[args addObject:[NSString stringWithFormat:@"--home%@", [Launcher userdir]]];
  609. [self launchGame:args];
  610. }
  611. }
  612. - (IBAction)playAction:(id)sender
  613. {
  614. [window makeFirstResponder:window]; //ensure fields are exited and committed
  615. [self playFile:nil];
  616. }
  617. - (IBAction)playMap:(id)sender
  618. {
  619. NSArray *sel = [maps selectedObjects];
  620. if(sel && [sel count] > 0) [self playFile:[[sel objectAtIndex:0] path]];
  621. }
  622. - (IBAction)openUserdir:(id)sender
  623. {
  624. [[NSWorkspace sharedWorkspace] openFile:[Launcher userdir]];
  625. }
  626. @end