]> Dogcows Code - chaz/thecheat/blobdiff - CheatDocument.m
The Cheat 1.2
[chaz/thecheat] / CheatDocument.m
diff --git a/CheatDocument.m b/CheatDocument.m
new file mode 100644 (file)
index 0000000..b058d74
--- /dev/null
@@ -0,0 +1,1287 @@
+
+// **********************************************************************
+// The Cheat - A universal game cheater for Mac OS X
+// (C) 2003-2005 Chaz McGarvey (BrokenZipper)
+// 
+// This program 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 1, or (at your option)
+// any later version.
+// 
+// This program 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 this program; if not, write to the Free Software
+// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+// 
+
+#import "CheatDocument.h"
+
+
+// GLOBALS
+// service browsing globals
+unsigned static _tc_document_count = 0;
+NSNetServiceBrowser static *_tc_service_browser = nil;
+NSMutableArray static *_tc_cheat_services = nil;
+// global target
+Process static *_tc_target = nil;
+
+
+@interface CheatDocument ( PrivateAPI )
+
+// mode switching
+- (void)_switchTo:(NSView *)destination from:(NSView *)source;
+// using the service browser
++ (void)_documentCreated;
++ (void)_documentDestroyed;
+// service addition/removal
+- (void)_cheatServiceFound:(NSNotification *)note;
+- (void)_cheatServiceRemoved:(NSNotification *)note;
+// interface
+- (void)_setupInitialInterface;
+// notifications
+- (void)_displayValuesPrefChanged:(NSNotification *)note;
+- (void)_windowOnTopPrefChanged:(NSNotification *)note;
+- (void)_hitsDisplayedPrefChanged:(NSNotification *)note;
+
+@end
+
+@interface NSTableView ( PrivateAPI )
+
+- (NSRange)_rowsInRectAssumingRowsCoverVisible:(NSRect)rect;
+
+@end
+
+
+@implementation CheatDocument
+
+
+// #############################################################################
+#pragma mark Initialization
+// #############################################################################
+
+- (id)init // designated
+{
+       if ( self = [super init] )
+       {
+               NSNotificationCenter *nc= [NSNotificationCenter defaultCenter];
+               
+               ChazLog( @"init doc %X", self );
+               [CheatDocument _documentCreated];
+               
+               // register for service change notifications
+               [nc addObserver:self selector:@selector(_cheatServiceFound:) name:TCServiceFoundNote object:nil];
+               [nc addObserver:self selector:@selector(_cheatServiceRemoved:) name:TCServiceRemovedNote object:nil];
+               
+               _cheatData = [[CheatData alloc] init];
+               _searchData = [[SearchData alloc] init];
+               
+               // show search mode when documents are first created
+               _connectsOnOpen = YES;
+               [self setMode:TCSearchMode];
+       }
+       return self;
+}
+
+- (id)initWithContentsOfFile:(NSString *)fileName ofType:(NSString *)docType
+{
+       if ( self = [super initWithContentsOfFile:fileName ofType:docType] )
+       {
+               // if document opened from a file, show cheat mode by default
+               [self setMode:TCCheatMode];
+       }
+       return self;
+}
+
+- (id)initWithContentsOfURL:(NSURL *)aURL ofType:(NSString *)docType
+{
+       if ( self = [super initWithContentsOfURL:aURL ofType:docType] )
+       {
+               // if document opened from a URL, show cheat mode by default
+               [self setMode:TCCheatMode];
+       }
+       return self;
+}
+
+- (void)dealloc
+{
+       ChazLog( @"dealloc doc %X", self );
+       
+       // unregister observers
+       [(NSNotificationCenter *)[NSNotificationCenter defaultCenter] removeObserver:self];
+       
+       [_cheater setDelegate:nil];
+       [self disconnectFromCheater];
+       
+       [_cheatData release];
+       [_searchData release];
+       
+       [_serverObject release];
+       [_process release];
+       
+       // release the fade if one is occuring
+       [_fadeView removeFromSuperview];
+       [_fadeView release];
+       
+       [CheatDocument _documentDestroyed];
+       
+       [super dealloc];
+}
+
+
+// #############################################################################
+#pragma mark Nib Loading
+// #############################################################################
+
+- (NSString *)windowNibName
+{
+    return @"CheatDocument";
+}
+
+- (void)windowControllerDidLoadNib:(NSWindowController *)aController
+{
+       [super windowControllerDidLoadNib:aController];
+       
+       NSNotificationCenter *nc= [NSNotificationCenter defaultCenter];
+       
+       // register for app launch/quit notifications
+       [nc addObserver:self selector:@selector(_displayValuesPrefChanged:) name:TCDisplayValuesChangedNote object:nil];
+       [nc addObserver:self selector:@selector(_windowOnTopPrefChanged:) name:TCWindowsOnTopChangedNote object:nil];
+       [nc addObserver:self selector:@selector(_hitsDisplayedPrefChanged:) name:TCHitsDisplayedChangedNote object:nil];
+       
+       // setup window frame saving
+       [ibWindow useOptimizedDrawing:YES];
+       [ibWindow setFrameAutosaveName:@"TCCheatWindow"];
+       
+       // set options
+       if ( [[NSUserDefaults standardUserDefaults] boolForKey:TCWindowsOnTopPref] )
+       {
+               [ibWindow setLevel:NSPopUpMenuWindowLevel];
+       }
+
+       // display one of the modes
+       if ( _mode == TCCheatMode ) {
+               [self _switchTo:ibCheatContentView from:ibPlaceView];
+       }
+       else if ( _mode == TCSearchMode ) {
+               [self _switchTo:ibSearchContentView from:ibPlaceView];
+       }
+       
+       // configure the initial interface
+       [self _setupInitialInterface];
+       
+       // update interface
+       [ibStatusText setDefaultStatus:[self defaultStatusString]];
+       [self updateInterface];
+       
+       // automatically connect to the local cheater
+       if ( _connectsOnOpen ) {
+               [self ibSetLocalCheater:nil];
+       }
+       
+       ChazLog( @"superview: %@", [[ibSearchVariableTable superview] superview] );
+}
+
+
+// #############################################################################
+#pragma mark Handling Files
+// #############################################################################
+
+- (NSData *)dataRepresentationOfType:(NSString *)type
+{
+    return [NSArchiver archivedDataWithRootObject:_cheatData];
+}
+
+- (BOOL)loadDataRepresentation:(NSData *)data ofType:(NSString *)type
+{
+       [_cheatData release];
+       _cheatData = nil;
+       
+       if ( [type isEqualToString:@"Cheat Document"] ) {
+               NS_DURING
+                       _cheatData = [[NSUnarchiver unarchiveObjectWithData:data] retain];
+               NS_HANDLER
+                       if ( !_cheatData ) {
+                               // alert the user of the unparsable file
+                               NSBeep();
+                               NSRunAlertPanel( @"The Cheat can't read file.", @"The file \"%@\" can't be read.  It is probably not a cheat file, or it may be corrupted.", @"OK", nil, nil, [self fileName] );
+                               return NO;
+                       }
+               NS_ENDHANDLER
+       }
+       
+       [self updateInterface];
+       
+    return YES;
+}
+
+
+// #############################################################################
+#pragma mark Service Finding
+// #############################################################################
+
++ (NSArray *)cheatServices
+{
+       return [NSArray arrayWithArray:_tc_cheat_services];
+}
+
+
+- (void)_cheatServiceFound:(NSNotification *)note
+{
+       NSMenuItem *menuItem;
+       NSNetService *item = [note object];
+       
+       // add the newly found service to the server popup
+       menuItem = [[NSMenuItem alloc] init];
+       [menuItem setTarget:self];
+       [menuItem setAction:@selector(ibSetRemoteCheater:)];
+       [menuItem setTitle:[item name]];
+       [menuItem setRepresentedObject:item];
+       [self addServer:menuItem];
+       [menuItem release];
+}
+
+- (void)_cheatServiceRemoved:(NSNotification *)note
+{
+       NSNetService *item = [note object];
+       
+       // remove the service from the menu
+       [self removeServerWithObject:item];
+}
+
+
+// using the service browser
++ (void)_documentCreated
+{
+       _tc_document_count++;
+       
+       if ( _tc_document_count == 1 ) {
+               // first document created, so start the service browser
+               [_tc_service_browser stop];
+               [_tc_cheat_services release];
+               // create and setup the browser
+               _tc_service_browser = [[NSNetServiceBrowser alloc] init];
+               [_tc_service_browser setDelegate:self];
+               [_tc_service_browser searchForServicesOfType:@"_cheat._tcp." inDomain:@""];
+               // create the service array
+               _tc_cheat_services = [[NSMutableArray alloc] init];
+       }
+}
+
++ (void)_documentDestroyed
+{
+       _tc_document_count--;
+       
+       if ( _tc_document_count == 0 ) {
+               // last document destroyed, so stop the service browser
+               [_tc_service_browser stop];
+               [_tc_cheat_services release];
+               // set the globals to nil for safety
+               _tc_service_browser = nil;
+               _tc_cheat_services = nil;
+       }
+}
+
+
+// NSNetServiceBrowser delegate methods
++ (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)browser
+{
+       ChazLog( @"service browser will search" );
+}
+
++ (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)browser
+{
+       // if the browser stops we assume it needs to die.
+       ChazLog( @"service browser did stop search" );
+       [browser release];
+}
+
++ (void)netServiceBrowser:(NSNetServiceBrowser *)browser didNotSearch:(NSDictionary *)errorDict
+{
+       ChazLog( @"service browser failed with error code: %i", [[errorDict objectForKey:NSNetServicesErrorCode] intValue] );
+}
+
++ (void)netServiceBrowser:(NSNetServiceBrowser *)browser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
+{
+       ChazLog( @"service browser found service: %@", [aNetService name] );
+       
+       // ignore if this is the local server.
+       if ( [[(AppController *)NSApp cheatServer] isListening] &&
+                [[aNetService name] isEqualToString:[[NSUserDefaults standardUserDefaults] objectForKey:TCBroadcastNamePref]] ) {
+               return;
+       }
+       
+       [_tc_cheat_services addObject:aNetService];
+       // send a notification for the new service
+       [[NSNotificationCenter defaultCenter] postNotificationName:TCServiceFoundNote object:aNetService];
+}
+
++ (void)netServiceBrowser:(NSNetServiceBrowser *)browser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
+{
+       ChazLog( @"service browser removed service: %@", [aNetService name] );
+       
+       [_tc_cheat_services removeObject:aNetService];
+       // send a notification for the new service
+       [[NSNotificationCenter defaultCenter] postNotificationName:TCServiceRemovedNote object:aNetService];
+}
+
+
+// #############################################################################
+#pragma mark Changing Mode
+// #############################################################################
+
+- (void)setMode:(TCDocumentMode)mode
+{
+       // if the nib isn't loaded, change the mode
+       if ( !ibWindow ) {
+               _mode = mode;
+       }
+}
+
+
+- (void)switchToCheatMode
+{
+       NSResponder *responder = [ibWindow firstResponder];
+       
+       if ( [responder isKindOfClass:[NSText class]] ) {
+               /* Since text views et al. make the field editor the first
+               responder, you have to take its delegate since that will
+               be set to the actual text view. */
+               responder = [(NSText *)responder delegate];
+       }
+       
+       if ( _mode == TCCheatMode ) {
+               return;
+       }
+       _mode = TCCheatMode;
+       [self _switchTo:ibCheatContentView from:ibSearchContentView];
+       
+       // update the next key view
+       [ibProcessPopup setNextKeyView:ibCheatVariableTable];
+       // update current key view
+       if ( !_lastResponder || _lastResponder == ibWindow ) {
+               // set default responder
+               [ibWindow makeFirstResponder:ibCheatVariableTable];
+       }
+       else {
+               [ibWindow makeFirstResponder:_lastResponder];
+       }
+       _lastResponder = responder;
+}
+
+- (void)switchToSearchMode
+{
+       NSResponder *responder = [ibWindow firstResponder];
+       
+       if ( [responder isKindOfClass:[NSText class]] ) {
+               responder = [(NSText *)responder delegate];
+       }
+       
+       if ( _mode == TCSearchMode ) {
+               return;
+       }
+       _mode = TCSearchMode;
+       [self _switchTo:ibSearchContentView from:ibCheatContentView];
+       
+       // update the next key view
+       [ibProcessPopup setNextKeyView:ibSearchTypePopup];
+       // update current key view
+       if ( !_lastResponder || _lastResponder == ibWindow ) {
+               [ibWindow makeFirstResponder:ibSearchValueField];
+       }
+       else {
+               [ibWindow makeFirstResponder:_lastResponder];
+       }
+       _lastResponder = responder;
+}
+
+
+- (void)_switchTo:(NSView *)destination from:(NSView *)source
+{
+       NSView *contentView = [ibWindow contentView];
+       NSRect frame = [source frame];
+       NSImage *fadeImage = nil;
+       
+       if ( gFadeAnimationDuration != 0.0 && [source lockFocusIfCanDraw] ) {
+               // draw the view to the representation
+               NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:[source bounds]];
+               [source unlockFocus];
+               
+               // create the image object
+               fadeImage = [[NSImage alloc] initWithSize:frame.size];
+               [fadeImage addRepresentation:imageRep];
+               
+               if ( _fadeView ) {
+                       // remove the old fade view
+                       [_fadeView removeFromSuperview];
+                       [_fadeView release];
+               }
+               
+               // create the new fade view and start the fade
+               _fadeView = [[FadeView alloc] initWithFrame:frame];
+               [_fadeView setAutoresizingMask:[source autoresizingMask]];
+               [_fadeView setDelegate:self];
+               [_fadeView setImage:fadeImage];
+               [_fadeView setFadeDuration:gFadeAnimationDuration];
+               [contentView addSubview:_fadeView];
+               [_fadeView startFadeAnimation];
+               
+               [fadeImage release];
+       }
+       
+       // update view size of incoming view
+       [destination setFrame:frame];
+       // replace the views
+       [contentView replaceSubview:source with:destination];
+}
+
+
+// FadeView Delegate
+- (void)fadeViewFinishedAnimation:(FadeView *)theView
+{
+       [_fadeView removeFromSuperview];
+       [_fadeView release];
+       _fadeView = nil;
+}
+
+
+// #############################################################################
+#pragma mark Accessors
+// #############################################################################
+
+- (NSString *)defaultStatusString
+{
+       if ( !_cheater ) {
+               return @"Not Connected";
+       }
+       return [NSString stringWithFormat:@"Connected to %@.", [_cheater hostAddress]];
+}
+
+- (BOOL)isLoadedFromFile
+{
+       return ([self fileName] != nil);
+}
+
+
+- (void)addServer:(NSMenuItem *)item
+{
+       NSMenu *serverMenu = [ibServerPopup menu];
+       
+       if ( item ) {
+               if ( [serverMenu numberOfItems] <= 2 ) {
+                       // separator line
+                       [serverMenu addItem:[NSMenuItem separatorItem]];
+               }
+               [serverMenu addItem:item];
+       }
+}
+
+- (void)removeServerWithObject:(id)serverObject
+{
+       NSMenu *serverMenu = [ibServerPopup menu];
+       
+       if ( serverObject ) {
+               [serverMenu removeItemWithRepresentedObject:serverObject];
+               if ( [serverMenu numberOfItems] == 3 ) {
+                       // separator line
+                       [serverMenu removeItemAtIndex:2];
+               }
+       }
+}
+
+
+// #############################################################################
+#pragma mark Interface
+// #############################################################################
+
+- (void)_setupInitialInterface
+{
+       NSMenu *serverMenu;
+       NSMenuItem *menuItem;
+       
+       NSArray *cheatServices;
+       unsigned i, len;
+       
+       // create and set the server popup menu
+       serverMenu = [[NSMenu alloc] init];
+       [serverMenu setAutoenablesItems:YES];
+       // add menu items
+       // local connection item
+       menuItem = [[NSMenuItem alloc] init];
+       [menuItem setTarget:self];
+       [menuItem setAction:@selector(ibSetLocalCheater:)];
+       [menuItem setTitle:@"On This Computer"];
+       [menuItem setRepresentedObject:[NSNull null]];
+       [serverMenu addItem:menuItem];
+       [menuItem release];
+       // arbitrary connection item
+       menuItem = [[NSMenuItem alloc] init];
+       [menuItem setTarget:self];
+       [menuItem setAction:@selector(ibRunCustomServerSheet:)];
+       [menuItem setTitle:[NSString stringWithFormat:@"Other Server%C", 0x2026]];
+       [menuItem setKeyEquivalent:@"k"];
+       [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
+       [serverMenu addItem:menuItem];
+       [menuItem release];
+       // set the menu
+       [ibServerPopup setMenu:serverMenu];
+       [serverMenu release];
+       
+       // add current list of rendezvous services
+       cheatServices = [CheatDocument cheatServices];
+       len = [cheatServices count];
+       for ( i = 0; i < len; i++ ) {
+               NSNetService *item = [cheatServices objectAtIndex:i];
+               menuItem = [[NSMenuItem alloc] init];
+               [menuItem setTarget:self];
+               [menuItem setAction:@selector(ibSetRemoteCheater:)];
+               [menuItem setTitle:[item name]];
+               [menuItem setRepresentedObject:item];
+               [self addServer:menuItem];
+               [menuItem release];
+       }
+       
+       [ibSearchVariableTable setDoubleAction:@selector(ibAddSearchVariable:)];
+       [ibSearchVariableTable setCanDelete:NO];
+       
+       // BUG: for some reason IB doesn't like to set the default selection
+       // for an NSMatrix to anything but the first cell, so set this explicitly.
+       [ibSearchValueUsedMatrix selectCellWithTag:TCGivenValue];
+       
+       // we use undoing/redoing for reverting search results
+       [self setHasUndoManager:NO];
+}
+
+- (void)updateInterface
+{
+       if ( _cheatData )
+       {
+               // if there is cheat data, fill in the data information
+               [ibWindow setTitle:[self displayName]];
+               if ( [_cheatData process] ) {
+                       if ( [[_cheatData cheatInfo] isEqualToString:@""] ) {
+                               [ibCheatInfoText setStringValue:[NSString stringWithFormat:@"%@ %@",
+                                       [_cheatData gameName],
+                                       [_cheatData gameVersion]]];
+                       }
+                       else {
+                               [ibCheatInfoText setStringValue:[NSString stringWithFormat:@"%@ %@ - %@",
+                                       [_cheatData gameName],
+                                       [_cheatData gameVersion],
+                                       [_cheatData cheatInfo]]];
+                       }
+               }
+               else {
+                       [ibCheatInfoText setStringValue:[_cheatData cheatInfo]];
+               }
+               
+               [ibCheatRepeatButton setState:[_cheatData repeats]];
+               [ibCheatRepeatField setDoubleValue:[_cheatData repeatInterval]];
+       }
+       
+       // if we're connected...
+       if ( _cheater )
+       {
+               if ( _status == TCIdleStatus )
+               {
+                       // WINDOW
+                       [ibServerPopup setEnabled:YES];
+                       [ibProcessPopup setEnabled:YES];
+                       // SEARCH MODE
+                       [ibSearchValueUsedMatrix setEnabled:YES];
+                       if ( [_searchData hasSearchedOnce] ) {
+                               [ibSearchTypePopup setEnabled:NO];
+                               [ibSearchIntegerSignMatrix setEnabled:NO];
+                               [[ibSearchValueUsedMatrix cellWithTag:TCLastValue] setEnabled:YES];
+                               if ( [_searchData valueUsed] == TCGivenValue ) {
+                                       [ibSearchValueField setEnabled:YES];
+                               }
+                               else {
+                                       [ibSearchValueField setEnabled:NO];
+                               }
+                               [ibSearchClearButton setEnabled:YES];
+                               [ibSearchVariableTable setEnabled:YES];
+                               int selectedRows = [ibSearchVariableTable numberOfSelectedRows];
+                               if ( selectedRows > 0 ) {
+                                       [ibSearchVariableButton setEnabled:YES];
+                               }
+                               else {
+                                       [ibSearchVariableButton setEnabled:NO];
+                               }
+                       }
+                       else {
+                               [ibSearchTypePopup setEnabled:YES];
+                               if ( [_searchData isTypeInteger] ) {
+                                       [ibSearchIntegerSignMatrix setEnabled:YES];
+                               }
+                               else {
+                                       [ibSearchIntegerSignMatrix setEnabled:NO];
+                               }
+                               [[ibSearchValueUsedMatrix cellWithTag:TCLastValue] setEnabled:NO];
+                               [ibSearchValueUsedMatrix selectCellWithTag:[_searchData valueUsed]];
+                               [ibSearchValueField setEnabled:YES];
+                               [ibSearchClearButton setEnabled:NO];
+                               [ibSearchVariableTable setEnabled:NO];
+                               [ibSearchVariableButton setEnabled:NO];
+                       }
+                       if ( [_searchData variableType] != TCString ) {
+                               [ibSearchOperatorPopup setEnabled:YES];
+                       }
+                       else {
+                               [ibSearchOperatorPopup setEnabled:NO];
+                       }
+                       [ibSearchButton setTitle:@"Search"];
+                       [ibSearchButton setAction:@selector(ibSearch:)];
+                       [ibSearchButton setKeyEquivalent:@""];
+                       [ibSearchButton setEnabled:YES];
+                       // CHEAT MODE
+                       [ibCheatVariableTable setEnabled:YES];
+                       [ibCheatRepeatButton setEnabled:YES];
+                       [ibCheatRepeatAuxText setTextColor:[NSColor controlTextColor]];
+                       [ibCheatRepeatField setEnabled:[_cheatData repeats]];
+                       [ibCheatButton setTitle:@"Apply Cheat"];
+                       [ibCheatButton setAction:@selector(ibCheat:)];
+                       [ibCheatButton setKeyEquivalent:@"\r"];
+                       if ( [_cheatData enabledVariableCount] > 0 ) {
+                               [ibCheatButton setEnabled:YES];
+                               if ( [[_cheatData process] sameApplicationAs:_process] ) {
+                                       [ibCheatButton setKeyEquivalent:@"\r"];
+                               }
+                               else {
+                                       [ibCheatButton setKeyEquivalent:@""];
+                               }
+                       }
+                       else {
+                               [ibCheatButton setEnabled:NO];
+                       }
+               }
+               else
+               {
+                       // WINDOW
+                       [ibServerPopup setEnabled:NO];
+                       [ibProcessPopup setEnabled:NO];
+                       // SEARCH MODE
+                       [ibSearchTypePopup setEnabled:NO];
+                       [ibSearchIntegerSignMatrix setEnabled:NO];
+                       [ibSearchOperatorPopup setEnabled:NO];
+                       [ibSearchValueUsedMatrix setEnabled:NO];
+                       [ibSearchValueField setEnabled:NO];
+                       [ibSearchClearButton setEnabled:NO];
+                       [ibSearchVariableTable setEnabled:NO];
+                       [ibSearchVariableButton setEnabled:NO];
+                       // CHEAT MODE
+                       [ibCheatVariableTable setEnabled:NO];
+                       [ibCheatRepeatButton setEnabled:NO];
+                       [ibCheatRepeatAuxText setTextColor:[NSColor disabledControlTextColor]];
+                       [ibCheatRepeatField setEnabled:NO];
+                       
+                       if ( _status == TCSearchingStatus ) {
+                               [ibSearchButton setTitle:@"Cancel"];
+                               [ibSearchButton setAction:@selector(ibCancelSearch:)];
+                               [ibSearchButton setKeyEquivalent:@"\E"];
+                               [ibSearchButton setEnabled:!_isCancelingTask];
+                               [ibCheatButton setTitle:@"Apply Cheat"];
+                               [ibCheatButton setAction:@selector(ibCheat:)];
+                               if ( [[_cheatData process] sameApplicationAs:_process] ) {
+                                       [ibCheatButton setKeyEquivalent:@"\r"];
+                               }
+                               else {
+                                       [ibCheatButton setKeyEquivalent:@""];
+                               }
+                               [ibCheatButton setEnabled:NO];
+                       }
+                       else if ( _status == TCCheatingStatus ) {
+                               [ibSearchButton setTitle:@"Search"];
+                               [ibSearchButton setAction:@selector(ibSearch:)];
+                               [ibSearchButton setKeyEquivalent:@""];
+                               [ibSearchButton setEnabled:NO];
+                               [ibCheatButton setTitle:@"Stop Cheat"];
+                               [ibCheatButton setAction:@selector(ibStopCheat:)];
+                               [ibCheatButton setKeyEquivalent:@"\E"];
+                               [ibCheatButton setEnabled:!_isCancelingTask];
+                       }
+                       else {
+                               [ibSearchButton setTitle:@"Search"];
+                               [ibSearchButton setAction:@selector(ibSearch:)];
+                               [ibSearchButton setKeyEquivalent:@""];
+                               [ibSearchButton setEnabled:NO];
+                               [ibCheatButton setTitle:@"Apply Cheat"];
+                               [ibCheatButton setAction:@selector(ibCheat:)];
+                               if ( [[_cheatData process] sameApplicationAs:_process] ) {
+                                       [ibCheatButton setKeyEquivalent:@"\r"];
+                               }
+                               else {
+                                       [ibCheatButton setKeyEquivalent:@""];
+                               }
+                               [ibCheatButton setEnabled:NO];
+                       }
+               }
+       }
+       else
+       {
+               // WINDOW
+               [ibServerPopup setEnabled:YES];
+               [ibProcessPopup setEnabled:NO];
+               // SEARCH MODE
+               [ibSearchTypePopup setEnabled:NO];
+               [ibSearchIntegerSignMatrix setEnabled:NO];
+               [ibSearchOperatorPopup setEnabled:NO];
+               [ibSearchValueUsedMatrix setEnabled:NO];
+               [ibSearchValueField setEnabled:NO];
+               [ibSearchButton setEnabled:NO];
+               [ibSearchClearButton setEnabled:NO];
+               [ibSearchVariableTable setEnabled:NO];
+               [ibSearchVariableButton setEnabled:NO];
+               // CHEAT MODE
+               [ibCheatVariableTable setEnabled:NO];
+               [ibCheatRepeatButton setEnabled:NO];
+               [ibCheatRepeatAuxText setTextColor:[NSColor disabledControlTextColor]];
+               [ibCheatRepeatField setEnabled:NO];
+               [ibCheatButton setEnabled:NO];
+       }
+}
+
+
+- (void)setActualResults:(unsigned)count
+{
+       unsigned recieved = [_searchData numberOfResults];
+       
+       if ( count == 0 ) {
+               [ibSearchVariableTable setToolTip:@""];
+       }
+       else if ( recieved == count ) {
+               if ( count == 1 ) {
+                       [ibSearchVariableTable setToolTip:[NSString stringWithFormat:@"Displaying one result."]];
+               }
+               else {
+                       [ibSearchVariableTable setToolTip:[NSString stringWithFormat:@"Displaying %i results.", count]];
+               }
+       }
+       else if ( recieved < count ) {
+               [ibSearchVariableTable setToolTip:[NSString stringWithFormat:@"Displaying %i of %i results.", recieved, count]];
+       }
+}
+
+
+- (NSString *)displayName
+{
+       // override the default window title if there is a custom one
+       NSString *title = [_cheatData windowTitle];
+       
+       if ( !title || [title isEqualToString:@""] ) {
+               return [super displayName];
+       }
+       return title;
+}
+
+- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
+{
+       //ChazLog( @"validate menuitem: %@", [menuItem title] );
+       
+       // the undo/redo
+       if ( [menuItem action] == @selector(ibUndo:) ) {
+               if ( [_searchData undoesLeft] > 0 ) {
+                       return YES;
+               }
+               else {
+                       return NO;
+               }
+       }
+       if ( [menuItem action] == @selector(ibRedo:) ) {
+               if ( [_searchData redoesLeft] > 0 ) {
+                       return YES;
+               }
+               else {
+                       return NO;
+               }
+       }
+       
+       // the add variables items
+       if ( [menuItem action] == @selector(ibAddCheatVariable:) && _status == TCCheatingStatus ) {
+               return NO;
+       }
+
+       // the 'pause' menu item
+       if ( [menuItem tag] == 1000 ) {
+               if ( !_cheater ) {
+                       return NO;
+               }
+               if ( _isTargetPaused ) {
+                       [menuItem setTitle:@"Resume Target"];
+                       [menuItem setAction:@selector(ibResumeTarget:)];
+               }
+               else {
+                       [menuItem setTitle:@"Pause Target"];
+                       [menuItem setAction:@selector(ibPauseTarget:)];
+               }
+       }
+       
+       // the 'memory dump' menu item
+       else if ( [menuItem tag] == 1001 ) {
+               if ( !_cheater || _status != TCIdleStatus ) {
+                       return NO;
+               }
+       }
+       // the 'mode switch' menu item
+       else if ( [menuItem tag] == 1002 ) {
+               if ( _mode == TCSearchMode ) {
+                       [menuItem setTitle:@"Show Cheat Mode"];
+               }
+               else /* _mode == TCCheatMode */ {
+                       [menuItem setTitle:@"Show Search Mode"];
+               }
+       }
+       // the 'edit variables' menu item
+       else if ( [menuItem tag] == 1003 ) {
+               return (_mode == TCCheatMode && [ibCheatVariableTable selectedRow] != -1);
+       }
+       // the 'clear search' menu item
+       else if ( [menuItem tag] == 1004 ) {
+               return [ibSearchClearButton isEnabled];
+       }
+       // the cancel menu item
+       else if ( [menuItem tag] == 1005 ) {
+               if ( !_cheater || _isCancelingTask ) {
+                       return NO;
+               }
+               if ( _status == TCSearchingStatus ) {
+                       [menuItem setTitle:@"Cancel Search"];
+                       [menuItem setAction:@selector(ibCancelSearch:)];
+               }
+               else if ( _status == TCCheatingStatus ) {
+                       [menuItem setTitle:@"Stop Cheat"];
+                       [menuItem setAction:@selector(ibStopCheat:)];
+               }
+               else if ( _status == TCDumpingStatus ) {
+                       [menuItem setTitle:@"Cancel Dump"];
+                       [menuItem setAction:@selector(ibCancelDump:)];
+               }
+               else {
+                       return NO;
+               }
+       }
+       
+       return [super validateMenuItem:menuItem];
+}
+
+
+- (void)setDocumentChanged
+{
+       // only count document changes if there are variables
+       // and if the pref is set
+       if ( [[NSUserDefaults standardUserDefaults] boolForKey:TCAskForSavePref] &&
+                ([_cheatData variableCount] > 0 || [self isLoadedFromFile]) ) {
+               [self updateChangeCount:NSChangeDone];
+       }
+}
+
+
+- (int)numberOfRowsInTableView:(NSTableView *)aTableView
+{
+       if ( aTableView == ibCheatVariableTable ) {
+               return [_cheatData variableCount];
+       }
+       else if ( aTableView == ibSearchVariableTable ) {
+               return [_searchData numberOfResults];
+       }
+       return 0;
+}
+
+- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
+{
+       NSString *identifier = [aTableColumn identifier];
+       
+       if ( aTableView == ibCheatVariableTable ) {
+               Variable *variable = [_cheatData variableAtIndex:rowIndex];
+               
+               if ( [identifier isEqualToString:@"enable"] ) {
+                       return [NSNumber numberWithBool:[variable isEnabled]];
+               }
+               else if ( [identifier isEqualToString:@"variable"] ) {
+                       return [variable typeString];
+               }
+               else if ( [identifier isEqualToString:@"address"] ) {
+                       return [variable addressString];
+               }
+               else if ( [identifier isEqualToString:@"value"] ) {
+                       return [variable stringValue];
+               }
+       }
+       else if ( aTableView == ibSearchVariableTable ) {
+               return [_searchData stringForRow:rowIndex];
+       }
+       return @"";
+}
+
+- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
+{
+       NSString *identifier = [aTableColumn identifier];
+       
+       if ( aTableView == ibCheatVariableTable ) {
+               Variable *variable = [_cheatData variableAtIndex:rowIndex];
+               
+               if ( [identifier isEqualToString:@"address"] ) {
+                       if ( [variable setAddressString:anObject] ) {
+                               [self setDocumentChanged];
+                       }
+               }
+               else if ( [identifier isEqualToString:@"value"] ) {
+                       if ( [variable setStringValue:anObject] ) {
+                               [self setDocumentChanged];
+                       }
+               }
+       }
+}
+
+- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
+{
+       NSTableView *aTableView = [aNotification object];
+       
+       if ( aTableView == ibSearchVariableTable ) {
+               int selectedRows = [aTableView numberOfSelectedRows];
+               if ( selectedRows > 1 ) {
+                       [ibSearchVariableButton setTitle:@"Add Variables"];
+               }
+               else if (  selectedRows == 1 ) {
+                       [ibSearchVariableButton setTitle:@"Add Variable"];
+               }
+       }
+}
+
+
+- (BOOL)tableViewDidReceiveEnterKey:(NSTableView *)tableView
+{
+       if ( tableView == ibSearchVariableTable ) {
+               [ibSearchVariableButton performClick:nil];
+               return YES;
+       }
+       return NO;
+}
+
+- (BOOL)tableViewDidReceiveSpaceKey:(NSTableView *)tableView
+{
+       if ( tableView == ibCheatVariableTable ) {
+               [self ibSetVariableEnabled:nil];
+               return YES;
+       }
+       return NO;
+}
+
+- (NSString *)tableViewPasteboardType:(NSTableView *)tableView
+{
+       return @"TCVariablePboardType";
+}
+
+- (NSData *)tableView:(NSTableView *)tableView copyRows:(NSArray *)rows
+{
+       NSMutableArray *vars;
+       int i, top;
+       
+       top = [rows count];
+       vars = [[NSMutableArray alloc] initWithCapacity:top];
+       
+       // add the new variables
+       if ( tableView == ibSearchVariableTable ) {
+               for ( i = 0; i < top; i++ ) {
+                       unsigned index = [[rows objectAtIndex:i] unsignedIntValue];
+                       [vars addObject:[_searchData variableAtIndex:index]];
+               }
+       }
+       else {
+               for ( i = 0; i < top; i++ ) {
+                       unsigned index = [[rows objectAtIndex:i] unsignedIntValue];
+                       [vars addObject:[_cheatData variableAtIndex:index]];
+               }
+       }
+       
+       return [NSArchiver archivedDataWithRootObject:[vars autorelease]];
+}
+
+- (void)tableView:(NSTableView *)tableView pasteRowsWithData:(NSData *)rowData
+{
+       NSArray *vars = [NSUnarchiver unarchiveObjectWithData:rowData];
+       int i, top, lastRow;
+       
+       if ( tableView == ibSearchVariableTable ) {
+               NSBeep();
+               return;
+       }
+       
+       top = [vars count];
+       for ( i = 0; i < top; i++ ) {
+               Variable *var = [vars objectAtIndex:i];
+               [_cheatData addVariable:var];
+       }
+       
+       lastRow = [_cheatData variableCount]-1;
+       [tableView reloadData];
+       if ( MacOSXVersion() >= 0x1030 ) {
+               [tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:lastRow] byExtendingSelection:NO];
+       }
+       else {
+               [tableView selectRow:lastRow byExtendingSelection:NO];
+       }
+       [tableView scrollRowToVisible:lastRow];
+       
+       [self setDocumentChanged];
+       [self updateInterface];
+}
+
+- (void)tableView:(NSTableView *)tableView deleteRows:(NSArray *)rows
+{
+       int i, len;
+       
+       if ( tableView == ibCheatVariableTable ) {
+               len = [rows count];
+               for ( i = len-1; i >= 0; i-- ) {
+                       [_cheatData removeVariableAtIndex:[[rows objectAtIndex:i] unsignedIntValue]];
+               }
+               // reselect the last item if the selection is now invalid
+               len = [_cheatData variableCount] - 1;
+               if ( [tableView selectedRow] > len ) {
+                       if ( MacOSXVersion() >= 0x1030 ) {
+                               [tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:len] byExtendingSelection:NO];
+                       }
+                       else {
+                               [tableView selectRow:len byExtendingSelection:NO];
+                       }
+               }
+               [tableView reloadData];
+               
+               [self setDocumentChanged];
+               [self updateInterface];
+       }
+}
+
+// VariableTable Delegate
+- (void)tableView:(NSTableView *)aTableView didChangeVisibleRows:(NSRange)rows
+{
+       ChazLog( @"new visible rows: %@", NSStringFromRange( rows ) );
+       if ( [_searchData valuesLoaded] ) {
+               [self watchVariables];
+       }
+}
+
+
+// #############################################################################
+#pragma mark Utility
+// #############################################################################
+
++ (void)setGlobalTarget:(Process *)target
+{
+       [target retain];
+       [_tc_target release];
+       _tc_target = target;
+}
+
++ (Process *)globalTarget
+{
+       return _tc_target;
+}
+
+
+- (void)showError:(NSString *)error
+{
+       NSColor *red = [NSColor colorWithCalibratedRed:0.7 green:0.0 blue:0.0 alpha:1.0];
+       NSBeep();
+       [ibStatusText setDefaultStatus:[self defaultStatusString]];
+       [ibStatusText setTemporaryStatus:[NSString stringWithFormat:@"Error: %@", error] color:red duration:7.0];
+}
+
+
+- (BOOL)shouldConnectWithServer:(NSMenuItem *)item
+{
+       id serverObject;
+       
+       if ( _resolvingService ) {
+               // don't connect if a service is being resolved
+               [ibServerPopup selectItemAtIndex:[ibServerPopup indexOfItemWithRepresentedObject:_resolvingService]];
+               return NO;
+       }
+       
+       if ( [item respondsToSelector:@selector(representedObject)] ) {
+               serverObject = [item representedObject];
+       }
+       else {
+               serverObject = [NSNull null];
+       }
+       
+       if ( [_serverObject isEqual:serverObject] ) {
+               // already connected, don't connect
+               return NO;
+       }
+       return YES;
+}
+
+- (void)selectConnectedCheater
+{
+       int index = [ibServerPopup indexOfItemWithRepresentedObject:_serverObject];
+       if ( index != -1 ) {
+               [ibServerPopup selectItemAtIndex:index];
+       }
+}
+
+- (void)connectWithServer:(NSMenuItem *)item
+{
+       id serverObject;
+       
+       if ( [item respondsToSelector:@selector(representedObject)] ) {
+               serverObject = [item representedObject];
+       }
+       else {
+               serverObject = [NSNull null];
+       }
+       
+       // save a reference to the server object
+       [serverObject retain];
+       [_serverObject release];
+       _serverObject = serverObject;
+}
+
+- (void)disconnectFromCheater
+{
+       NSMenu *blankMenu;
+       
+       // don't do anything if we are already disconnected
+       if ( !_cheater ) {
+               return;
+       }
+       
+       _status = TCIdleStatus;
+       
+       // clear the search
+       [_searchData clearResults];
+       [ibSearchVariableTable reloadData];
+       
+       // clear the selected process
+       [_process release];
+       _process = nil;
+       [_serverObject release];
+       _serverObject = nil;
+       
+       if ( ![self isLoadedFromFile] ) {
+               [_cheatData setProcess:nil];
+       }
+       
+       // clear the process menu
+       blankMenu = [[NSMenu alloc] initWithTitle:@""];
+       [blankMenu addItemWithTitle:@"" action:NULL keyEquivalent:@""];
+       [ibProcessPopup setMenu:blankMenu];
+       [blankMenu release];
+       
+       // kill the connection
+       [_cheater disconnect];
+       [_cheater release];
+       _cheater = nil;
+}
+
+
+- (void)setConnectOnOpen:(BOOL)flag
+{
+       _connectsOnOpen = flag;
+}
+
+- (void)connectWithURL:(NSString *)url
+{
+       NSMenu *serverMenu = [ibServerPopup menu];
+       NSURL *theUrl = [NSURL URLWithString:url];
+       
+       NSString *host;
+       int port;
+       
+       NSData *addrData;
+       int indexIfAlreadyExists;
+       
+       host = [theUrl host];
+       port = [[theUrl port] intValue];
+       
+       if ( !host ) {
+               NSBeginInformationalAlertSheet( @"The Cheat can't parse the URL.", @"OK", nil, nil, ibWindow, self, NULL, NULL, NULL,
+                                                                               @"The Cheat can't connect to the server because \"%@\" is not a valid URL.", url );
+               goto FAILURE;
+       }
+       
+       // use default port number
+       if ( !port ) {
+               port = TCDefaultListenPort;
+       }
+       
+       addrData = [MySocket addressWithHost:host port:port];
+       if ( !addrData ) {
+               NSBeginInformationalAlertSheet( @"The Cheat can't find the server.", @"OK", nil, nil, ibWindow, self, NULL, NULL, NULL,
+                                                                               @"The Cheat can't connect to the server \"%@\" because it can't be found.", host );
+               goto FAILURE;
+       }
+       
+       indexIfAlreadyExists = [serverMenu indexOfItemWithRepresentedObject:addrData];
+       if ( indexIfAlreadyExists == -1 ) {
+               NSMenuItem *menuItem;
+               // add the newly found service to the server popup
+               menuItem = [[NSMenuItem alloc] init];
+               [menuItem setTarget:self];
+               [menuItem setAction:@selector(ibSetCustomCheater:)];
+               [menuItem setTitle:[NSString stringWithFormat:@"%@:%i", host, port]];
+               [menuItem setRepresentedObject:addrData];
+               [self addServer:menuItem];
+               // select new item
+               [self ibSetCustomCheater:menuItem];
+               // cleanup
+               [menuItem release];
+       }
+       else {
+               // select matching item
+               [self ibSetCustomCheater:[serverMenu itemAtIndex:indexIfAlreadyExists]];
+       }
+       
+FAILURE:;
+       [self selectConnectedCheater];
+}
+
+
+- (void)watchVariables
+{
+       NSRange range;
+       
+       if ( [[NSUserDefaults standardUserDefaults] boolForKey:TCDisplayValuesPref] ) {
+               float interval = [[NSUserDefaults standardUserDefaults] floatForKey:TCValueUpdatePref];
+               
+               range = [ibSearchVariableTable visibleRows];
+               [_cheater watchVariablesAtIndex:range.location count:range.length interval:interval];
+       }
+       else {
+               [_cheater stopWatchingVariables];
+       }
+}
+
+
+// #############################################################################
+#pragma mark Notifications
+// #############################################################################
+
+- (void)_displayValuesPrefChanged:(NSNotification *)note
+{
+       [self watchVariables];
+}
+
+- (void)_windowOnTopPrefChanged:(NSNotification *)note
+{
+       ChazLog( @"_windowOnTopPrefChanged" );
+       if ( [[NSUserDefaults standardUserDefaults] boolForKey:TCWindowsOnTopPref] ) {
+               [ibWindow setLevel:NSPopUpMenuWindowLevel];
+       }
+       else {
+               [ibWindow setLevel:NSNormalWindowLevel];
+       }
+}
+
+- (void)_hitsDisplayedPrefChanged:(NSNotification *)note
+{
+       // send preferences to the cheater
+       [_cheater limitReturnedResults:[[NSUserDefaults standardUserDefaults] integerForKey:TCHitsDisplayedPref]];
+}
+
+
+@end
This page took 0.045062 seconds and 4 git commands to generate.