X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fthecheat;a=blobdiff_plain;f=CheatDocument.m;fp=CheatDocument.m;h=b058d74b917cd46915872c79429df76100b0a577;hp=0000000000000000000000000000000000000000;hb=d27548f80fe411fda2ee69c74a24eab4292267e9;hpb=e8d51183acdd2410a38dcf8f0efbf7c30cd6c581 diff --git a/CheatDocument.m b/CheatDocument.m new file mode 100644 index 0000000..b058d74 --- /dev/null +++ b/CheatDocument.m @@ -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