/* =========================================================================== Return to Castle Wolfenstein multiplayer GPL Source Code Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”). RTCW MP Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. RTCW MP Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with RTCW MP Source Code. If not, see . In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ // $Header$ #import "Q3Controller.h" #import #import #include "client.h" #include "macosx_local.h" #ifdef OMNI_TIMER #import "macosx_timers.h" #endif #define MAX_ARGC 1024 static qboolean Sys_IsProcessingTerminationRequest = qfalse; static void Sys_CreatePathToFile( NSString *path, NSDictionary *attributes ); @interface Q3Controller ( Private ) - (void)quakeMain; @end @implementation Q3Controller #ifndef DEDICATED - (void)applicationDidFinishLaunching : (NSNotification *)notification; { NS_DURING { [self quakeMain]; } NS_HANDLER { Sys_Error( "%@", [localException reason] ); } NS_ENDHANDLER; Sys_Quit(); } -(void)applicationDidUnhide : (NSNotification *)notification; { // Don't reactivate the game if we are asking whether to quit if ( Sys_IsProcessingTerminationRequest ) { return; } if ( !Sys_Unhide() ) { // Didn't work -- hide again so we should get another chance to unhide later [NSApp hide : nil]; } } -(NSApplicationTerminateReply)applicationShouldTerminate : (NSApplication *)sender; { int choice; if ( !Sys_IsHidden ) { // We're terminating via -terminate: return NSTerminateNow; } // Avoid reactivating GL when we unhide due to this panel Sys_IsProcessingTerminationRequest = qtrue; choice = NSRunAlertPanel( nil, @ "Quit without saving?", @ "Don't Quit", @ "Quit", nil ); Sys_IsProcessingTerminationRequest = qfalse; if ( choice == NSAlertAlternateReturn ) { return NSTerminateNow; } // Make sure we get re-hidden [NSApp hide : nil]; return NSTerminateCancel; } // Actions -(IBAction)paste : (id)sender; { int shiftWasDown, insertWasDown; unsigned int currentTime; currentTime = Sys_Milliseconds(); // Save the original keyboard state shiftWasDown = keys[K_SHIFT].down; insertWasDown = keys[K_INS].down; // Fake a Shift-Insert keyboard event keys[K_SHIFT].down = qtrue; Sys_QueEvent( currentTime, SE_KEY, K_INS, qtrue, 0, NULL ); Sys_QueEvent( currentTime, SE_KEY, K_INS, qfalse, 0, NULL ); // Restore the original keyboard state keys[K_SHIFT].down = shiftWasDown; keys[K_INS].down = insertWasDown; } extern void CL_Quit_f( void ); -(IBAction)requestTerminate : (id)sender; { Com_Quit_f(); // UI_QuitMenu(); } -(void)showBanner; { static BOOL hasShownBanner = NO; if ( !hasShownBanner ) { cvar_t *showBanner; hasShownBanner = YES; showBanner = Cvar_Get( "cl_showBanner", "1", 0 ); if ( showBanner->integer != 0 ) { NSPanel *splashPanel; NSImage *bannerImage; NSRect bannerRect; NSImageView *bannerImageView; bannerImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForImageResource:@ "banner.jpg"]]; bannerRect = NSMakeRect( 0.0, 0.0, [bannerImage size].width, [bannerImage size].height ); splashPanel = [[NSPanel alloc] initWithContentRect:bannerRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; bannerImageView = [[NSImageView alloc] initWithFrame:bannerRect]; [bannerImageView setImage : bannerImage]; [splashPanel setContentView : bannerImageView]; [bannerImageView release]; [splashPanel center]; [splashPanel setHasShadow : YES]; [splashPanel orderFront : nil]; [NSThread sleepUntilDate :[NSDate dateWithTimeIntervalSinceNow : 2.5]]; [splashPanel close]; [bannerImage release]; } } } // Services -(void)connectToServer : (NSPasteboard *)pasteboard userData : (NSString *)data error : (NSString **)error; { NSArray *pasteboardTypes; pasteboardTypes = [pasteboard types]; if ([pasteboardTypes containsObject : NSStringPboardType] ) { NSString *requestedServer; requestedServer = [pasteboard stringForType:NSStringPboardType]; if ( requestedServer ) { Cbuf_AddText( va( "connect %s\n", [requestedServer cString] ) ); return; } } *error = @ "Unable to connect to server: could not find string on pasteboard"; } -(void)performCommand : (NSPasteboard *)pasteboard userData : (NSString *)data error : (NSString **)error; { NSArray *pasteboardTypes; pasteboardTypes = [pasteboard types]; if ([pasteboardTypes containsObject : NSStringPboardType] ) { NSString *requestedCommand; requestedCommand = [pasteboard stringForType:NSStringPboardType]; if ( requestedCommand ) { Cbuf_AddText( va( "%s\n", [requestedCommand cString] ) ); return; } } *error = @ "Unable to perform command: could not find string on pasteboard"; } #endif - (void)quakeMain; { NSAutoreleasePool *pool; int argc = 0; const char *argv[MAX_ARGC]; NSProcessInfo *processInfo; NSArray *arguments; unsigned int argumentIndex, argumentCount; NSFileManager *defaultManager; unsigned int commandLineLength; NSString *installationPathKey, *installationPath; char *cmdline; BOOL foundDirectory; NSString *appName, *demoAppName, *selectButton; pool = [[NSAutoreleasePool alloc] init]; [NSApp setServicesProvider : self]; processInfo = [NSProcessInfo processInfo]; arguments = [processInfo arguments]; argumentCount = [arguments count]; for ( argumentIndex = 0; argumentIndex < argumentCount; argumentIndex++ ) { NSString *arg; arg = [arguments objectAtIndex:argumentIndex]; // Don't pass the Process Serial Number command line arg that the Window Server/Finder invokes us with if ([arg hasPrefix : @ "-psn_"] ) { continue; } argv[argc++] = strdup([arg cString] ); } // Figure out where the level data is stored. installationPathKey = @ "RetailInstallationPath"; installationPath = [[NSUserDefaults standardUserDefaults] objectForKey:installationPathKey]; if ( !installationPath ) { // Default to the directory containing the executable (which is where most users will want to put it installationPath = [[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent]; installationPath = [installationPath stringByAppendingPathComponent: @ "wolfmp"]; } appName = [[[NSBundle mainBundle] infoDictionary] objectForKey: @ "CFBundleName"]; #ifdef DEDICATED #warning TJW: We are hard coding the app name here since the dedicated server is a tool, not a app bundle and does not have access to the Info.plist that the client app does. Suck. appName = @ "WolfensteinMP"; #endif demoAppName = appName; while ( YES ) { NSString *dataPath; NSOpenPanel *openPanel; int result; foundDirectory = NO; defaultManager = [NSFileManager defaultManager]; NSLog( @ "Candidate installation path = %@", installationPath ); #ifdef PRE_RELEASE_DEMO dataPath = [installationPath stringByAppendingPathComponent: @ "demomain"]; #else dataPath = [installationPath stringByAppendingPathComponent: @ "main"]; #endif if ([defaultManager fileExistsAtPath : dataPath] ) { // Check that the data directory contains at least one .pk3 file. We don't know what it will be named, so don't hard code a name (for example it might be named 'french.pk3' for a French release NSArray *files; unsigned int fileIndex; files = [defaultManager directoryContentsAtPath: dataPath]; fileIndex = [files count]; while ( fileIndex-- ) { if ([[files objectAtIndex : fileIndex] hasSuffix : @ "pk3"] ) { //NSLog(@"Found %@.", [files objectAtIndex: fileIndex]); foundDirectory = YES; break; } } } if ( foundDirectory ) { break; } #ifdef DEDICATED #warning TJW: We are hard coding the app name and default domain here since the dedicated server is a tool, not a app bundle and does not have access to the Info.plist that the client app does. Suck. NSLog( @ "Unable to determine installation directory. Please move the executable into the '%@' installation directory or add a '%@' key in the 'WolfDedicated' defaults domain.", appName, installationPathKey, [[NSBundle mainBundle] bundleIdentifier] ); break; // Sys_Quit(); // exit(1); #else selectButton = @ "Select Retail Installation..."; result = NSRunAlertPanel( demoAppName, @ "You need to select the installation directory for %@ (not any directory inside of it -- the installation directory itself).", selectButton, @ "Quit", nil, appName ); switch ( result ) { case NSAlertDefaultReturn: break; default: Sys_Quit(); break; } openPanel = [NSOpenPanel openPanel]; [openPanel setAllowsMultipleSelection : NO]; [openPanel setCanChooseDirectories : YES]; [openPanel setCanChooseFiles : NO]; result = [openPanel runModalForDirectory:nil file:nil]; if ( result == NSOKButton ) { NSArray *filenames; filenames = [openPanel filenames]; if ([filenames count] == 1 ) { installationPath = [filenames objectAtIndex:0]; [[NSUserDefaults standardUserDefaults] setObject : installationPath forKey : installationPathKey]; [[NSUserDefaults standardUserDefaults] synchronize]; } } #endif } // Create the application support directory if it doesn't exist already do { NSArray *results; NSString *libraryPath, *homePath, *filePath; NSDictionary *attributes; results = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSUserDomainMask, YES ); if ( ![results count] ) { break; } libraryPath = [results objectAtIndex: 0]; homePath = [libraryPath stringByAppendingPathComponent: @ "Application Support"]; homePath = [homePath stringByAppendingPathComponent: appName]; filePath = [homePath stringByAppendingPathComponent: @ "foo"]; attributes = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithUnsignedInt: 0750], NSFilePosixPermissions, nil]; NS_DURING { Sys_CreatePathToFile( filePath, attributes ); Sys_SetDefaultHomePath([homePath fileSystemRepresentation] ); } NS_HANDLER { NSLog( @ "Exception: %@", localException ); #ifndef DEDICATED NSRunAlertPanel( nil, @ "Unable to create '%@'. Please make sure that you have permission to write to this folder and re-run the game.", @ "OK", nil, nil, homePath ); #endif Sys_Quit(); } NS_ENDHANDLER; } while ( 0 ); // Provoke the CD scanning code into looking up the CD. Sys_CheckCD(); // Let the filesystem know where our local install is Sys_SetDefaultInstallPath([installationPath cString] ); // merge the command line, this is kinda silly for ( commandLineLength = 1, argumentIndex = 1; argumentIndex < argc; argumentIndex++ ) commandLineLength += strlen( argv[argumentIndex] ) + 1; cmdline = malloc( commandLineLength ); *cmdline = '\0'; for ( argumentIndex = 1; argumentIndex < argc; argumentIndex++ ) { if ( argumentIndex > 1 ) { strcat( cmdline, " " ); } strcat( cmdline, argv[argumentIndex] ); } Com_Init( cmdline ); #ifndef DEDICATED [NSApp activateIgnoringOtherApps : YES]; #endif while ( 1 ) { Com_Frame(); // We should think about doing this less frequently than every frame [pool release]; pool = [[NSAutoreleasePool alloc] init]; } [pool release]; } @end // Creates any directories needed to be able to create a file at the specified path. Raises an exception on failure. static void Sys_CreatePathToFile( NSString *path, NSDictionary *attributes ) { NSArray *pathComponents; unsigned int dirIndex, dirCount; unsigned int startingIndex; NSFileManager *manager; manager = [NSFileManager defaultManager]; pathComponents = [path pathComponents]; dirCount = [pathComponents count] - 1; startingIndex = 0; for ( dirIndex = startingIndex; dirIndex < dirCount; dirIndex++ ) { NSString *partialPath; BOOL fileExists; partialPath = [NSString pathWithComponents:[pathComponents subarrayWithRange:NSMakeRange( 0, dirIndex + 1 )]]; // Don't use the 'fileExistsAtPath:isDirectory:' version since it doesn't traverse symlinks fileExists = [manager fileExistsAtPath:partialPath]; if ( !fileExists ) { if ( ![manager createDirectoryAtPath : partialPath attributes : attributes] ) { [NSException raise : NSGenericException format : @ "Unable to create a directory at path: %@", partialPath]; } } else { NSDictionary *attributes; attributes = [manager fileAttributesAtPath:partialPath traverseLink:YES]; if ( ![[attributes objectForKey : NSFileType] isEqualToString : NSFileTypeDirectory] ) { [NSException raise : NSGenericException format : @ "Unable to write to path \"%@\" because \"%@\" is not a directory", path, partialPath]; } } } }