2 // **********************************************************************
3 // The Cheat - A universal game cheater for Mac OS X
4 // (C) 2003-2005 Chaz McGarvey (BrokenZipper)
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 1, or (at your option)
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 #import "CheatDocument.h"
25 // service browsing globals
26 unsigned static _tc_document_count
= 0;
27 NSNetServiceBrowser
static *_tc_service_browser
= nil;
28 NSMutableArray
static *_tc_cheat_services
= nil;
30 Process
static *_tc_target
= nil;
33 @interface CheatDocument ( PrivateAPI
)
36 - (void)_switchTo
:(NSView
*)destination from
:(NSView
*)source
;
37 // using the service browser
38 + (void)_documentCreated
;
39 + (void)_documentDestroyed
;
40 // service addition/removal
41 - (void)_cheatServiceFound
:(NSNotification
*)note
;
42 - (void)_cheatServiceRemoved
:(NSNotification
*)note
;
44 - (void)_setupInitialInterface
;
46 - (void)_displayValuesPrefChanged
:(NSNotification
*)note
;
47 - (void)_windowOnTopPrefChanged
:(NSNotification
*)note
;
48 - (void)_hitsDisplayedPrefChanged
:(NSNotification
*)note
;
52 @interface NSTableView ( PrivateAPI
)
54 - (NSRange
)_rowsInRectAssumingRowsCoverVisible
:(NSRect
)rect
;
59 @implementation CheatDocument
62 // #############################################################################
63 #pragma mark Initialization
64 // #############################################################################
66 - (id)init
// designated
68 if ( self = [super init
] )
70 NSNotificationCenter
*nc
= [NSNotificationCenter defaultCenter
];
72 ChazLog( @
"init doc %X", self );
73 [CheatDocument _documentCreated
];
75 // register for service change notifications
76 [nc addObserver
:self selector
:@selector(_cheatServiceFound
:) name
:TCServiceFoundNote object
:nil];
77 [nc addObserver
:self selector
:@selector(_cheatServiceRemoved
:) name
:TCServiceRemovedNote object
:nil];
79 _cheatData
= [[CheatData alloc
] init
];
80 _searchData
= [[SearchData alloc
] init
];
81 [_searchData setProcess
:_process
];
83 // show search mode when documents are first created
84 _connectsOnOpen
= YES
;
85 [self setMode
:TCSearchMode
];
90 - (id)initWithContentsOfFile
:(NSString
*)fileName ofType
:(NSString
*)docType
92 if ( self = [super initWithContentsOfFile
:fileName ofType
:docType
] )
94 // if document opened from a file, show cheat mode by default
95 [self setMode
:TCCheatMode
];
100 - (id)initWithContentsOfURL
:(NSURL
*)aURL ofType
:(NSString
*)docType
102 if ( self = [super initWithContentsOfURL
:aURL ofType
:docType
] )
104 // if document opened from a URL, show cheat mode by default
105 [self setMode
:TCCheatMode
];
112 ChazLog( @
"dealloc doc %X", self );
114 // unregister observers
115 [(NSNotificationCenter
*)[NSNotificationCenter defaultCenter
] removeObserver
:self];
117 [_cheater setDelegate
:nil];
118 [self disconnectFromCheater
];
120 [_cheatData release
];
121 [_searchData release
];
123 [_serverObject release
];
126 // release the fade if one is occuring
127 [_fadeView removeFromSuperview
];
130 [CheatDocument _documentDestroyed
];
136 // #############################################################################
137 #pragma mark Nib Loading
138 // #############################################################################
140 - (NSString
*)windowNibName
142 return @
"CheatDocument";
145 - (void)windowControllerDidLoadNib
:(NSWindowController
*)aController
147 [super windowControllerDidLoadNib
:aController
];
149 NSNotificationCenter
*nc
= [NSNotificationCenter defaultCenter
];
151 // register for app launch/quit notifications
152 [nc addObserver
:self selector
:@selector(_displayValuesPrefChanged
:) name
:TCDisplayValuesChangedNote object
:nil];
153 [nc addObserver
:self selector
:@selector(_windowOnTopPrefChanged
:) name
:TCWindowsOnTopChangedNote object
:nil];
154 [nc addObserver
:self selector
:@selector(_hitsDisplayedPrefChanged
:) name
:TCHitsDisplayedChangedNote object
:nil];
156 // setup window frame saving
157 [ibWindow useOptimizedDrawing
:YES
];
158 [ibWindow setFrameAutosaveName
:@
"TCCheatWindow"];
161 if ( [[NSUserDefaults standardUserDefaults
] boolForKey
:TCWindowsOnTopPref
] )
163 [ibWindow setLevel
:NSPopUpMenuWindowLevel
];
166 // display one of the modes
167 if ( _mode
== TCCheatMode
) {
168 [self _switchTo
:ibCheatContentView from
:ibPlaceView
];
170 else if ( _mode
== TCSearchMode
) {
171 [self _switchTo
:ibSearchContentView from
:ibPlaceView
];
174 // configure the initial interface
175 [self _setupInitialInterface
];
178 [ibStatusText setDefaultStatus
:[self defaultStatusString
]];
179 [self updateInterface
];
181 // automatically connect to the local cheater
182 if ( _connectsOnOpen
) {
183 [self ibSetLocalCheater
:nil];
186 ChazLog( @
"superview: %@", [[ibSearchVariableTable superview
] superview
] );
190 // #############################################################################
191 #pragma mark Handling Files
192 // #############################################################################
194 - (NSData
*)dataRepresentationOfType
:(NSString
*)type
196 return [NSArchiver archivedDataWithRootObject
:_cheatData
];
199 - (BOOL)loadDataRepresentation
:(NSData
*)data ofType
:(NSString
*)type
201 [_cheatData release
];
204 if ( [type isEqualToString
:@
"Cheat Document"] ) {
206 _cheatData
= [[NSUnarchiver unarchiveObjectWithData
:data
] retain
];
209 // alert the user of the unparsable file
211 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
] );
217 [self updateInterface
];
223 // #############################################################################
224 #pragma mark Service Finding
225 // #############################################################################
227 + (NSArray
*)cheatServices
229 return [NSArray arrayWithArray
:_tc_cheat_services
];
233 - (void)_cheatServiceFound
:(NSNotification
*)note
235 NSMenuItem
*menuItem
;
236 NSNetService
*item
= [note object
];
238 // add the newly found service to the server popup
239 menuItem
= [[NSMenuItem alloc
] init
];
240 [menuItem setTarget
:self];
241 [menuItem setAction
:@selector(ibSetRemoteCheater
:)];
242 [menuItem setTitle
:[item name
]];
243 [menuItem setRepresentedObject
:item
];
244 [self addServer
:menuItem
];
248 - (void)_cheatServiceRemoved
:(NSNotification
*)note
250 NSNetService
*item
= [note object
];
252 // remove the service from the menu
253 [self removeServerWithObject
:item
];
257 // using the service browser
258 + (void)_documentCreated
260 _tc_document_count
++;
262 if ( _tc_document_count
== 1 ) {
263 // first document created, so start the service browser
264 [_tc_service_browser stop
];
265 [_tc_cheat_services release
];
266 // create and setup the browser
267 _tc_service_browser
= [[NSNetServiceBrowser alloc
] init
];
268 [_tc_service_browser setDelegate
:self];
269 [_tc_service_browser searchForServicesOfType
:@
"_cheat._tcp." inDomain
:@
""];
270 // create the service array
271 _tc_cheat_services
= [[NSMutableArray alloc
] init
];
275 + (void)_documentDestroyed
277 _tc_document_count
--;
279 if ( _tc_document_count
== 0 ) {
280 // last document destroyed, so stop the service browser
281 [_tc_service_browser stop
];
282 [_tc_cheat_services release
];
283 // set the globals to nil for safety
284 _tc_service_browser
= nil;
285 _tc_cheat_services
= nil;
290 // NSNetServiceBrowser delegate methods
291 + (void)netServiceBrowserWillSearch
:(NSNetServiceBrowser
*)browser
293 ChazLog( @
"service browser will search" );
296 + (void)netServiceBrowserDidStopSearch
:(NSNetServiceBrowser
*)browser
298 // if the browser stops we assume it needs to die.
299 ChazLog( @
"service browser did stop search" );
303 + (void)netServiceBrowser
:(NSNetServiceBrowser
*)browser didNotSearch
:(NSDictionary
*)errorDict
305 ChazLog( @
"service browser failed with error code: %i", [[errorDict objectForKey
:NSNetServicesErrorCode
] intValue
] );
308 + (void)netServiceBrowser
:(NSNetServiceBrowser
*)browser didFindService
:(NSNetService
*)aNetService moreComing
:(BOOL)moreComing
310 ChazLog( @
"service browser found service: %@", [aNetService name
] );
312 // ignore if this is the local server.
313 if ( [[(AppController
*)NSApp cheatServer
] isListening
] &&
314 [[aNetService name
] isEqualToString
:[[NSUserDefaults standardUserDefaults
] objectForKey
:TCBroadcastNamePref
]] ) {
318 [_tc_cheat_services addObject
:aNetService
];
319 // send a notification for the new service
320 [[NSNotificationCenter defaultCenter
] postNotificationName
:TCServiceFoundNote object
:aNetService
];
323 + (void)netServiceBrowser
:(NSNetServiceBrowser
*)browser didRemoveService
:(NSNetService
*)aNetService moreComing
:(BOOL)moreComing
325 ChazLog( @
"service browser removed service: %@", [aNetService name
] );
327 [_tc_cheat_services removeObject
:aNetService
];
328 // send a notification for the new service
329 [[NSNotificationCenter defaultCenter
] postNotificationName
:TCServiceRemovedNote object
:aNetService
];
333 // #############################################################################
334 #pragma mark Changing Mode
335 // #############################################################################
337 - (void)setMode
:(TCDocumentMode
)mode
339 // if the nib isn't loaded, change the mode
346 - (void)switchToCheatMode
348 NSResponder
*responder
= [ibWindow firstResponder
];
350 if ( [responder isKindOfClass
:[NSText
class]] ) {
351 /* Since text views et al. make the field editor the first
352 responder, you have to take its delegate since that will
353 be set to the actual text view. */
354 responder
= [(NSText
*)responder delegate
];
357 if ( _mode
== TCCheatMode
) {
361 [self _switchTo
:ibCheatContentView from
:ibSearchContentView
];
363 // update the next key view
364 [ibProcessPopup setNextKeyView
:ibCheatVariableTable
];
365 // update current key view
366 if ( !_lastResponder || _lastResponder
== ibWindow
) {
367 // set default responder
368 [ibWindow makeFirstResponder
:ibCheatVariableTable
];
371 [ibWindow makeFirstResponder
:_lastResponder
];
373 _lastResponder
= responder
;
376 - (void)switchToSearchMode
378 NSResponder
*responder
= [ibWindow firstResponder
];
380 if ( [responder isKindOfClass
:[NSText
class]] ) {
381 responder
= [(NSText
*)responder delegate
];
384 if ( _mode
== TCSearchMode
) {
387 _mode
= TCSearchMode
;
388 [self _switchTo
:ibSearchContentView from
:ibCheatContentView
];
390 // update the next key view
391 [ibProcessPopup setNextKeyView
:ibSearchTypePopup
];
392 // update current key view
393 if ( !_lastResponder || _lastResponder
== ibWindow
) {
394 [ibWindow makeFirstResponder
:ibSearchValueField
];
397 [ibWindow makeFirstResponder
:_lastResponder
];
399 _lastResponder
= responder
;
403 - (void)_switchTo
:(NSView
*)destination from
:(NSView
*)source
405 NSView
*contentView
= [ibWindow contentView
];
406 NSRect frame
= [source frame
];
407 NSImage
*fadeImage
= nil;
409 if ( gFadeAnimationDuration
!= 0.0 && [source lockFocusIfCanDraw
] ) {
410 // draw the view to the representation
411 NSBitmapImageRep
*imageRep
= [[NSBitmapImageRep alloc
] initWithFocusedViewRect
:[source bounds
]];
412 [source unlockFocus
];
414 // create the image object
415 fadeImage
= [[NSImage alloc
] initWithSize
:frame.size
];
416 [fadeImage addRepresentation
:imageRep
];
419 // remove the old fade view
420 [_fadeView removeFromSuperview
];
424 // create the new fade view and start the fade
425 _fadeView
= [[FadeView alloc
] initWithFrame
:frame
];
426 [_fadeView setAutoresizingMask
:[source autoresizingMask
]];
427 [_fadeView setDelegate
:self];
428 [_fadeView setImage
:fadeImage
];
429 [_fadeView setFadeDuration
:gFadeAnimationDuration
];
430 [contentView addSubview
:_fadeView
];
431 [_fadeView startFadeAnimation
];
436 // update view size of incoming view
437 [destination setFrame
:frame
];
439 [contentView replaceSubview
:source with
:destination
];
444 - (void)fadeViewFinishedAnimation
:(FadeView
*)theView
446 [_fadeView removeFromSuperview
];
452 // #############################################################################
453 #pragma mark Accessors
454 // #############################################################################
456 - (NSString
*)defaultStatusString
459 return @
"Not Connected";
461 return [NSString stringWithFormat
:@
"Connected to %@.", [_cheater hostAddress
]];
464 - (BOOL)isLoadedFromFile
466 return ([self fileName
] != nil);
470 - (void)addServer
:(NSMenuItem
*)item
472 NSMenu
*serverMenu
= [ibServerPopup menu
];
475 if ( [serverMenu numberOfItems
] <= 2 ) {
477 [serverMenu addItem
:[NSMenuItem separatorItem
]];
479 [serverMenu addItem
:item
];
483 - (void)removeServerWithObject
:(id)serverObject
485 NSMenu
*serverMenu
= [ibServerPopup menu
];
487 if ( serverObject
) {
488 [serverMenu removeItemWithRepresentedObject
:serverObject
];
489 if ( [serverMenu numberOfItems
] == 3 ) {
491 [serverMenu removeItemAtIndex
:2];
497 // #############################################################################
498 #pragma mark Interface
499 // #############################################################################
501 - (void)_setupInitialInterface
504 NSMenuItem
*menuItem
;
506 NSArray
*cheatServices
;
509 // create and set the server popup menu
510 serverMenu
= [[NSMenu alloc
] init
];
511 [serverMenu setAutoenablesItems
:YES
];
513 // local connection item
514 menuItem
= [[NSMenuItem alloc
] init
];
515 [menuItem setTarget
:self];
516 [menuItem setAction
:@selector(ibSetLocalCheater
:)];
517 [menuItem setTitle
:@
"On This Computer"];
518 [menuItem setRepresentedObject
:[NSNull null
]];
519 [serverMenu addItem
:menuItem
];
521 // arbitrary connection item
522 menuItem
= [[NSMenuItem alloc
] init
];
523 [menuItem setTarget
:self];
524 [menuItem setAction
:@selector(ibRunCustomServerSheet
:)];
525 [menuItem setTitle
:[NSString stringWithFormat
:@
"Other Server%C", 0x2026]];
526 [menuItem setKeyEquivalent
:@
"k"];
527 [menuItem setKeyEquivalentModifierMask
:NSCommandKeyMask
];
528 [serverMenu addItem
:menuItem
];
531 [ibServerPopup setMenu
:serverMenu
];
532 [serverMenu release
];
534 // add current list of rendezvous services
535 cheatServices
= [CheatDocument cheatServices
];
536 len
= [cheatServices count
];
537 for ( i
= 0; i
< len
; i
++ ) {
538 NSNetService
*item
= [cheatServices objectAtIndex
:i
];
539 menuItem
= [[NSMenuItem alloc
] init
];
540 [menuItem setTarget
:self];
541 [menuItem setAction
:@selector(ibSetRemoteCheater
:)];
542 [menuItem setTitle
:[item name
]];
543 [menuItem setRepresentedObject
:item
];
544 [self addServer
:menuItem
];
548 [ibSearchVariableTable setDoubleAction
:@selector(ibAddSearchVariable
:)];
549 [ibSearchVariableTable setCanDelete
:NO
];
551 // BUG: for some reason IB doesn't like to set the default selection
552 // for an NSMatrix to anything but the first cell, so set this explicitly.
553 [ibSearchValueUsedMatrix selectCellWithTag
:TCGivenValue
];
555 // we use undoing/redoing for reverting search results
556 [self setHasUndoManager
:NO
];
559 - (void)updateInterface
563 // if there is cheat data, fill in the data information
564 [ibWindow setTitle
:[self displayName
]];
565 if ( [_cheatData process
] ) {
566 if ( [[_cheatData cheatInfo
] isEqualToString
:@
""] ) {
567 [ibCheatInfoText setStringValue
:[NSString stringWithFormat
:@
"%@ %@",
568 [_cheatData gameName
],
569 [_cheatData gameVersion
]]];
572 [ibCheatInfoText setStringValue
:[NSString stringWithFormat
:@
"%@ %@ - %@",
573 [_cheatData gameName
],
574 [_cheatData gameVersion
],
575 [_cheatData cheatInfo
]]];
579 [ibCheatInfoText setStringValue
:[_cheatData cheatInfo
]];
582 [ibCheatRepeatButton setState
:[_cheatData repeats
]];
583 [ibCheatRepeatField setDoubleValue
:[_cheatData repeatInterval
]];
586 // if we're connected...
589 if ( _status
== TCIdleStatus
)
592 [ibServerPopup setEnabled
:YES
];
593 [ibProcessPopup setEnabled
:YES
];
595 [ibSearchValueUsedMatrix setEnabled
:YES
];
596 if ( [_searchData hasSearchedOnce
] ) {
597 [ibSearchTypePopup setEnabled
:NO
];
598 [ibSearchIntegerSignMatrix setEnabled
:NO
];
599 [[ibSearchValueUsedMatrix cellWithTag
:TCLastValue
] setEnabled
:YES
];
600 if ( [_searchData valueUsed
] == TCGivenValue
) {
601 [ibSearchValueField setEnabled
:YES
];
604 [ibSearchValueField setEnabled
:NO
];
606 [ibSearchClearButton setEnabled
:YES
];
607 [ibSearchVariableTable setEnabled
:YES
];
608 int selectedRows
= [ibSearchVariableTable numberOfSelectedRows
];
609 if ( selectedRows
> 0 ) {
610 [ibSearchVariableButton setEnabled
:YES
];
613 [ibSearchVariableButton setEnabled
:NO
];
617 [ibSearchTypePopup setEnabled
:YES
];
618 if ( [_searchData isTypeInteger
] ) {
619 [ibSearchIntegerSignMatrix setEnabled
:YES
];
622 [ibSearchIntegerSignMatrix setEnabled
:NO
];
624 [[ibSearchValueUsedMatrix cellWithTag
:TCLastValue
] setEnabled
:NO
];
625 [ibSearchValueUsedMatrix selectCellWithTag
:[_searchData valueUsed
]];
626 [ibSearchValueField setEnabled
:YES
];
627 [ibSearchClearButton setEnabled
:NO
];
628 [ibSearchVariableTable setEnabled
:NO
];
629 [ibSearchVariableButton setEnabled
:NO
];
631 if ( [_searchData variableType
] != TCString
) {
632 [ibSearchOperatorPopup setEnabled
:YES
];
635 [ibSearchOperatorPopup setEnabled
:NO
];
637 [ibSearchButton setTitle
:@
"Search"];
638 [ibSearchButton setAction
:@selector(ibSearch
:)];
639 [ibSearchButton setKeyEquivalent
:@
""];
640 [ibSearchButton setEnabled
:YES
];
642 [ibCheatVariableTable setEnabled
:YES
];
643 [ibCheatRepeatButton setEnabled
:YES
];
644 [ibCheatRepeatAuxText setTextColor
:[NSColor controlTextColor
]];
645 [ibCheatRepeatField setEnabled
:[_cheatData repeats
]];
646 [ibCheatButton setTitle
:@
"Apply Cheat"];
647 [ibCheatButton setAction
:@selector(ibCheat
:)];
648 [ibCheatButton setKeyEquivalent
:@
"\r"];
649 if ( [_cheatData enabledVariableCount
] > 0 ) {
650 [ibCheatButton setEnabled
:YES
];
651 if ( [[_cheatData process
] sameApplicationAs
:_process
] ) {
652 [ibCheatButton setKeyEquivalent
:@
"\r"];
655 [ibCheatButton setKeyEquivalent
:@
""];
659 [ibCheatButton setEnabled
:NO
];
665 [ibServerPopup setEnabled
:NO
];
666 [ibProcessPopup setEnabled
:NO
];
668 [ibSearchTypePopup setEnabled
:NO
];
669 [ibSearchIntegerSignMatrix setEnabled
:NO
];
670 [ibSearchOperatorPopup setEnabled
:NO
];
671 [ibSearchValueUsedMatrix setEnabled
:NO
];
672 [ibSearchValueField setEnabled
:NO
];
673 [ibSearchClearButton setEnabled
:NO
];
674 [ibSearchVariableTable setEnabled
:NO
];
675 [ibSearchVariableButton setEnabled
:NO
];
677 [ibCheatVariableTable setEnabled
:NO
];
678 [ibCheatRepeatButton setEnabled
:NO
];
679 [ibCheatRepeatAuxText setTextColor
:[NSColor disabledControlTextColor
]];
680 [ibCheatRepeatField setEnabled
:NO
];
682 if ( _status
== TCSearchingStatus
) {
683 [ibSearchButton setTitle
:@
"Cancel"];
684 [ibSearchButton setAction
:@selector(ibCancelSearch
:)];
685 [ibSearchButton setKeyEquivalent
:@
"\E"];
686 [ibSearchButton setEnabled
:!_isCancelingTask
];
687 [ibCheatButton setTitle
:@
"Apply Cheat"];
688 [ibCheatButton setAction
:@selector(ibCheat
:)];
689 if ( [[_cheatData process
] sameApplicationAs
:_process
] ) {
690 [ibCheatButton setKeyEquivalent
:@
"\r"];
693 [ibCheatButton setKeyEquivalent
:@
""];
695 [ibCheatButton setEnabled
:NO
];
697 else if ( _status
== TCCheatingStatus
) {
698 [ibSearchButton setTitle
:@
"Search"];
699 [ibSearchButton setAction
:@selector(ibSearch
:)];
700 [ibSearchButton setKeyEquivalent
:@
""];
701 [ibSearchButton setEnabled
:NO
];
702 [ibCheatButton setTitle
:@
"Stop Cheat"];
703 [ibCheatButton setAction
:@selector(ibStopCheat
:)];
704 [ibCheatButton setKeyEquivalent
:@
"\E"];
705 [ibCheatButton setEnabled
:!_isCancelingTask
];
708 [ibSearchButton setTitle
:@
"Search"];
709 [ibSearchButton setAction
:@selector(ibSearch
:)];
710 [ibSearchButton setKeyEquivalent
:@
""];
711 [ibSearchButton setEnabled
:NO
];
712 [ibCheatButton setTitle
:@
"Apply Cheat"];
713 [ibCheatButton setAction
:@selector(ibCheat
:)];
714 if ( [[_cheatData process
] sameApplicationAs
:_process
] ) {
715 [ibCheatButton setKeyEquivalent
:@
"\r"];
718 [ibCheatButton setKeyEquivalent
:@
""];
720 [ibCheatButton setEnabled
:NO
];
727 [ibServerPopup setEnabled
:YES
];
728 [ibProcessPopup setEnabled
:NO
];
730 [ibSearchTypePopup setEnabled
:NO
];
731 [ibSearchIntegerSignMatrix setEnabled
:NO
];
732 [ibSearchOperatorPopup setEnabled
:NO
];
733 [ibSearchValueUsedMatrix setEnabled
:NO
];
734 [ibSearchValueField setEnabled
:NO
];
735 [ibSearchButton setEnabled
:NO
];
736 [ibSearchClearButton setEnabled
:NO
];
737 [ibSearchVariableTable setEnabled
:NO
];
738 [ibSearchVariableButton setEnabled
:NO
];
740 [ibCheatVariableTable setEnabled
:NO
];
741 [ibCheatRepeatButton setEnabled
:NO
];
742 [ibCheatRepeatAuxText setTextColor
:[NSColor disabledControlTextColor
]];
743 [ibCheatRepeatField setEnabled
:NO
];
744 [ibCheatButton setEnabled
:NO
];
749 - (void)setActualResults
:(unsigned)count
751 unsigned recieved
= [_searchData numberOfResults
];
754 [ibSearchVariableTable setToolTip
:@
""];
756 else if ( recieved
== count
) {
758 [ibSearchVariableTable setToolTip
:[NSString stringWithFormat
:@
"Displaying one result."]];
761 [ibSearchVariableTable setToolTip
:[NSString stringWithFormat
:@
"Displaying %i results.", count
]];
764 else if ( recieved
< count
) {
765 [ibSearchVariableTable setToolTip
:[NSString stringWithFormat
:@
"Displaying %i of %i results.", recieved
, count
]];
770 - (NSString
*)displayName
772 // override the default window title if there is a custom one
773 NSString
*title
= [_cheatData windowTitle
];
775 if ( !title ||
[title isEqualToString
:@
""] ) {
776 return [super displayName
];
781 - (BOOL)validateMenuItem
:(NSMenuItem
*)menuItem
783 //ChazLog( @"validate menuitem: %@", [menuItem title] );
786 if ( [menuItem action
] == @selector(ibUndo
:) ) {
787 if ( [_searchData undoesLeft
] > 0 ) {
794 if ( [menuItem action
] == @selector(ibRedo
:) ) {
795 if ( [_searchData redoesLeft
] > 0 ) {
803 // the add variables items
804 if ( [menuItem action
] == @selector(ibAddCheatVariable
:) && _status
== TCCheatingStatus
) {
808 // the 'pause' menu item
809 if ( [menuItem tag
] == 1000 ) {
813 if ( _isTargetPaused
) {
814 [menuItem setTitle
:@
"Resume Target"];
815 [menuItem setAction
:@selector(ibResumeTarget
:)];
818 [menuItem setTitle
:@
"Pause Target"];
819 [menuItem setAction
:@selector(ibPauseTarget
:)];
823 // the 'memory dump' menu item
824 else if ( [menuItem tag
] == 1001 ) {
825 if ( !_cheater || _status
!= TCIdleStatus
) {
829 // the 'mode switch' menu item
830 else if ( [menuItem tag
] == 1002 ) {
831 if ( _mode
== TCSearchMode
) {
832 [menuItem setTitle
:@
"Show Cheat Mode"];
834 else /* _mode == TCCheatMode */ {
835 [menuItem setTitle
:@
"Show Search Mode"];
838 // the 'edit variables' menu item
839 else if ( [menuItem tag
] == 1003 ) {
840 return (_mode
== TCCheatMode
&& [ibCheatVariableTable selectedRow
] != -1);
842 // the 'clear search' menu item
843 else if ( [menuItem tag
] == 1004 ) {
844 return [ibSearchClearButton isEnabled
];
846 // the cancel menu item
847 else if ( [menuItem tag
] == 1005 ) {
848 if ( !_cheater || _isCancelingTask
) {
851 if ( _status
== TCSearchingStatus
) {
852 [menuItem setTitle
:@
"Cancel Search"];
853 [menuItem setAction
:@selector(ibCancelSearch
:)];
855 else if ( _status
== TCCheatingStatus
) {
856 [menuItem setTitle
:@
"Stop Cheat"];
857 [menuItem setAction
:@selector(ibStopCheat
:)];
859 else if ( _status
== TCDumpingStatus
) {
860 [menuItem setTitle
:@
"Cancel Dump"];
861 [menuItem setAction
:@selector(ibCancelDump
:)];
868 return [super validateMenuItem
:menuItem
];
872 - (void)setDocumentChanged
874 // only count document changes if there are variables
875 // and if the pref is set
876 if ( [[NSUserDefaults standardUserDefaults
] boolForKey
:TCAskForSavePref
] &&
877 ([_cheatData variableCount
] > 0 ||
[self isLoadedFromFile
]) ) {
878 [self updateChangeCount
:NSChangeDone
];
883 - (int)numberOfRowsInTableView
:(NSTableView
*)aTableView
885 if ( aTableView
== ibCheatVariableTable
) {
886 return [_cheatData variableCount
];
888 else if ( aTableView
== ibSearchVariableTable
) {
889 return [_searchData numberOfResults
];
894 - (id)tableView
:(NSTableView
*)aTableView objectValueForTableColumn
:(NSTableColumn
*)aTableColumn row
:(int)rowIndex
896 NSString
*identifier
= [aTableColumn identifier
];
898 if ( aTableView
== ibCheatVariableTable
) {
899 Variable
*variable
= [_cheatData variableAtIndex
:rowIndex
];
901 if ( [identifier isEqualToString
:@
"enable"] ) {
902 return [NSNumber numberWithBool
:[variable isEnabled
]];
904 else if ( [identifier isEqualToString
:@
"variable"] ) {
905 return [variable typeString
];
907 else if ( [identifier isEqualToString
:@
"address"] ) {
908 return [variable addressString
];
910 else if ( [identifier isEqualToString
:@
"value"] ) {
911 return [variable stringValue
];
914 else if ( aTableView
== ibSearchVariableTable
) {
915 return [_searchData stringForRow
:rowIndex
];
920 - (void)tableView
:(NSTableView
*)aTableView setObjectValue
:(id)anObject forTableColumn
:(NSTableColumn
*)aTableColumn row
:(int)rowIndex
922 NSString
*identifier
= [aTableColumn identifier
];
924 if ( aTableView
== ibCheatVariableTable
) {
925 Variable
*variable
= [_cheatData variableAtIndex
:rowIndex
];
927 if ( [identifier isEqualToString
:@
"address"] ) {
928 if ( [variable setAddressString
:anObject
] ) {
929 [self setDocumentChanged
];
932 else if ( [identifier isEqualToString
:@
"value"] ) {
933 if ( [variable setStringValue
:anObject
] ) {
934 [self setDocumentChanged
];
940 - (void)tableViewSelectionDidChange
:(NSNotification
*)aNotification
942 NSTableView
*aTableView
= [aNotification object
];
944 if ( aTableView
== ibSearchVariableTable
) {
945 int selectedRows
= [aTableView numberOfSelectedRows
];
946 if ( selectedRows
> 1 ) {
947 [ibSearchVariableButton setTitle
:@
"Add Variables"];
949 else if ( selectedRows
== 1 ) {
950 [ibSearchVariableButton setTitle
:@
"Add Variable"];
956 - (BOOL)tableViewDidReceiveEnterKey
:(NSTableView
*)tableView
958 if ( tableView
== ibSearchVariableTable
) {
959 [ibSearchVariableButton performClick
:nil];
965 - (BOOL)tableViewDidReceiveSpaceKey
:(NSTableView
*)tableView
967 if ( tableView
== ibCheatVariableTable
) {
968 [self ibSetVariableEnabled
:nil];
974 - (NSString
*)tableViewPasteboardType
:(NSTableView
*)tableView
976 return @
"TCVariablePboardType";
979 - (NSData
*)tableView
:(NSTableView
*)tableView copyRows
:(NSArray
*)rows
981 NSMutableArray
*vars
;
985 vars
= [[NSMutableArray alloc
] initWithCapacity
:top
];
987 // add the new variables
988 if ( tableView
== ibSearchVariableTable
) {
989 for ( i
= 0; i
< top
; i
++ ) {
990 unsigned index
= [[rows objectAtIndex
:i
] unsignedIntValue
];
991 [vars addObject
:[_searchData variableAtIndex
:index
]];
995 for ( i
= 0; i
< top
; i
++ ) {
996 unsigned index
= [[rows objectAtIndex
:i
] unsignedIntValue
];
997 [vars addObject
:[_cheatData variableAtIndex
:index
]];
1001 return [NSArchiver archivedDataWithRootObject
:[vars autorelease
]];
1004 - (void)tableView
:(NSTableView
*)tableView pasteRowsWithData
:(NSData
*)rowData
1006 NSArray
*vars
= [NSUnarchiver unarchiveObjectWithData
:rowData
];
1007 int i
, top
, lastRow
;
1009 if ( tableView
== ibSearchVariableTable
) {
1015 for ( i
= 0; i
< top
; i
++ ) {
1016 Variable
*var
= [vars objectAtIndex
:i
];
1017 [_cheatData addVariable
:var
];
1020 lastRow
= [_cheatData variableCount
]-1;
1021 [tableView reloadData
];
1022 if ( MacOSXVersion() >= 0x1030 ) {
1023 [tableView selectRowIndexes
:[NSIndexSet indexSetWithIndex
:lastRow
] byExtendingSelection
:NO
];
1026 [tableView selectRow
:lastRow byExtendingSelection
:NO
];
1028 [tableView scrollRowToVisible
:lastRow
];
1030 [self setDocumentChanged
];
1031 [self updateInterface
];
1034 - (void)tableView
:(NSTableView
*)tableView deleteRows
:(NSArray
*)rows
1038 if ( tableView
== ibCheatVariableTable
) {
1040 for ( i
= len
-1; i
>= 0; i
-- ) {
1041 [_cheatData removeVariableAtIndex
:[[rows objectAtIndex
:i
] unsignedIntValue
]];
1043 // reselect the last item if the selection is now invalid
1044 len
= [_cheatData variableCount
] - 1;
1045 if ( [tableView selectedRow
] > len
) {
1046 if ( MacOSXVersion() >= 0x1030 ) {
1047 [tableView selectRowIndexes
:[NSIndexSet indexSetWithIndex
:len
] byExtendingSelection
:NO
];
1050 [tableView selectRow
:len byExtendingSelection
:NO
];
1053 [tableView reloadData
];
1055 [self setDocumentChanged
];
1056 [self updateInterface
];
1060 // VariableTable Delegate
1061 - (void)tableView
:(NSTableView
*)aTableView didChangeVisibleRows
:(NSRange
)rows
1063 ChazLog( @
"new visible rows: %@", NSStringFromRange( rows
) );
1064 if ( [_searchData valuesLoaded
] ) {
1065 [self watchVariables
];
1070 // #############################################################################
1071 #pragma mark Utility
1072 // #############################################################################
1074 + (void)setGlobalTarget
:(Process
*)target
1077 [_tc_target release
];
1078 _tc_target
= target
;
1081 + (Process
*)globalTarget
1087 - (void)showError
:(NSString
*)error
1089 NSColor
*red
= [NSColor colorWithCalibratedRed
:0.7 green
:0.0 blue
:0.0 alpha
:1.0];
1091 [ibStatusText setDefaultStatus
:[self defaultStatusString
]];
1092 [ibStatusText setTemporaryStatus
:[NSString stringWithFormat
:@
"Error: %@", error
] color
:red duration
:7.0];
1096 - (BOOL)shouldConnectWithServer
:(NSMenuItem
*)item
1100 if ( _resolvingService
) {
1101 // don't connect if a service is being resolved
1102 [ibServerPopup selectItemAtIndex
:[ibServerPopup indexOfItemWithRepresentedObject
:_resolvingService
]];
1106 if ( [item respondsToSelector
:@selector(representedObject
)] ) {
1107 serverObject
= [item representedObject
];
1110 serverObject
= [NSNull null
];
1113 if ( [_serverObject isEqual
:serverObject
] ) {
1114 // already connected, don't connect
1120 - (void)selectConnectedCheater
1122 int index
= [ibServerPopup indexOfItemWithRepresentedObject
:_serverObject
];
1123 if ( index
!= -1 ) {
1124 [ibServerPopup selectItemAtIndex
:index
];
1128 - (void)connectWithServer
:(NSMenuItem
*)item
1132 if ( [item respondsToSelector
:@selector(representedObject
)] ) {
1133 serverObject
= [item representedObject
];
1136 serverObject
= [NSNull null
];
1139 // save a reference to the server object
1140 [serverObject retain
];
1141 [_serverObject release
];
1142 _serverObject
= serverObject
;
1145 - (void)disconnectFromCheater
1149 // don't do anything if we are already disconnected
1154 _status
= TCIdleStatus
;
1157 [_searchData clearResults
];
1159 //[ibSearchVariableTable reloadData]; // this can cause a crash, so commenting it out for now.
1160 // clear the selected process
1163 [_serverObject release
];
1164 _serverObject
= nil;
1166 if ( ![self isLoadedFromFile
] ) {
1167 [_cheatData setProcess
:nil];
1170 // clear the process menu
1171 blankMenu
= [[NSMenu alloc
] initWithTitle
:@
""];
1172 [blankMenu addItemWithTitle
:@
"" action
:NULL keyEquivalent
:@
""];
1173 [ibProcessPopup setMenu
:blankMenu
];
1174 [blankMenu release
];
1176 // kill the connection
1177 [_cheater disconnect
];
1183 - (void)setConnectOnOpen
:(BOOL)flag
1185 _connectsOnOpen
= flag
;
1188 - (void)connectWithURL
:(NSString
*)url
1190 NSMenu
*serverMenu
= [ibServerPopup menu
];
1191 NSURL
*theUrl
= [NSURL URLWithString
:url
];
1197 int indexIfAlreadyExists
;
1199 host
= [theUrl host
];
1200 port
= [[theUrl port
] intValue
];
1203 NSBeginInformationalAlertSheet( @
"The Cheat can't parse the URL.", @
"OK", nil, nil, ibWindow
, self, NULL
, NULL
, NULL
,
1204 @
"The Cheat can't connect to the server because \"%@\" is not a valid URL.", url
);
1208 // use default port number
1210 port
= TCDefaultListenPort
;
1213 addrData
= [MySocket addressWithHost
:host port
:port
];
1215 NSBeginInformationalAlertSheet( @
"The Cheat can't find the server.", @
"OK", nil, nil, ibWindow
, self, NULL
, NULL
, NULL
,
1216 @
"The Cheat can't connect to the server \"%@\" because it can't be found.", host
);
1220 indexIfAlreadyExists
= [serverMenu indexOfItemWithRepresentedObject
:addrData
];
1221 if ( indexIfAlreadyExists
== -1 ) {
1222 NSMenuItem
*menuItem
;
1223 // add the newly found service to the server popup
1224 menuItem
= [[NSMenuItem alloc
] init
];
1225 [menuItem setTarget
:self];
1226 [menuItem setAction
:@selector(ibSetCustomCheater
:)];
1227 [menuItem setTitle
:[NSString stringWithFormat
:@
"%@:%i", host
, port
]];
1228 [menuItem setRepresentedObject
:addrData
];
1229 [self addServer
:menuItem
];
1231 [self ibSetCustomCheater
:menuItem
];
1236 // select matching item
1237 [self ibSetCustomCheater
:[serverMenu itemAtIndex
:indexIfAlreadyExists
]];
1241 [self selectConnectedCheater
];
1245 - (void)watchVariables
1249 if ( [[NSUserDefaults standardUserDefaults
] boolForKey
:TCDisplayValuesPref
] ) {
1250 float interval
= [[NSUserDefaults standardUserDefaults
] floatForKey
:TCValueUpdatePref
];
1252 range
= [ibSearchVariableTable visibleRows
];
1253 [_cheater watchVariablesAtIndex
:range.location count
:range.length interval
:interval
];
1256 [_cheater stopWatchingVariables
];
1261 // #############################################################################
1262 #pragma mark Notifications
1263 // #############################################################################
1265 - (void)_displayValuesPrefChanged
:(NSNotification
*)note
1267 [self watchVariables
];
1270 - (void)_windowOnTopPrefChanged
:(NSNotification
*)note
1272 ChazLog( @
"_windowOnTopPrefChanged" );
1273 if ( [[NSUserDefaults standardUserDefaults
] boolForKey
:TCWindowsOnTopPref
] ) {
1274 [ibWindow setLevel
:NSPopUpMenuWindowLevel
];
1277 [ibWindow setLevel
:NSNormalWindowLevel
];
1281 - (void)_hitsDisplayedPrefChanged
:(NSNotification
*)note
1283 // send preferences to the cheater
1284 [_cheater limitReturnedResults
:[[NSUserDefaults standardUserDefaults
] integerForKey
:TCHitsDisplayedPref
]];