3 * The Cheat - The legendary universal game trainer for Mac OS X.
4 * http://www.brokenzipper.com/trac/wiki/TheCheat
6 * Copyright (c) 2003-2011, Charles McGarvey et al.
8 * Distributable under the terms and conditions of the 2-clause BSD
9 * license; see the file COPYING for the legal text of the license.
12 #import "CheatDocument.h"
16 // service browsing globals
17 unsigned static _tc_document_count
= 0;
18 NSNetServiceBrowser
static *_tc_service_browser
= nil;
19 NSMutableArray
static *_tc_cheat_services
= nil;
21 Process
static *_tc_target
= nil;
24 @interface CheatDocument ( PrivateAPI
)
27 - (void)_switchTo
:(NSView
*)destination from
:(NSView
*)source
;
28 // using the service browser
29 + (void)_documentCreated
;
30 + (void)_documentDestroyed
;
31 // service addition/removal
32 - (void)_cheatServiceFound
:(NSNotification
*)note
;
33 - (void)_cheatServiceRemoved
:(NSNotification
*)note
;
35 - (void)_setupInitialInterface
;
37 - (void)_displayValuesPrefChanged
:(NSNotification
*)note
;
38 - (void)_windowOnTopPrefChanged
:(NSNotification
*)note
;
39 - (void)_hitsDisplayedPrefChanged
:(NSNotification
*)note
;
43 @interface NSTableView ( PrivateAPI
)
45 - (NSRange
)_rowsInRectAssumingRowsCoverVisible
:(NSRect
)rect
;
50 @implementation CheatDocument
53 // #############################################################################
54 #pragma mark Initialization
55 // #############################################################################
57 - (id)init
// designated
59 if ( self = [super init
] )
61 NSNotificationCenter
*nc
= [NSNotificationCenter defaultCenter
];
63 ChazLog( @
"init doc %X", self );
64 [CheatDocument _documentCreated
];
66 // register for service change notifications
67 [nc addObserver
:self selector
:@selector(_cheatServiceFound
:) name
:TCServiceFoundNote object
:nil];
68 [nc addObserver
:self selector
:@selector(_cheatServiceRemoved
:) name
:TCServiceRemovedNote object
:nil];
70 _cheatData
= [[CheatData alloc
] init
];
71 _searchData
= [[SearchData alloc
] init
];
72 [_searchData setProcess
:_process
];
74 // show search mode when documents are first created
75 _connectsOnOpen
= YES
;
76 [self setMode
:TCSearchMode
];
81 - (id)initWithContentsOfFile
:(NSString
*)fileName ofType
:(NSString
*)docType
83 if ( self = [super initWithContentsOfFile
:fileName ofType
:docType
] )
85 // if document opened from a file, show cheat mode by default
86 [self setMode
:TCCheatMode
];
91 - (id)initWithContentsOfURL
:(NSURL
*)aURL ofType
:(NSString
*)docType
93 if ( self = [super initWithContentsOfURL
:aURL ofType
:docType
] )
95 // if document opened from a URL, show cheat mode by default
96 [self setMode
:TCCheatMode
];
103 ChazLog( @
"dealloc doc %X", self );
105 // unregister observers
106 [(NSNotificationCenter
*)[NSNotificationCenter defaultCenter
] removeObserver
:self];
108 [_cheater setDelegate
:nil];
109 [self disconnectFromCheater
];
111 [_cheatData release
];
112 [_searchData release
];
114 [_serverObject release
];
117 // release the fade if one is occuring
118 [_fadeView removeFromSuperview
];
121 [CheatDocument _documentDestroyed
];
127 // #############################################################################
128 #pragma mark Nib Loading
129 // #############################################################################
131 - (NSString
*)windowNibName
133 return @
"CheatDocument";
136 - (void)windowControllerDidLoadNib
:(NSWindowController
*)aController
138 [super windowControllerDidLoadNib
:aController
];
140 NSNotificationCenter
*nc
= [NSNotificationCenter defaultCenter
];
142 // register for app launch/quit notifications
143 [nc addObserver
:self selector
:@selector(_displayValuesPrefChanged
:) name
:TCDisplayValuesChangedNote object
:nil];
144 [nc addObserver
:self selector
:@selector(_windowOnTopPrefChanged
:) name
:TCWindowsOnTopChangedNote object
:nil];
145 [nc addObserver
:self selector
:@selector(_hitsDisplayedPrefChanged
:) name
:TCHitsDisplayedChangedNote object
:nil];
147 // setup window frame saving
148 [ibWindow useOptimizedDrawing
:YES
];
149 [ibWindow setFrameAutosaveName
:@
"TCCheatWindow"];
152 if ( [[NSUserDefaults standardUserDefaults
] boolForKey
:TCWindowsOnTopPref
] )
154 [ibWindow setLevel
:NSPopUpMenuWindowLevel
];
157 // display one of the modes
158 if ( _mode
== TCCheatMode
) {
159 [self _switchTo
:ibCheatContentView from
:ibPlaceView
];
161 else if ( _mode
== TCSearchMode
) {
162 [self _switchTo
:ibSearchContentView from
:ibPlaceView
];
165 // configure the initial interface
166 [self _setupInitialInterface
];
169 [ibStatusText setDefaultStatus
:[self defaultStatusString
]];
170 [self updateInterface
];
172 // automatically connect to the local cheater
173 if ( _connectsOnOpen
) {
174 [self ibSetLocalCheater
:nil];
177 ChazLog( @
"superview: %@", [[ibSearchVariableTable superview
] superview
] );
181 // #############################################################################
182 #pragma mark Handling Files
183 // #############################################################################
185 - (NSData
*)dataRepresentationOfType
:(NSString
*)type
187 return [NSArchiver archivedDataWithRootObject
:_cheatData
];
190 - (BOOL)loadDataRepresentation
:(NSData
*)data ofType
:(NSString
*)type
192 [_cheatData release
];
195 if ( [type isEqualToString
:@
"Cheat Document"] ) {
197 _cheatData
= [[NSUnarchiver unarchiveObjectWithData
:data
] retain
];
200 // alert the user of the unparsable file
202 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
] );
208 [self updateInterface
];
214 // #############################################################################
215 #pragma mark Service Finding
216 // #############################################################################
218 + (NSArray
*)cheatServices
220 return [NSArray arrayWithArray
:_tc_cheat_services
];
224 - (void)_cheatServiceFound
:(NSNotification
*)note
226 NSMenuItem
*menuItem
;
227 NSNetService
*item
= [note object
];
229 // add the newly found service to the server popup
230 menuItem
= [[NSMenuItem alloc
] init
];
231 [menuItem setTarget
:self];
232 [menuItem setAction
:@selector(ibSetRemoteCheater
:)];
233 [menuItem setTitle
:[item name
]];
234 [menuItem setRepresentedObject
:item
];
235 [self addServer
:menuItem
];
239 - (void)_cheatServiceRemoved
:(NSNotification
*)note
241 NSNetService
*item
= [note object
];
243 // remove the service from the menu
244 [self removeServerWithObject
:item
];
248 // using the service browser
249 + (void)_documentCreated
251 _tc_document_count
++;
253 if ( _tc_document_count
== 1 ) {
254 // first document created, so start the service browser
255 [_tc_service_browser stop
];
256 [_tc_cheat_services release
];
257 // create and setup the browser
258 _tc_service_browser
= [[NSNetServiceBrowser alloc
] init
];
259 [_tc_service_browser setDelegate
:self];
260 [_tc_service_browser searchForServicesOfType
:@
"_cheat._tcp." inDomain
:@
""];
261 // create the service array
262 _tc_cheat_services
= [[NSMutableArray alloc
] init
];
266 + (void)_documentDestroyed
268 _tc_document_count
--;
270 if ( _tc_document_count
== 0 ) {
271 // last document destroyed, so stop the service browser
272 [_tc_service_browser stop
];
273 [_tc_cheat_services release
];
274 // set the globals to nil for safety
275 _tc_service_browser
= nil;
276 _tc_cheat_services
= nil;
281 // NSNetServiceBrowser delegate methods
282 + (void)netServiceBrowserWillSearch
:(NSNetServiceBrowser
*)browser
284 ChazLog( @
"service browser will search" );
287 + (void)netServiceBrowserDidStopSearch
:(NSNetServiceBrowser
*)browser
289 // if the browser stops we assume it needs to die.
290 ChazLog( @
"service browser did stop search" );
294 + (void)netServiceBrowser
:(NSNetServiceBrowser
*)browser didNotSearch
:(NSDictionary
*)errorDict
296 ChazLog( @
"service browser failed with error code: %i", [[errorDict objectForKey
:NSNetServicesErrorCode
] intValue
] );
299 + (void)netServiceBrowser
:(NSNetServiceBrowser
*)browser didFindService
:(NSNetService
*)aNetService moreComing
:(BOOL)moreComing
301 ChazLog( @
"service browser found service: %@", [aNetService name
] );
303 // ignore if this is the local server.
304 if ( [[(AppController
*)NSApp cheatServer
] isListening
] &&
305 [[aNetService name
] isEqualToString
:[[NSUserDefaults standardUserDefaults
] objectForKey
:TCBroadcastNamePref
]] ) {
309 [_tc_cheat_services addObject
:aNetService
];
310 // send a notification for the new service
311 [[NSNotificationCenter defaultCenter
] postNotificationName
:TCServiceFoundNote object
:aNetService
];
314 + (void)netServiceBrowser
:(NSNetServiceBrowser
*)browser didRemoveService
:(NSNetService
*)aNetService moreComing
:(BOOL)moreComing
316 ChazLog( @
"service browser removed service: %@", [aNetService name
] );
318 [_tc_cheat_services removeObject
:aNetService
];
319 // send a notification for the new service
320 [[NSNotificationCenter defaultCenter
] postNotificationName
:TCServiceRemovedNote object
:aNetService
];
324 // #############################################################################
325 #pragma mark Changing Mode
326 // #############################################################################
328 - (void)setMode
:(TCDocumentMode
)mode
330 // if the nib isn't loaded, change the mode
337 - (void)switchToCheatMode
339 NSResponder
*responder
= [ibWindow firstResponder
];
341 if ( [responder isKindOfClass
:[NSText
class]] ) {
342 /* Since text views et al. make the field editor the first
343 responder, you have to take its delegate since that will
344 be set to the actual text view. */
345 responder
= [(NSText
*)responder delegate
];
348 if ( _mode
== TCCheatMode
) {
352 [self _switchTo
:ibCheatContentView from
:ibSearchContentView
];
354 // update the next key view
355 [ibProcessPopup setNextKeyView
:ibCheatVariableTable
];
356 // update current key view
357 if ( !_lastResponder || _lastResponder
== ibWindow
) {
358 // set default responder
359 [ibWindow makeFirstResponder
:ibCheatVariableTable
];
362 [ibWindow makeFirstResponder
:_lastResponder
];
364 _lastResponder
= responder
;
367 - (void)switchToSearchMode
369 NSResponder
*responder
= [ibWindow firstResponder
];
371 if ( [responder isKindOfClass
:[NSText
class]] ) {
372 responder
= [(NSText
*)responder delegate
];
375 if ( _mode
== TCSearchMode
) {
378 _mode
= TCSearchMode
;
379 [self _switchTo
:ibSearchContentView from
:ibCheatContentView
];
381 // update the next key view
382 [ibProcessPopup setNextKeyView
:ibSearchTypePopup
];
383 // update current key view
384 if ( !_lastResponder || _lastResponder
== ibWindow
) {
385 [ibWindow makeFirstResponder
:ibSearchValueField
];
388 [ibWindow makeFirstResponder
:_lastResponder
];
390 _lastResponder
= responder
;
394 - (void)_switchTo
:(NSView
*)destination from
:(NSView
*)source
396 NSView
*contentView
= [ibWindow contentView
];
397 NSRect frame
= [source frame
];
398 NSImage
*fadeImage
= nil;
400 if ( gFadeAnimationDuration
!= 0.0 && [source lockFocusIfCanDraw
] ) {
401 // draw the view to the representation
402 NSBitmapImageRep
*imageRep
= [[NSBitmapImageRep alloc
] initWithFocusedViewRect
:[source bounds
]];
403 [source unlockFocus
];
405 // create the image object
406 fadeImage
= [[NSImage alloc
] initWithSize
:frame.size
];
407 [fadeImage addRepresentation
:imageRep
];
410 // remove the old fade view
411 [_fadeView removeFromSuperview
];
415 // create the new fade view and start the fade
416 _fadeView
= [[FadeView alloc
] initWithFrame
:frame
];
417 [_fadeView setAutoresizingMask
:[source autoresizingMask
]];
418 [_fadeView setDelegate
:self];
419 [_fadeView setImage
:fadeImage
];
420 [_fadeView setFadeDuration
:gFadeAnimationDuration
];
421 [contentView addSubview
:_fadeView
];
422 [_fadeView startFadeAnimation
];
427 // update view size of incoming view
428 [destination setFrame
:frame
];
430 [contentView replaceSubview
:source with
:destination
];
435 - (void)fadeViewFinishedAnimation
:(FadeView
*)theView
437 [_fadeView removeFromSuperview
];
443 // #############################################################################
444 #pragma mark Accessors
445 // #############################################################################
447 - (NSString
*)defaultStatusString
450 return @
"Not Connected";
452 return [NSString stringWithFormat
:@
"Connected to %@.", [_cheater hostAddress
]];
455 - (BOOL)isLoadedFromFile
457 return ([self fileName
] != nil);
461 - (void)addServer
:(NSMenuItem
*)item
463 NSMenu
*serverMenu
= [ibServerPopup menu
];
466 if ( [serverMenu numberOfItems
] <= 2 ) {
468 [serverMenu addItem
:[NSMenuItem separatorItem
]];
470 [serverMenu addItem
:item
];
474 - (void)removeServerWithObject
:(id)serverObject
476 NSMenu
*serverMenu
= [ibServerPopup menu
];
478 if ( serverObject
) {
479 [serverMenu removeItemWithRepresentedObject
:serverObject
];
480 if ( [serverMenu numberOfItems
] == 3 ) {
482 [serverMenu removeItemAtIndex
:2];
488 // #############################################################################
489 #pragma mark Interface
490 // #############################################################################
492 - (void)_setupInitialInterface
495 NSMenuItem
*menuItem
;
497 NSArray
*cheatServices
;
500 // create and set the server popup menu
501 serverMenu
= [[NSMenu alloc
] init
];
502 [serverMenu setAutoenablesItems
:YES
];
504 // local connection item
505 menuItem
= [[NSMenuItem alloc
] init
];
506 [menuItem setTarget
:self];
507 [menuItem setAction
:@selector(ibSetLocalCheater
:)];
508 [menuItem setTitle
:@
"On This Computer"];
509 [menuItem setRepresentedObject
:[NSNull null
]];
510 [serverMenu addItem
:menuItem
];
512 // arbitrary connection item
513 menuItem
= [[NSMenuItem alloc
] init
];
514 [menuItem setTarget
:self];
515 [menuItem setAction
:@selector(ibRunCustomServerSheet
:)];
516 [menuItem setTitle
:[NSString stringWithFormat
:@
"Other Server%C", 0x2026]];
517 [menuItem setKeyEquivalent
:@
"k"];
518 [menuItem setKeyEquivalentModifierMask
:NSCommandKeyMask
];
519 [serverMenu addItem
:menuItem
];
522 [ibServerPopup setMenu
:serverMenu
];
523 [serverMenu release
];
525 // add current list of rendezvous services
526 cheatServices
= [CheatDocument cheatServices
];
527 len
= [cheatServices count
];
528 for ( i
= 0; i
< len
; i
++ ) {
529 NSNetService
*item
= [cheatServices objectAtIndex
:i
];
530 menuItem
= [[NSMenuItem alloc
] init
];
531 [menuItem setTarget
:self];
532 [menuItem setAction
:@selector(ibSetRemoteCheater
:)];
533 [menuItem setTitle
:[item name
]];
534 [menuItem setRepresentedObject
:item
];
535 [self addServer
:menuItem
];
539 [ibSearchVariableTable setDoubleAction
:@selector(ibAddSearchVariable
:)];
540 [ibSearchVariableTable setCanDelete
:NO
];
542 // BUG: for some reason IB doesn't like to set the default selection
543 // for an NSMatrix to anything but the first cell, so set this explicitly.
544 [ibSearchValueUsedMatrix selectCellWithTag
:TCGivenValue
];
546 // we use undoing/redoing for reverting search results
547 [self setHasUndoManager
:NO
];
550 - (void)updateInterface
554 // if there is cheat data, fill in the data information
555 [ibWindow setTitle
:[self displayName
]];
556 if ( [_cheatData process
] ) {
557 if ( [[_cheatData cheatInfo
] isEqualToString
:@
""] ) {
558 [ibCheatInfoText setStringValue
:[NSString stringWithFormat
:@
"%@ %@",
559 [_cheatData gameName
],
560 [_cheatData gameVersion
]]];
563 [ibCheatInfoText setStringValue
:[NSString stringWithFormat
:@
"%@ %@ - %@",
564 [_cheatData gameName
],
565 [_cheatData gameVersion
],
566 [_cheatData cheatInfo
]]];
570 [ibCheatInfoText setStringValue
:[_cheatData cheatInfo
]];
573 [ibCheatRepeatButton setState
:[_cheatData repeats
]];
574 [ibCheatRepeatField setDoubleValue
:[_cheatData repeatInterval
]];
577 // if we're connected...
580 if ( _status
== TCIdleStatus
)
583 [ibServerPopup setEnabled
:YES
];
584 [ibProcessPopup setEnabled
:YES
];
586 [ibSearchValueUsedMatrix setEnabled
:YES
];
587 if ( [_searchData hasSearchedOnce
] ) {
588 [ibSearchTypePopup setEnabled
:NO
];
589 [ibSearchIntegerSignMatrix setEnabled
:NO
];
590 [[ibSearchValueUsedMatrix cellWithTag
:TCLastValue
] setEnabled
:YES
];
591 if ( [_searchData valueUsed
] == TCGivenValue
) {
592 [ibSearchValueField setEnabled
:YES
];
595 [ibSearchValueField setEnabled
:NO
];
597 [ibSearchClearButton setEnabled
:YES
];
598 [ibSearchVariableTable setEnabled
:YES
];
599 int selectedRows
= [ibSearchVariableTable numberOfSelectedRows
];
600 if ( selectedRows
> 0 ) {
601 [ibSearchVariableButton setEnabled
:YES
];
604 [ibSearchVariableButton setEnabled
:NO
];
608 [ibSearchTypePopup setEnabled
:YES
];
609 if ( [_searchData isTypeInteger
] ) {
610 [ibSearchIntegerSignMatrix setEnabled
:YES
];
613 [ibSearchIntegerSignMatrix setEnabled
:NO
];
615 [[ibSearchValueUsedMatrix cellWithTag
:TCLastValue
] setEnabled
:NO
];
616 [ibSearchValueUsedMatrix selectCellWithTag
:[_searchData valueUsed
]];
617 [ibSearchValueField setEnabled
:YES
];
618 [ibSearchClearButton setEnabled
:NO
];
619 [ibSearchVariableTable setEnabled
:NO
];
620 [ibSearchVariableButton setEnabled
:NO
];
622 if ( [_searchData variableType
] != TCString
) {
623 [ibSearchOperatorPopup setEnabled
:YES
];
626 [ibSearchOperatorPopup setEnabled
:NO
];
628 [ibSearchButton setTitle
:@
"Search"];
629 [ibSearchButton setAction
:@selector(ibSearch
:)];
630 [ibSearchButton setKeyEquivalent
:@
""];
631 [ibSearchButton setEnabled
:YES
];
633 [ibCheatVariableTable setEnabled
:YES
];
634 [ibCheatRepeatButton setEnabled
:YES
];
635 [ibCheatRepeatAuxText setTextColor
:[NSColor controlTextColor
]];
636 [ibCheatRepeatField setEnabled
:[_cheatData repeats
]];
637 [ibCheatButton setTitle
:@
"Apply Cheat"];
638 [ibCheatButton setAction
:@selector(ibCheat
:)];
639 [ibCheatButton setKeyEquivalent
:@
"\r"];
640 if ( [_cheatData enabledVariableCount
] > 0 ) {
641 [ibCheatButton setEnabled
:YES
];
642 if ( [[_cheatData process
] sameApplicationAs
:_process
] ) {
643 [ibCheatButton setKeyEquivalent
:@
"\r"];
646 [ibCheatButton setKeyEquivalent
:@
""];
650 [ibCheatButton setEnabled
:NO
];
656 [ibServerPopup setEnabled
:NO
];
657 [ibProcessPopup setEnabled
:NO
];
659 [ibSearchTypePopup setEnabled
:NO
];
660 [ibSearchIntegerSignMatrix setEnabled
:NO
];
661 [ibSearchOperatorPopup setEnabled
:NO
];
662 [ibSearchValueUsedMatrix setEnabled
:NO
];
663 [ibSearchValueField setEnabled
:NO
];
664 [ibSearchClearButton setEnabled
:NO
];
665 [ibSearchVariableTable setEnabled
:NO
];
666 [ibSearchVariableButton setEnabled
:NO
];
668 [ibCheatVariableTable setEnabled
:NO
];
669 [ibCheatRepeatButton setEnabled
:NO
];
670 [ibCheatRepeatAuxText setTextColor
:[NSColor disabledControlTextColor
]];
671 [ibCheatRepeatField setEnabled
:NO
];
673 if ( _status
== TCSearchingStatus
) {
674 [ibSearchButton setTitle
:@
"Cancel"];
675 [ibSearchButton setAction
:@selector(ibCancelSearch
:)];
676 [ibSearchButton setKeyEquivalent
:@
"\E"];
677 [ibSearchButton setEnabled
:!_isCancelingTask
];
678 [ibCheatButton setTitle
:@
"Apply Cheat"];
679 [ibCheatButton setAction
:@selector(ibCheat
:)];
680 if ( [[_cheatData process
] sameApplicationAs
:_process
] ) {
681 [ibCheatButton setKeyEquivalent
:@
"\r"];
684 [ibCheatButton setKeyEquivalent
:@
""];
686 [ibCheatButton setEnabled
:NO
];
688 else if ( _status
== TCCheatingStatus
) {
689 [ibSearchButton setTitle
:@
"Search"];
690 [ibSearchButton setAction
:@selector(ibSearch
:)];
691 [ibSearchButton setKeyEquivalent
:@
""];
692 [ibSearchButton setEnabled
:NO
];
693 [ibCheatButton setTitle
:@
"Stop Cheat"];
694 [ibCheatButton setAction
:@selector(ibStopCheat
:)];
695 [ibCheatButton setKeyEquivalent
:@
"\E"];
696 [ibCheatButton setEnabled
:!_isCancelingTask
];
699 [ibSearchButton setTitle
:@
"Search"];
700 [ibSearchButton setAction
:@selector(ibSearch
:)];
701 [ibSearchButton setKeyEquivalent
:@
""];
702 [ibSearchButton setEnabled
:NO
];
703 [ibCheatButton setTitle
:@
"Apply Cheat"];
704 [ibCheatButton setAction
:@selector(ibCheat
:)];
705 if ( [[_cheatData process
] sameApplicationAs
:_process
] ) {
706 [ibCheatButton setKeyEquivalent
:@
"\r"];
709 [ibCheatButton setKeyEquivalent
:@
""];
711 [ibCheatButton setEnabled
:NO
];
718 [ibServerPopup setEnabled
:YES
];
719 [ibProcessPopup setEnabled
:NO
];
721 [ibSearchTypePopup setEnabled
:NO
];
722 [ibSearchIntegerSignMatrix setEnabled
:NO
];
723 [ibSearchOperatorPopup setEnabled
:NO
];
724 [ibSearchValueUsedMatrix setEnabled
:NO
];
725 [ibSearchValueField setEnabled
:NO
];
726 [ibSearchButton setEnabled
:NO
];
727 [ibSearchClearButton setEnabled
:NO
];
728 [ibSearchVariableTable setEnabled
:NO
];
729 [ibSearchVariableButton setEnabled
:NO
];
731 [ibCheatVariableTable setEnabled
:NO
];
732 [ibCheatRepeatButton setEnabled
:NO
];
733 [ibCheatRepeatAuxText setTextColor
:[NSColor disabledControlTextColor
]];
734 [ibCheatRepeatField setEnabled
:NO
];
735 [ibCheatButton setEnabled
:NO
];
740 - (void)setActualResults
:(unsigned)count
742 unsigned recieved
= [_searchData numberOfResults
];
745 [ibSearchVariableTable setToolTip
:@
""];
747 else if ( recieved
== count
) {
749 [ibSearchVariableTable setToolTip
:[NSString stringWithFormat
:@
"Displaying one result."]];
752 [ibSearchVariableTable setToolTip
:[NSString stringWithFormat
:@
"Displaying %i results.", count
]];
755 else if ( recieved
< count
) {
756 [ibSearchVariableTable setToolTip
:[NSString stringWithFormat
:@
"Displaying %i of %i results.", recieved
, count
]];
761 - (NSString
*)displayName
763 // override the default window title if there is a custom one
764 NSString
*title
= [_cheatData windowTitle
];
766 if ( !title ||
[title isEqualToString
:@
""] ) {
767 return [super displayName
];
772 - (BOOL)validateMenuItem
:(NSMenuItem
*)menuItem
774 //ChazLog( @"validate menuitem: %@", [menuItem title] );
777 if ( [menuItem action
] == @selector(ibUndo
:) ) {
778 if ( [_searchData undoesLeft
] > 0 ) {
785 if ( [menuItem action
] == @selector(ibRedo
:) ) {
786 if ( [_searchData redoesLeft
] > 0 ) {
794 // the add variables items
795 if ( [menuItem action
] == @selector(ibAddCheatVariable
:) && _status
== TCCheatingStatus
) {
799 // the 'pause' menu item
800 if ( [menuItem tag
] == 1000 ) {
804 if ( _isTargetPaused
) {
805 [menuItem setTitle
:@
"Resume Target"];
806 [menuItem setAction
:@selector(ibResumeTarget
:)];
809 [menuItem setTitle
:@
"Pause Target"];
810 [menuItem setAction
:@selector(ibPauseTarget
:)];
814 // the 'memory dump' menu item
815 else if ( [menuItem tag
] == 1001 ) {
816 if ( !_cheater || _status
!= TCIdleStatus
) {
820 // the 'mode switch' menu item
821 else if ( [menuItem tag
] == 1002 ) {
822 if ( _mode
== TCSearchMode
) {
823 [menuItem setTitle
:@
"Show Cheat Mode"];
825 else /* _mode == TCCheatMode */ {
826 [menuItem setTitle
:@
"Show Search Mode"];
829 // the 'edit variables' menu item
830 else if ( [menuItem tag
] == 1003 ) {
831 return (_mode
== TCCheatMode
&& [ibCheatVariableTable selectedRow
] != -1);
833 // the 'clear search' menu item
834 else if ( [menuItem tag
] == 1004 ) {
835 return [ibSearchClearButton isEnabled
];
837 // the cancel menu item
838 else if ( [menuItem tag
] == 1005 ) {
839 if ( !_cheater || _isCancelingTask
) {
842 if ( _status
== TCSearchingStatus
) {
843 [menuItem setTitle
:@
"Cancel Search"];
844 [menuItem setAction
:@selector(ibCancelSearch
:)];
846 else if ( _status
== TCCheatingStatus
) {
847 [menuItem setTitle
:@
"Stop Cheat"];
848 [menuItem setAction
:@selector(ibStopCheat
:)];
850 else if ( _status
== TCDumpingStatus
) {
851 [menuItem setTitle
:@
"Cancel Dump"];
852 [menuItem setAction
:@selector(ibCancelDump
:)];
859 return [super validateMenuItem
:menuItem
];
863 - (void)setDocumentChanged
865 // only count document changes if there are variables
866 // and if the pref is set
867 if ( [[NSUserDefaults standardUserDefaults
] boolForKey
:TCAskForSavePref
] &&
868 ([_cheatData variableCount
] > 0 ||
[self isLoadedFromFile
]) ) {
869 [self updateChangeCount
:NSChangeDone
];
874 - (int)numberOfRowsInTableView
:(NSTableView
*)aTableView
876 if ( aTableView
== ibCheatVariableTable
) {
877 return [_cheatData variableCount
];
879 else if ( aTableView
== ibSearchVariableTable
) {
880 return [_searchData numberOfResults
];
885 - (id)tableView
:(NSTableView
*)aTableView objectValueForTableColumn
:(NSTableColumn
*)aTableColumn row
:(int)rowIndex
887 NSString
*identifier
= [aTableColumn identifier
];
889 if ( aTableView
== ibCheatVariableTable
) {
890 Variable
*variable
= [_cheatData variableAtIndex
:rowIndex
];
892 if ( [identifier isEqualToString
:@
"enable"] ) {
893 return [NSNumber numberWithBool
:[variable isEnabled
]];
895 else if ( [identifier isEqualToString
:@
"variable"] ) {
896 return [variable typeString
];
898 else if ( [identifier isEqualToString
:@
"address"] ) {
899 return [variable addressString
];
901 else if ( [identifier isEqualToString
:@
"value"] ) {
902 return [variable stringValue
];
905 else if ( aTableView
== ibSearchVariableTable
) {
906 return [_searchData stringForRow
:rowIndex
];
911 - (void)tableView
:(NSTableView
*)aTableView setObjectValue
:(id)anObject forTableColumn
:(NSTableColumn
*)aTableColumn row
:(int)rowIndex
913 NSString
*identifier
= [aTableColumn identifier
];
915 if ( aTableView
== ibCheatVariableTable
) {
916 Variable
*variable
= [_cheatData variableAtIndex
:rowIndex
];
918 if ( [identifier isEqualToString
:@
"address"] ) {
919 if ( [variable setAddressString
:anObject
] ) {
920 [self setDocumentChanged
];
923 else if ( [identifier isEqualToString
:@
"value"] ) {
924 if ( [variable setStringValue
:anObject
] ) {
925 [self setDocumentChanged
];
931 - (void)tableViewSelectionDidChange
:(NSNotification
*)aNotification
933 NSTableView
*aTableView
= [aNotification object
];
935 if ( aTableView
== ibSearchVariableTable
) {
936 int selectedRows
= [aTableView numberOfSelectedRows
];
937 if ( selectedRows
> 1 ) {
938 [ibSearchVariableButton setTitle
:@
"Add Variables"];
940 else if ( selectedRows
== 1 ) {
941 [ibSearchVariableButton setTitle
:@
"Add Variable"];
947 - (BOOL)tableViewDidReceiveEnterKey
:(NSTableView
*)tableView
949 if ( tableView
== ibSearchVariableTable
) {
950 [ibSearchVariableButton performClick
:nil];
956 - (BOOL)tableViewDidReceiveSpaceKey
:(NSTableView
*)tableView
958 if ( tableView
== ibCheatVariableTable
) {
959 [self ibSetVariableEnabled
:nil];
965 - (NSString
*)tableViewPasteboardType
:(NSTableView
*)tableView
967 return @
"TCVariablePboardType";
970 - (NSData
*)tableView
:(NSTableView
*)tableView copyRows
:(NSArray
*)rows
972 NSMutableArray
*vars
;
976 vars
= [[NSMutableArray alloc
] initWithCapacity
:top
];
978 // add the new variables
979 if ( tableView
== ibSearchVariableTable
) {
980 for ( i
= 0; i
< top
; i
++ ) {
981 unsigned index
= [[rows objectAtIndex
:i
] unsignedIntValue
];
982 [vars addObject
:[_searchData variableAtIndex
:index
]];
986 for ( i
= 0; i
< top
; i
++ ) {
987 unsigned index
= [[rows objectAtIndex
:i
] unsignedIntValue
];
988 [vars addObject
:[_cheatData variableAtIndex
:index
]];
992 return [NSArchiver archivedDataWithRootObject
:[vars autorelease
]];
995 - (void)tableView
:(NSTableView
*)tableView pasteRowsWithData
:(NSData
*)rowData
997 NSArray
*vars
= [NSUnarchiver unarchiveObjectWithData
:rowData
];
1000 if ( tableView
== ibSearchVariableTable
) {
1006 for ( i
= 0; i
< top
; i
++ ) {
1007 Variable
*var
= [vars objectAtIndex
:i
];
1008 [_cheatData addVariable
:var
];
1011 lastRow
= [_cheatData variableCount
]-1;
1012 [tableView reloadData
];
1013 if ( MacOSXVersion() >= 0x1030 ) {
1014 [tableView selectRowIndexes
:[NSIndexSet indexSetWithIndex
:lastRow
] byExtendingSelection
:NO
];
1017 [tableView selectRow
:lastRow byExtendingSelection
:NO
];
1019 [tableView scrollRowToVisible
:lastRow
];
1021 [self setDocumentChanged
];
1022 [self updateInterface
];
1025 - (void)tableView
:(NSTableView
*)tableView deleteRows
:(NSArray
*)rows
1029 if ( tableView
== ibCheatVariableTable
) {
1031 for ( i
= len
-1; i
>= 0; i
-- ) {
1032 [_cheatData removeVariableAtIndex
:[[rows objectAtIndex
:i
] unsignedIntValue
]];
1034 // reselect the last item if the selection is now invalid
1035 len
= [_cheatData variableCount
] - 1;
1036 if ( [tableView selectedRow
] > len
) {
1037 if ( MacOSXVersion() >= 0x1030 ) {
1038 [tableView selectRowIndexes
:[NSIndexSet indexSetWithIndex
:len
] byExtendingSelection
:NO
];
1041 [tableView selectRow
:len byExtendingSelection
:NO
];
1044 [tableView reloadData
];
1046 [self setDocumentChanged
];
1047 [self updateInterface
];
1051 // VariableTable Delegate
1052 - (void)tableView
:(NSTableView
*)aTableView didChangeVisibleRows
:(NSRange
)rows
1054 ChazLog( @
"new visible rows: %@", NSStringFromRange( rows
) );
1055 if ( [_searchData valuesLoaded
] ) {
1056 [self watchVariables
];
1061 // #############################################################################
1062 #pragma mark Utility
1063 // #############################################################################
1065 + (void)setGlobalTarget
:(Process
*)target
1068 [_tc_target release
];
1069 _tc_target
= target
;
1072 + (Process
*)globalTarget
1078 - (void)showError
:(NSString
*)error
1080 NSColor
*red
= [NSColor colorWithCalibratedRed
:0.7 green
:0.0 blue
:0.0 alpha
:1.0];
1082 [ibStatusText setDefaultStatus
:[self defaultStatusString
]];
1083 [ibStatusText setTemporaryStatus
:[NSString stringWithFormat
:@
"Error: %@", error
] color
:red duration
:7.0];
1087 - (BOOL)shouldConnectWithServer
:(NSMenuItem
*)item
1091 if ( _resolvingService
) {
1092 // don't connect if a service is being resolved
1093 [ibServerPopup selectItemAtIndex
:[ibServerPopup indexOfItemWithRepresentedObject
:_resolvingService
]];
1097 if ( [item respondsToSelector
:@selector(representedObject
)] ) {
1098 serverObject
= [item representedObject
];
1101 serverObject
= [NSNull null
];
1104 if ( [_serverObject isEqual
:serverObject
] ) {
1105 // already connected, don't connect
1111 - (void)selectConnectedCheater
1113 int index
= [ibServerPopup indexOfItemWithRepresentedObject
:_serverObject
];
1114 if ( index
!= -1 ) {
1115 [ibServerPopup selectItemAtIndex
:index
];
1119 - (void)connectWithServer
:(NSMenuItem
*)item
1123 if ( [item respondsToSelector
:@selector(representedObject
)] ) {
1124 serverObject
= [item representedObject
];
1127 serverObject
= [NSNull null
];
1130 // save a reference to the server object
1131 [serverObject retain
];
1132 [_serverObject release
];
1133 _serverObject
= serverObject
;
1136 - (void)disconnectFromCheater
1140 // don't do anything if we are already disconnected
1145 _status
= TCIdleStatus
;
1148 [_searchData clearResults
];
1150 //[ibSearchVariableTable reloadData]; // this can cause a crash, so commenting it out for now.
1151 // clear the selected process
1154 [_serverObject release
];
1155 _serverObject
= nil;
1157 if ( ![self isLoadedFromFile
] ) {
1158 [_cheatData setProcess
:nil];
1161 // clear the process menu
1162 blankMenu
= [[NSMenu alloc
] initWithTitle
:@
""];
1163 [blankMenu addItemWithTitle
:@
"" action
:NULL keyEquivalent
:@
""];
1164 [ibProcessPopup setMenu
:blankMenu
];
1165 [blankMenu release
];
1167 // kill the connection
1168 [_cheater disconnect
];
1174 - (void)setConnectOnOpen
:(BOOL)flag
1176 _connectsOnOpen
= flag
;
1179 - (void)connectWithURL
:(NSString
*)url
1181 NSMenu
*serverMenu
= [ibServerPopup menu
];
1182 NSURL
*theUrl
= [NSURL URLWithString
:url
];
1188 int indexIfAlreadyExists
;
1190 host
= [theUrl host
];
1191 port
= [[theUrl port
] intValue
];
1194 NSBeginInformationalAlertSheet( @
"The Cheat can't parse the URL.", @
"OK", nil, nil, ibWindow
, self, NULL
, NULL
, NULL
,
1195 @
"The Cheat can't connect to the server because \"%@\" is not a valid URL.", url
);
1199 // use default port number
1201 port
= TCDefaultListenPort
;
1204 addrData
= [MySocket addressWithHost
:host port
:port
];
1206 NSBeginInformationalAlertSheet( @
"The Cheat can't find the server.", @
"OK", nil, nil, ibWindow
, self, NULL
, NULL
, NULL
,
1207 @
"The Cheat can't connect to the server \"%@\" because it can't be found.", host
);
1211 indexIfAlreadyExists
= [serverMenu indexOfItemWithRepresentedObject
:addrData
];
1212 if ( indexIfAlreadyExists
== -1 ) {
1213 NSMenuItem
*menuItem
;
1214 // add the newly found service to the server popup
1215 menuItem
= [[NSMenuItem alloc
] init
];
1216 [menuItem setTarget
:self];
1217 [menuItem setAction
:@selector(ibSetCustomCheater
:)];
1218 [menuItem setTitle
:[NSString stringWithFormat
:@
"%@:%i", host
, port
]];
1219 [menuItem setRepresentedObject
:addrData
];
1220 [self addServer
:menuItem
];
1222 [self ibSetCustomCheater
:menuItem
];
1227 // select matching item
1228 [self ibSetCustomCheater
:[serverMenu itemAtIndex
:indexIfAlreadyExists
]];
1232 [self selectConnectedCheater
];
1236 - (void)watchVariables
1240 if ( [[NSUserDefaults standardUserDefaults
] boolForKey
:TCDisplayValuesPref
] ) {
1241 float interval
= [[NSUserDefaults standardUserDefaults
] floatForKey
:TCValueUpdatePref
];
1243 range
= [ibSearchVariableTable visibleRows
];
1244 [_cheater watchVariablesAtIndex
:range.location count
:range.length interval
:interval
];
1247 [_cheater stopWatchingVariables
];
1252 // #############################################################################
1253 #pragma mark Notifications
1254 // #############################################################################
1256 - (void)_displayValuesPrefChanged
:(NSNotification
*)note
1258 [self watchVariables
];
1261 - (void)_windowOnTopPrefChanged
:(NSNotification
*)note
1263 ChazLog( @
"_windowOnTopPrefChanged" );
1264 if ( [[NSUserDefaults standardUserDefaults
] boolForKey
:TCWindowsOnTopPref
] ) {
1265 [ibWindow setLevel
:NSPopUpMenuWindowLevel
];
1268 [ibWindow setLevel
:NSNormalWindowLevel
];
1272 - (void)_hitsDisplayedPrefChanged
:(NSNotification
*)note
1274 // send preferences to the cheater
1275 [_cheater limitReturnedResults
:[[NSUserDefaults standardUserDefaults
] integerForKey
:TCHitsDisplayedPref
]];