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
];
82 // show search mode when documents are first created
83 _connectsOnOpen
= YES
;
84 [self setMode
:TCSearchMode
];
89 - (id)initWithContentsOfFile
:(NSString
*)fileName ofType
:(NSString
*)docType
91 if ( self = [super initWithContentsOfFile
:fileName ofType
:docType
] )
93 // if document opened from a file, show cheat mode by default
94 [self setMode
:TCCheatMode
];
99 - (id)initWithContentsOfURL
:(NSURL
*)aURL ofType
:(NSString
*)docType
101 if ( self = [super initWithContentsOfURL
:aURL ofType
:docType
] )
103 // if document opened from a URL, show cheat mode by default
104 [self setMode
:TCCheatMode
];
111 ChazLog( @
"dealloc doc %X", self );
113 // unregister observers
114 [(NSNotificationCenter
*)[NSNotificationCenter defaultCenter
] removeObserver
:self];
116 [_cheater setDelegate
:nil];
117 [self disconnectFromCheater
];
119 [_cheatData release
];
120 [_searchData release
];
122 [_serverObject release
];
125 // release the fade if one is occuring
126 [_fadeView removeFromSuperview
];
129 [CheatDocument _documentDestroyed
];
135 // #############################################################################
136 #pragma mark Nib Loading
137 // #############################################################################
139 - (NSString
*)windowNibName
141 return @
"CheatDocument";
144 - (void)windowControllerDidLoadNib
:(NSWindowController
*)aController
146 [super windowControllerDidLoadNib
:aController
];
148 NSNotificationCenter
*nc
= [NSNotificationCenter defaultCenter
];
150 // register for app launch/quit notifications
151 [nc addObserver
:self selector
:@selector(_displayValuesPrefChanged
:) name
:TCDisplayValuesChangedNote object
:nil];
152 [nc addObserver
:self selector
:@selector(_windowOnTopPrefChanged
:) name
:TCWindowsOnTopChangedNote object
:nil];
153 [nc addObserver
:self selector
:@selector(_hitsDisplayedPrefChanged
:) name
:TCHitsDisplayedChangedNote object
:nil];
155 // setup window frame saving
156 [ibWindow useOptimizedDrawing
:YES
];
157 [ibWindow setFrameAutosaveName
:@
"TCCheatWindow"];
160 if ( [[NSUserDefaults standardUserDefaults
] boolForKey
:TCWindowsOnTopPref
] )
162 [ibWindow setLevel
:NSPopUpMenuWindowLevel
];
165 // display one of the modes
166 if ( _mode
== TCCheatMode
) {
167 [self _switchTo
:ibCheatContentView from
:ibPlaceView
];
169 else if ( _mode
== TCSearchMode
) {
170 [self _switchTo
:ibSearchContentView from
:ibPlaceView
];
173 // configure the initial interface
174 [self _setupInitialInterface
];
177 [ibStatusText setDefaultStatus
:[self defaultStatusString
]];
178 [self updateInterface
];
180 // automatically connect to the local cheater
181 if ( _connectsOnOpen
) {
182 [self ibSetLocalCheater
:nil];
185 ChazLog( @
"superview: %@", [[ibSearchVariableTable superview
] superview
] );
189 // #############################################################################
190 #pragma mark Handling Files
191 // #############################################################################
193 - (NSData
*)dataRepresentationOfType
:(NSString
*)type
195 return [NSArchiver archivedDataWithRootObject
:_cheatData
];
198 - (BOOL)loadDataRepresentation
:(NSData
*)data ofType
:(NSString
*)type
200 [_cheatData release
];
203 if ( [type isEqualToString
:@
"Cheat Document"] ) {
205 _cheatData
= [[NSUnarchiver unarchiveObjectWithData
:data
] retain
];
208 // alert the user of the unparsable file
210 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
] );
216 [self updateInterface
];
222 // #############################################################################
223 #pragma mark Service Finding
224 // #############################################################################
226 + (NSArray
*)cheatServices
228 return [NSArray arrayWithArray
:_tc_cheat_services
];
232 - (void)_cheatServiceFound
:(NSNotification
*)note
234 NSMenuItem
*menuItem
;
235 NSNetService
*item
= [note object
];
237 // add the newly found service to the server popup
238 menuItem
= [[NSMenuItem alloc
] init
];
239 [menuItem setTarget
:self];
240 [menuItem setAction
:@selector(ibSetRemoteCheater
:)];
241 [menuItem setTitle
:[item name
]];
242 [menuItem setRepresentedObject
:item
];
243 [self addServer
:menuItem
];
247 - (void)_cheatServiceRemoved
:(NSNotification
*)note
249 NSNetService
*item
= [note object
];
251 // remove the service from the menu
252 [self removeServerWithObject
:item
];
256 // using the service browser
257 + (void)_documentCreated
259 _tc_document_count
++;
261 if ( _tc_document_count
== 1 ) {
262 // first document created, so start the service browser
263 [_tc_service_browser stop
];
264 [_tc_cheat_services release
];
265 // create and setup the browser
266 _tc_service_browser
= [[NSNetServiceBrowser alloc
] init
];
267 [_tc_service_browser setDelegate
:self];
268 [_tc_service_browser searchForServicesOfType
:@
"_cheat._tcp." inDomain
:@
""];
269 // create the service array
270 _tc_cheat_services
= [[NSMutableArray alloc
] init
];
274 + (void)_documentDestroyed
276 _tc_document_count
--;
278 if ( _tc_document_count
== 0 ) {
279 // last document destroyed, so stop the service browser
280 [_tc_service_browser stop
];
281 [_tc_cheat_services release
];
282 // set the globals to nil for safety
283 _tc_service_browser
= nil;
284 _tc_cheat_services
= nil;
289 // NSNetServiceBrowser delegate methods
290 + (void)netServiceBrowserWillSearch
:(NSNetServiceBrowser
*)browser
292 ChazLog( @
"service browser will search" );
295 + (void)netServiceBrowserDidStopSearch
:(NSNetServiceBrowser
*)browser
297 // if the browser stops we assume it needs to die.
298 ChazLog( @
"service browser did stop search" );
302 + (void)netServiceBrowser
:(NSNetServiceBrowser
*)browser didNotSearch
:(NSDictionary
*)errorDict
304 ChazLog( @
"service browser failed with error code: %i", [[errorDict objectForKey
:NSNetServicesErrorCode
] intValue
] );
307 + (void)netServiceBrowser
:(NSNetServiceBrowser
*)browser didFindService
:(NSNetService
*)aNetService moreComing
:(BOOL)moreComing
309 ChazLog( @
"service browser found service: %@", [aNetService name
] );
311 // ignore if this is the local server.
312 if ( [[(AppController
*)NSApp cheatServer
] isListening
] &&
313 [[aNetService name
] isEqualToString
:[[NSUserDefaults standardUserDefaults
] objectForKey
:TCBroadcastNamePref
]] ) {
317 [_tc_cheat_services addObject
:aNetService
];
318 // send a notification for the new service
319 [[NSNotificationCenter defaultCenter
] postNotificationName
:TCServiceFoundNote object
:aNetService
];
322 + (void)netServiceBrowser
:(NSNetServiceBrowser
*)browser didRemoveService
:(NSNetService
*)aNetService moreComing
:(BOOL)moreComing
324 ChazLog( @
"service browser removed service: %@", [aNetService name
] );
326 [_tc_cheat_services removeObject
:aNetService
];
327 // send a notification for the new service
328 [[NSNotificationCenter defaultCenter
] postNotificationName
:TCServiceRemovedNote object
:aNetService
];
332 // #############################################################################
333 #pragma mark Changing Mode
334 // #############################################################################
336 - (void)setMode
:(TCDocumentMode
)mode
338 // if the nib isn't loaded, change the mode
345 - (void)switchToCheatMode
347 NSResponder
*responder
= [ibWindow firstResponder
];
349 if ( [responder isKindOfClass
:[NSText
class]] ) {
350 /* Since text views et al. make the field editor the first
351 responder, you have to take its delegate since that will
352 be set to the actual text view. */
353 responder
= [(NSText
*)responder delegate
];
356 if ( _mode
== TCCheatMode
) {
360 [self _switchTo
:ibCheatContentView from
:ibSearchContentView
];
362 // update the next key view
363 [ibProcessPopup setNextKeyView
:ibCheatVariableTable
];
364 // update current key view
365 if ( !_lastResponder || _lastResponder
== ibWindow
) {
366 // set default responder
367 [ibWindow makeFirstResponder
:ibCheatVariableTable
];
370 [ibWindow makeFirstResponder
:_lastResponder
];
372 _lastResponder
= responder
;
375 - (void)switchToSearchMode
377 NSResponder
*responder
= [ibWindow firstResponder
];
379 if ( [responder isKindOfClass
:[NSText
class]] ) {
380 responder
= [(NSText
*)responder delegate
];
383 if ( _mode
== TCSearchMode
) {
386 _mode
= TCSearchMode
;
387 [self _switchTo
:ibSearchContentView from
:ibCheatContentView
];
389 // update the next key view
390 [ibProcessPopup setNextKeyView
:ibSearchTypePopup
];
391 // update current key view
392 if ( !_lastResponder || _lastResponder
== ibWindow
) {
393 [ibWindow makeFirstResponder
:ibSearchValueField
];
396 [ibWindow makeFirstResponder
:_lastResponder
];
398 _lastResponder
= responder
;
402 - (void)_switchTo
:(NSView
*)destination from
:(NSView
*)source
404 NSView
*contentView
= [ibWindow contentView
];
405 NSRect frame
= [source frame
];
406 NSImage
*fadeImage
= nil;
408 if ( gFadeAnimationDuration
!= 0.0 && [source lockFocusIfCanDraw
] ) {
409 // draw the view to the representation
410 NSBitmapImageRep
*imageRep
= [[NSBitmapImageRep alloc
] initWithFocusedViewRect
:[source bounds
]];
411 [source unlockFocus
];
413 // create the image object
414 fadeImage
= [[NSImage alloc
] initWithSize
:frame.size
];
415 [fadeImage addRepresentation
:imageRep
];
418 // remove the old fade view
419 [_fadeView removeFromSuperview
];
423 // create the new fade view and start the fade
424 _fadeView
= [[FadeView alloc
] initWithFrame
:frame
];
425 [_fadeView setAutoresizingMask
:[source autoresizingMask
]];
426 [_fadeView setDelegate
:self];
427 [_fadeView setImage
:fadeImage
];
428 [_fadeView setFadeDuration
:gFadeAnimationDuration
];
429 [contentView addSubview
:_fadeView
];
430 [_fadeView startFadeAnimation
];
435 // update view size of incoming view
436 [destination setFrame
:frame
];
438 [contentView replaceSubview
:source with
:destination
];
443 - (void)fadeViewFinishedAnimation
:(FadeView
*)theView
445 [_fadeView removeFromSuperview
];
451 // #############################################################################
452 #pragma mark Accessors
453 // #############################################################################
455 - (NSString
*)defaultStatusString
458 return @
"Not Connected";
460 return [NSString stringWithFormat
:@
"Connected to %@.", [_cheater hostAddress
]];
463 - (BOOL)isLoadedFromFile
465 return ([self fileName
] != nil);
469 - (void)addServer
:(NSMenuItem
*)item
471 NSMenu
*serverMenu
= [ibServerPopup menu
];
474 if ( [serverMenu numberOfItems
] <= 2 ) {
476 [serverMenu addItem
:[NSMenuItem separatorItem
]];
478 [serverMenu addItem
:item
];
482 - (void)removeServerWithObject
:(id)serverObject
484 NSMenu
*serverMenu
= [ibServerPopup menu
];
486 if ( serverObject
) {
487 [serverMenu removeItemWithRepresentedObject
:serverObject
];
488 if ( [serverMenu numberOfItems
] == 3 ) {
490 [serverMenu removeItemAtIndex
:2];
496 // #############################################################################
497 #pragma mark Interface
498 // #############################################################################
500 - (void)_setupInitialInterface
503 NSMenuItem
*menuItem
;
505 NSArray
*cheatServices
;
508 // create and set the server popup menu
509 serverMenu
= [[NSMenu alloc
] init
];
510 [serverMenu setAutoenablesItems
:YES
];
512 // local connection item
513 menuItem
= [[NSMenuItem alloc
] init
];
514 [menuItem setTarget
:self];
515 [menuItem setAction
:@selector(ibSetLocalCheater
:)];
516 [menuItem setTitle
:@
"On This Computer"];
517 [menuItem setRepresentedObject
:[NSNull null
]];
518 [serverMenu addItem
:menuItem
];
520 // arbitrary connection item
521 menuItem
= [[NSMenuItem alloc
] init
];
522 [menuItem setTarget
:self];
523 [menuItem setAction
:@selector(ibRunCustomServerSheet
:)];
524 [menuItem setTitle
:[NSString stringWithFormat
:@
"Other Server%C", 0x2026]];
525 [menuItem setKeyEquivalent
:@
"k"];
526 [menuItem setKeyEquivalentModifierMask
:NSCommandKeyMask
];
527 [serverMenu addItem
:menuItem
];
530 [ibServerPopup setMenu
:serverMenu
];
531 [serverMenu release
];
533 // add current list of rendezvous services
534 cheatServices
= [CheatDocument cheatServices
];
535 len
= [cheatServices count
];
536 for ( i
= 0; i
< len
; i
++ ) {
537 NSNetService
*item
= [cheatServices objectAtIndex
:i
];
538 menuItem
= [[NSMenuItem alloc
] init
];
539 [menuItem setTarget
:self];
540 [menuItem setAction
:@selector(ibSetRemoteCheater
:)];
541 [menuItem setTitle
:[item name
]];
542 [menuItem setRepresentedObject
:item
];
543 [self addServer
:menuItem
];
547 [ibSearchVariableTable setDoubleAction
:@selector(ibAddSearchVariable
:)];
548 [ibSearchVariableTable setCanDelete
:NO
];
550 // BUG: for some reason IB doesn't like to set the default selection
551 // for an NSMatrix to anything but the first cell, so set this explicitly.
552 [ibSearchValueUsedMatrix selectCellWithTag
:TCGivenValue
];
554 // we use undoing/redoing for reverting search results
555 [self setHasUndoManager
:NO
];
558 - (void)updateInterface
562 // if there is cheat data, fill in the data information
563 [ibWindow setTitle
:[self displayName
]];
564 if ( [_cheatData process
] ) {
565 if ( [[_cheatData cheatInfo
] isEqualToString
:@
""] ) {
566 [ibCheatInfoText setStringValue
:[NSString stringWithFormat
:@
"%@ %@",
567 [_cheatData gameName
],
568 [_cheatData gameVersion
]]];
571 [ibCheatInfoText setStringValue
:[NSString stringWithFormat
:@
"%@ %@ - %@",
572 [_cheatData gameName
],
573 [_cheatData gameVersion
],
574 [_cheatData cheatInfo
]]];
578 [ibCheatInfoText setStringValue
:[_cheatData cheatInfo
]];
581 [ibCheatRepeatButton setState
:[_cheatData repeats
]];
582 [ibCheatRepeatField setDoubleValue
:[_cheatData repeatInterval
]];
585 // if we're connected...
588 if ( _status
== TCIdleStatus
)
591 [ibServerPopup setEnabled
:YES
];
592 [ibProcessPopup setEnabled
:YES
];
594 [ibSearchValueUsedMatrix setEnabled
:YES
];
595 if ( [_searchData hasSearchedOnce
] ) {
596 [ibSearchTypePopup setEnabled
:NO
];
597 [ibSearchIntegerSignMatrix setEnabled
:NO
];
598 [[ibSearchValueUsedMatrix cellWithTag
:TCLastValue
] setEnabled
:YES
];
599 if ( [_searchData valueUsed
] == TCGivenValue
) {
600 [ibSearchValueField setEnabled
:YES
];
603 [ibSearchValueField setEnabled
:NO
];
605 [ibSearchClearButton setEnabled
:YES
];
606 [ibSearchVariableTable setEnabled
:YES
];
607 int selectedRows
= [ibSearchVariableTable numberOfSelectedRows
];
608 if ( selectedRows
> 0 ) {
609 [ibSearchVariableButton setEnabled
:YES
];
612 [ibSearchVariableButton setEnabled
:NO
];
616 [ibSearchTypePopup setEnabled
:YES
];
617 if ( [_searchData isTypeInteger
] ) {
618 [ibSearchIntegerSignMatrix setEnabled
:YES
];
621 [ibSearchIntegerSignMatrix setEnabled
:NO
];
623 [[ibSearchValueUsedMatrix cellWithTag
:TCLastValue
] setEnabled
:NO
];
624 [ibSearchValueUsedMatrix selectCellWithTag
:[_searchData valueUsed
]];
625 [ibSearchValueField setEnabled
:YES
];
626 [ibSearchClearButton setEnabled
:NO
];
627 [ibSearchVariableTable setEnabled
:NO
];
628 [ibSearchVariableButton setEnabled
:NO
];
630 if ( [_searchData variableType
] != TCString
) {
631 [ibSearchOperatorPopup setEnabled
:YES
];
634 [ibSearchOperatorPopup setEnabled
:NO
];
636 [ibSearchButton setTitle
:@
"Search"];
637 [ibSearchButton setAction
:@selector(ibSearch
:)];
638 [ibSearchButton setKeyEquivalent
:@
""];
639 [ibSearchButton setEnabled
:YES
];
641 [ibCheatVariableTable setEnabled
:YES
];
642 [ibCheatRepeatButton setEnabled
:YES
];
643 [ibCheatRepeatAuxText setTextColor
:[NSColor controlTextColor
]];
644 [ibCheatRepeatField setEnabled
:[_cheatData repeats
]];
645 [ibCheatButton setTitle
:@
"Apply Cheat"];
646 [ibCheatButton setAction
:@selector(ibCheat
:)];
647 [ibCheatButton setKeyEquivalent
:@
"\r"];
648 if ( [_cheatData enabledVariableCount
] > 0 ) {
649 [ibCheatButton setEnabled
:YES
];
650 if ( [[_cheatData process
] sameApplicationAs
:_process
] ) {
651 [ibCheatButton setKeyEquivalent
:@
"\r"];
654 [ibCheatButton setKeyEquivalent
:@
""];
658 [ibCheatButton setEnabled
:NO
];
664 [ibServerPopup setEnabled
:NO
];
665 [ibProcessPopup setEnabled
:NO
];
667 [ibSearchTypePopup setEnabled
:NO
];
668 [ibSearchIntegerSignMatrix setEnabled
:NO
];
669 [ibSearchOperatorPopup setEnabled
:NO
];
670 [ibSearchValueUsedMatrix setEnabled
:NO
];
671 [ibSearchValueField setEnabled
:NO
];
672 [ibSearchClearButton setEnabled
:NO
];
673 [ibSearchVariableTable setEnabled
:NO
];
674 [ibSearchVariableButton setEnabled
:NO
];
676 [ibCheatVariableTable setEnabled
:NO
];
677 [ibCheatRepeatButton setEnabled
:NO
];
678 [ibCheatRepeatAuxText setTextColor
:[NSColor disabledControlTextColor
]];
679 [ibCheatRepeatField setEnabled
:NO
];
681 if ( _status
== TCSearchingStatus
) {
682 [ibSearchButton setTitle
:@
"Cancel"];
683 [ibSearchButton setAction
:@selector(ibCancelSearch
:)];
684 [ibSearchButton setKeyEquivalent
:@
"\E"];
685 [ibSearchButton setEnabled
:!_isCancelingTask
];
686 [ibCheatButton setTitle
:@
"Apply Cheat"];
687 [ibCheatButton setAction
:@selector(ibCheat
:)];
688 if ( [[_cheatData process
] sameApplicationAs
:_process
] ) {
689 [ibCheatButton setKeyEquivalent
:@
"\r"];
692 [ibCheatButton setKeyEquivalent
:@
""];
694 [ibCheatButton setEnabled
:NO
];
696 else if ( _status
== TCCheatingStatus
) {
697 [ibSearchButton setTitle
:@
"Search"];
698 [ibSearchButton setAction
:@selector(ibSearch
:)];
699 [ibSearchButton setKeyEquivalent
:@
""];
700 [ibSearchButton setEnabled
:NO
];
701 [ibCheatButton setTitle
:@
"Stop Cheat"];
702 [ibCheatButton setAction
:@selector(ibStopCheat
:)];
703 [ibCheatButton setKeyEquivalent
:@
"\E"];
704 [ibCheatButton setEnabled
:!_isCancelingTask
];
707 [ibSearchButton setTitle
:@
"Search"];
708 [ibSearchButton setAction
:@selector(ibSearch
:)];
709 [ibSearchButton setKeyEquivalent
:@
""];
710 [ibSearchButton setEnabled
:NO
];
711 [ibCheatButton setTitle
:@
"Apply Cheat"];
712 [ibCheatButton setAction
:@selector(ibCheat
:)];
713 if ( [[_cheatData process
] sameApplicationAs
:_process
] ) {
714 [ibCheatButton setKeyEquivalent
:@
"\r"];
717 [ibCheatButton setKeyEquivalent
:@
""];
719 [ibCheatButton setEnabled
:NO
];
726 [ibServerPopup setEnabled
:YES
];
727 [ibProcessPopup setEnabled
:NO
];
729 [ibSearchTypePopup setEnabled
:NO
];
730 [ibSearchIntegerSignMatrix setEnabled
:NO
];
731 [ibSearchOperatorPopup setEnabled
:NO
];
732 [ibSearchValueUsedMatrix setEnabled
:NO
];
733 [ibSearchValueField setEnabled
:NO
];
734 [ibSearchButton setEnabled
:NO
];
735 [ibSearchClearButton setEnabled
:NO
];
736 [ibSearchVariableTable setEnabled
:NO
];
737 [ibSearchVariableButton setEnabled
:NO
];
739 [ibCheatVariableTable setEnabled
:NO
];
740 [ibCheatRepeatButton setEnabled
:NO
];
741 [ibCheatRepeatAuxText setTextColor
:[NSColor disabledControlTextColor
]];
742 [ibCheatRepeatField setEnabled
:NO
];
743 [ibCheatButton setEnabled
:NO
];
748 - (void)setActualResults
:(unsigned)count
750 unsigned recieved
= [_searchData numberOfResults
];
753 [ibSearchVariableTable setToolTip
:@
""];
755 else if ( recieved
== count
) {
757 [ibSearchVariableTable setToolTip
:[NSString stringWithFormat
:@
"Displaying one result."]];
760 [ibSearchVariableTable setToolTip
:[NSString stringWithFormat
:@
"Displaying %i results.", count
]];
763 else if ( recieved
< count
) {
764 [ibSearchVariableTable setToolTip
:[NSString stringWithFormat
:@
"Displaying %i of %i results.", recieved
, count
]];
769 - (NSString
*)displayName
771 // override the default window title if there is a custom one
772 NSString
*title
= [_cheatData windowTitle
];
774 if ( !title ||
[title isEqualToString
:@
""] ) {
775 return [super displayName
];
780 - (BOOL)validateMenuItem
:(NSMenuItem
*)menuItem
782 //ChazLog( @"validate menuitem: %@", [menuItem title] );
785 if ( [menuItem action
] == @selector(ibUndo
:) ) {
786 if ( [_searchData undoesLeft
] > 0 ) {
793 if ( [menuItem action
] == @selector(ibRedo
:) ) {
794 if ( [_searchData redoesLeft
] > 0 ) {
802 // the add variables items
803 if ( [menuItem action
] == @selector(ibAddCheatVariable
:) && _status
== TCCheatingStatus
) {
807 // the 'pause' menu item
808 if ( [menuItem tag
] == 1000 ) {
812 if ( _isTargetPaused
) {
813 [menuItem setTitle
:@
"Resume Target"];
814 [menuItem setAction
:@selector(ibResumeTarget
:)];
817 [menuItem setTitle
:@
"Pause Target"];
818 [menuItem setAction
:@selector(ibPauseTarget
:)];
822 // the 'memory dump' menu item
823 else if ( [menuItem tag
] == 1001 ) {
824 if ( !_cheater || _status
!= TCIdleStatus
) {
828 // the 'mode switch' menu item
829 else if ( [menuItem tag
] == 1002 ) {
830 if ( _mode
== TCSearchMode
) {
831 [menuItem setTitle
:@
"Show Cheat Mode"];
833 else /* _mode == TCCheatMode */ {
834 [menuItem setTitle
:@
"Show Search Mode"];
837 // the 'edit variables' menu item
838 else if ( [menuItem tag
] == 1003 ) {
839 return (_mode
== TCCheatMode
&& [ibCheatVariableTable selectedRow
] != -1);
841 // the 'clear search' menu item
842 else if ( [menuItem tag
] == 1004 ) {
843 return [ibSearchClearButton isEnabled
];
845 // the cancel menu item
846 else if ( [menuItem tag
] == 1005 ) {
847 if ( !_cheater || _isCancelingTask
) {
850 if ( _status
== TCSearchingStatus
) {
851 [menuItem setTitle
:@
"Cancel Search"];
852 [menuItem setAction
:@selector(ibCancelSearch
:)];
854 else if ( _status
== TCCheatingStatus
) {
855 [menuItem setTitle
:@
"Stop Cheat"];
856 [menuItem setAction
:@selector(ibStopCheat
:)];
858 else if ( _status
== TCDumpingStatus
) {
859 [menuItem setTitle
:@
"Cancel Dump"];
860 [menuItem setAction
:@selector(ibCancelDump
:)];
867 return [super validateMenuItem
:menuItem
];
871 - (void)setDocumentChanged
873 // only count document changes if there are variables
874 // and if the pref is set
875 if ( [[NSUserDefaults standardUserDefaults
] boolForKey
:TCAskForSavePref
] &&
876 ([_cheatData variableCount
] > 0 ||
[self isLoadedFromFile
]) ) {
877 [self updateChangeCount
:NSChangeDone
];
882 - (int)numberOfRowsInTableView
:(NSTableView
*)aTableView
884 if ( aTableView
== ibCheatVariableTable
) {
885 return [_cheatData variableCount
];
887 else if ( aTableView
== ibSearchVariableTable
) {
888 return [_searchData numberOfResults
];
893 - (id)tableView
:(NSTableView
*)aTableView objectValueForTableColumn
:(NSTableColumn
*)aTableColumn row
:(int)rowIndex
895 NSString
*identifier
= [aTableColumn identifier
];
897 if ( aTableView
== ibCheatVariableTable
) {
898 Variable
*variable
= [_cheatData variableAtIndex
:rowIndex
];
900 if ( [identifier isEqualToString
:@
"enable"] ) {
901 return [NSNumber numberWithBool
:[variable isEnabled
]];
903 else if ( [identifier isEqualToString
:@
"variable"] ) {
904 return [variable typeString
];
906 else if ( [identifier isEqualToString
:@
"address"] ) {
907 return [variable addressString
];
909 else if ( [identifier isEqualToString
:@
"value"] ) {
910 return [variable stringValue
];
913 else if ( aTableView
== ibSearchVariableTable
) {
914 return [_searchData stringForRow
:rowIndex
];
919 - (void)tableView
:(NSTableView
*)aTableView setObjectValue
:(id)anObject forTableColumn
:(NSTableColumn
*)aTableColumn row
:(int)rowIndex
921 NSString
*identifier
= [aTableColumn identifier
];
923 if ( aTableView
== ibCheatVariableTable
) {
924 Variable
*variable
= [_cheatData variableAtIndex
:rowIndex
];
926 if ( [identifier isEqualToString
:@
"address"] ) {
927 if ( [variable setAddressString
:anObject
] ) {
928 [self setDocumentChanged
];
931 else if ( [identifier isEqualToString
:@
"value"] ) {
932 if ( [variable setStringValue
:anObject
] ) {
933 [self setDocumentChanged
];
939 - (void)tableViewSelectionDidChange
:(NSNotification
*)aNotification
941 NSTableView
*aTableView
= [aNotification object
];
943 if ( aTableView
== ibSearchVariableTable
) {
944 int selectedRows
= [aTableView numberOfSelectedRows
];
945 if ( selectedRows
> 1 ) {
946 [ibSearchVariableButton setTitle
:@
"Add Variables"];
948 else if ( selectedRows
== 1 ) {
949 [ibSearchVariableButton setTitle
:@
"Add Variable"];
955 - (BOOL)tableViewDidReceiveEnterKey
:(NSTableView
*)tableView
957 if ( tableView
== ibSearchVariableTable
) {
958 [ibSearchVariableButton performClick
:nil];
964 - (BOOL)tableViewDidReceiveSpaceKey
:(NSTableView
*)tableView
966 if ( tableView
== ibCheatVariableTable
) {
967 [self ibSetVariableEnabled
:nil];
973 - (NSString
*)tableViewPasteboardType
:(NSTableView
*)tableView
975 return @
"TCVariablePboardType";
978 - (NSData
*)tableView
:(NSTableView
*)tableView copyRows
:(NSArray
*)rows
980 NSMutableArray
*vars
;
984 vars
= [[NSMutableArray alloc
] initWithCapacity
:top
];
986 // add the new variables
987 if ( tableView
== ibSearchVariableTable
) {
988 for ( i
= 0; i
< top
; i
++ ) {
989 unsigned index
= [[rows objectAtIndex
:i
] unsignedIntValue
];
990 [vars addObject
:[_searchData variableAtIndex
:index
]];
994 for ( i
= 0; i
< top
; i
++ ) {
995 unsigned index
= [[rows objectAtIndex
:i
] unsignedIntValue
];
996 [vars addObject
:[_cheatData variableAtIndex
:index
]];
1000 return [NSArchiver archivedDataWithRootObject
:[vars autorelease
]];
1003 - (void)tableView
:(NSTableView
*)tableView pasteRowsWithData
:(NSData
*)rowData
1005 NSArray
*vars
= [NSUnarchiver unarchiveObjectWithData
:rowData
];
1006 int i
, top
, lastRow
;
1008 if ( tableView
== ibSearchVariableTable
) {
1014 for ( i
= 0; i
< top
; i
++ ) {
1015 Variable
*var
= [vars objectAtIndex
:i
];
1016 [_cheatData addVariable
:var
];
1019 lastRow
= [_cheatData variableCount
]-1;
1020 [tableView reloadData
];
1021 if ( MacOSXVersion() >= 0x1030 ) {
1022 [tableView selectRowIndexes
:[NSIndexSet indexSetWithIndex
:lastRow
] byExtendingSelection
:NO
];
1025 [tableView selectRow
:lastRow byExtendingSelection
:NO
];
1027 [tableView scrollRowToVisible
:lastRow
];
1029 [self setDocumentChanged
];
1030 [self updateInterface
];
1033 - (void)tableView
:(NSTableView
*)tableView deleteRows
:(NSArray
*)rows
1037 if ( tableView
== ibCheatVariableTable
) {
1039 for ( i
= len
-1; i
>= 0; i
-- ) {
1040 [_cheatData removeVariableAtIndex
:[[rows objectAtIndex
:i
] unsignedIntValue
]];
1042 // reselect the last item if the selection is now invalid
1043 len
= [_cheatData variableCount
] - 1;
1044 if ( [tableView selectedRow
] > len
) {
1045 if ( MacOSXVersion() >= 0x1030 ) {
1046 [tableView selectRowIndexes
:[NSIndexSet indexSetWithIndex
:len
] byExtendingSelection
:NO
];
1049 [tableView selectRow
:len byExtendingSelection
:NO
];
1052 [tableView reloadData
];
1054 [self setDocumentChanged
];
1055 [self updateInterface
];
1059 // VariableTable Delegate
1060 - (void)tableView
:(NSTableView
*)aTableView didChangeVisibleRows
:(NSRange
)rows
1062 ChazLog( @
"new visible rows: %@", NSStringFromRange( rows
) );
1063 if ( [_searchData valuesLoaded
] ) {
1064 [self watchVariables
];
1069 // #############################################################################
1070 #pragma mark Utility
1071 // #############################################################################
1073 + (void)setGlobalTarget
:(Process
*)target
1076 [_tc_target release
];
1077 _tc_target
= target
;
1080 + (Process
*)globalTarget
1086 - (void)showError
:(NSString
*)error
1088 NSColor
*red
= [NSColor colorWithCalibratedRed
:0.7 green
:0.0 blue
:0.0 alpha
:1.0];
1090 [ibStatusText setDefaultStatus
:[self defaultStatusString
]];
1091 [ibStatusText setTemporaryStatus
:[NSString stringWithFormat
:@
"Error: %@", error
] color
:red duration
:7.0];
1095 - (BOOL)shouldConnectWithServer
:(NSMenuItem
*)item
1099 if ( _resolvingService
) {
1100 // don't connect if a service is being resolved
1101 [ibServerPopup selectItemAtIndex
:[ibServerPopup indexOfItemWithRepresentedObject
:_resolvingService
]];
1105 if ( [item respondsToSelector
:@selector(representedObject
)] ) {
1106 serverObject
= [item representedObject
];
1109 serverObject
= [NSNull null
];
1112 if ( [_serverObject isEqual
:serverObject
] ) {
1113 // already connected, don't connect
1119 - (void)selectConnectedCheater
1121 int index
= [ibServerPopup indexOfItemWithRepresentedObject
:_serverObject
];
1122 if ( index
!= -1 ) {
1123 [ibServerPopup selectItemAtIndex
:index
];
1127 - (void)connectWithServer
:(NSMenuItem
*)item
1131 if ( [item respondsToSelector
:@selector(representedObject
)] ) {
1132 serverObject
= [item representedObject
];
1135 serverObject
= [NSNull null
];
1138 // save a reference to the server object
1139 [serverObject retain
];
1140 [_serverObject release
];
1141 _serverObject
= serverObject
;
1144 - (void)disconnectFromCheater
1148 // don't do anything if we are already disconnected
1153 _status
= TCIdleStatus
;
1156 [_searchData clearResults
];
1157 [ibSearchVariableTable reloadData
];
1159 // clear the selected process
1162 [_serverObject release
];
1163 _serverObject
= nil;
1165 if ( ![self isLoadedFromFile
] ) {
1166 [_cheatData setProcess
:nil];
1169 // clear the process menu
1170 blankMenu
= [[NSMenu alloc
] initWithTitle
:@
""];
1171 [blankMenu addItemWithTitle
:@
"" action
:NULL keyEquivalent
:@
""];
1172 [ibProcessPopup setMenu
:blankMenu
];
1173 [blankMenu release
];
1175 // kill the connection
1176 [_cheater disconnect
];
1182 - (void)setConnectOnOpen
:(BOOL)flag
1184 _connectsOnOpen
= flag
;
1187 - (void)connectWithURL
:(NSString
*)url
1189 NSMenu
*serverMenu
= [ibServerPopup menu
];
1190 NSURL
*theUrl
= [NSURL URLWithString
:url
];
1196 int indexIfAlreadyExists
;
1198 host
= [theUrl host
];
1199 port
= [[theUrl port
] intValue
];
1202 NSBeginInformationalAlertSheet( @
"The Cheat can't parse the URL.", @
"OK", nil, nil, ibWindow
, self, NULL
, NULL
, NULL
,
1203 @
"The Cheat can't connect to the server because \"%@\" is not a valid URL.", url
);
1207 // use default port number
1209 port
= TCDefaultListenPort
;
1212 addrData
= [MySocket addressWithHost
:host port
:port
];
1214 NSBeginInformationalAlertSheet( @
"The Cheat can't find the server.", @
"OK", nil, nil, ibWindow
, self, NULL
, NULL
, NULL
,
1215 @
"The Cheat can't connect to the server \"%@\" because it can't be found.", host
);
1219 indexIfAlreadyExists
= [serverMenu indexOfItemWithRepresentedObject
:addrData
];
1220 if ( indexIfAlreadyExists
== -1 ) {
1221 NSMenuItem
*menuItem
;
1222 // add the newly found service to the server popup
1223 menuItem
= [[NSMenuItem alloc
] init
];
1224 [menuItem setTarget
:self];
1225 [menuItem setAction
:@selector(ibSetCustomCheater
:)];
1226 [menuItem setTitle
:[NSString stringWithFormat
:@
"%@:%i", host
, port
]];
1227 [menuItem setRepresentedObject
:addrData
];
1228 [self addServer
:menuItem
];
1230 [self ibSetCustomCheater
:menuItem
];
1235 // select matching item
1236 [self ibSetCustomCheater
:[serverMenu itemAtIndex
:indexIfAlreadyExists
]];
1240 [self selectConnectedCheater
];
1244 - (void)watchVariables
1248 if ( [[NSUserDefaults standardUserDefaults
] boolForKey
:TCDisplayValuesPref
] ) {
1249 float interval
= [[NSUserDefaults standardUserDefaults
] floatForKey
:TCValueUpdatePref
];
1251 range
= [ibSearchVariableTable visibleRows
];
1252 [_cheater watchVariablesAtIndex
:range.location count
:range.length interval
:interval
];
1255 [_cheater stopWatchingVariables
];
1260 // #############################################################################
1261 #pragma mark Notifications
1262 // #############################################################################
1264 - (void)_displayValuesPrefChanged
:(NSNotification
*)note
1266 [self watchVariables
];
1269 - (void)_windowOnTopPrefChanged
:(NSNotification
*)note
1271 ChazLog( @
"_windowOnTopPrefChanged" );
1272 if ( [[NSUserDefaults standardUserDefaults
] boolForKey
:TCWindowsOnTopPref
] ) {
1273 [ibWindow setLevel
:NSPopUpMenuWindowLevel
];
1276 [ibWindow setLevel
:NSNormalWindowLevel
];
1280 - (void)_hitsDisplayedPrefChanged
:(NSNotification
*)note
1282 // send preferences to the cheater
1283 [_cheater limitReturnedResults
:[[NSUserDefaults standardUserDefaults
] integerForKey
:TCHitsDisplayedPref
]];