]> Dogcows Code - chaz/thecheat/blob - CheatDocument.m
The Cheat 1.2.3
[chaz/thecheat] / CheatDocument.m
1
2 // **********************************************************************
3 // The Cheat - A universal game cheater for Mac OS X
4 // (C) 2003-2005 Chaz McGarvey (BrokenZipper)
5 //
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)
9 // any later version.
10 //
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.
15 //
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.
19 //
20
21 #import "CheatDocument.h"
22
23
24 // GLOBALS
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;
29 // global target
30 Process static *_tc_target = nil;
31
32
33 @interface CheatDocument ( PrivateAPI )
34
35 // mode switching
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;
43 // interface
44 - (void)_setupInitialInterface;
45 // notifications
46 - (void)_displayValuesPrefChanged:(NSNotification *)note;
47 - (void)_windowOnTopPrefChanged:(NSNotification *)note;
48 - (void)_hitsDisplayedPrefChanged:(NSNotification *)note;
49
50 @end
51
52 @interface NSTableView ( PrivateAPI )
53
54 - (NSRange)_rowsInRectAssumingRowsCoverVisible:(NSRect)rect;
55
56 @end
57
58
59 @implementation CheatDocument
60
61
62 // #############################################################################
63 #pragma mark Initialization
64 // #############################################################################
65
66 - (id)init // designated
67 {
68 if ( self = [super init] )
69 {
70 NSNotificationCenter *nc= [NSNotificationCenter defaultCenter];
71
72 ChazLog( @"init doc %X", self );
73 [CheatDocument _documentCreated];
74
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];
78
79 _cheatData = [[CheatData alloc] init];
80 _searchData = [[SearchData alloc] init];
81
82 // show search mode when documents are first created
83 _connectsOnOpen = YES;
84 [self setMode:TCSearchMode];
85 }
86 return self;
87 }
88
89 - (id)initWithContentsOfFile:(NSString *)fileName ofType:(NSString *)docType
90 {
91 if ( self = [super initWithContentsOfFile:fileName ofType:docType] )
92 {
93 // if document opened from a file, show cheat mode by default
94 [self setMode:TCCheatMode];
95 }
96 return self;
97 }
98
99 - (id)initWithContentsOfURL:(NSURL *)aURL ofType:(NSString *)docType
100 {
101 if ( self = [super initWithContentsOfURL:aURL ofType:docType] )
102 {
103 // if document opened from a URL, show cheat mode by default
104 [self setMode:TCCheatMode];
105 }
106 return self;
107 }
108
109 - (void)dealloc
110 {
111 ChazLog( @"dealloc doc %X", self );
112
113 // unregister observers
114 [(NSNotificationCenter *)[NSNotificationCenter defaultCenter] removeObserver:self];
115
116 [_cheater setDelegate:nil];
117 [self disconnectFromCheater];
118
119 [_cheatData release];
120 [_searchData release];
121
122 [_serverObject release];
123 [_process release];
124
125 // release the fade if one is occuring
126 [_fadeView removeFromSuperview];
127 [_fadeView release];
128
129 [CheatDocument _documentDestroyed];
130
131 [super dealloc];
132 }
133
134
135 // #############################################################################
136 #pragma mark Nib Loading
137 // #############################################################################
138
139 - (NSString *)windowNibName
140 {
141 return @"CheatDocument";
142 }
143
144 - (void)windowControllerDidLoadNib:(NSWindowController *)aController
145 {
146 [super windowControllerDidLoadNib:aController];
147
148 NSNotificationCenter *nc= [NSNotificationCenter defaultCenter];
149
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];
154
155 // setup window frame saving
156 [ibWindow useOptimizedDrawing:YES];
157 [ibWindow setFrameAutosaveName:@"TCCheatWindow"];
158
159 // set options
160 if ( [[NSUserDefaults standardUserDefaults] boolForKey:TCWindowsOnTopPref] )
161 {
162 [ibWindow setLevel:NSPopUpMenuWindowLevel];
163 }
164
165 // display one of the modes
166 if ( _mode == TCCheatMode ) {
167 [self _switchTo:ibCheatContentView from:ibPlaceView];
168 }
169 else if ( _mode == TCSearchMode ) {
170 [self _switchTo:ibSearchContentView from:ibPlaceView];
171 }
172
173 // configure the initial interface
174 [self _setupInitialInterface];
175
176 // update interface
177 [ibStatusText setDefaultStatus:[self defaultStatusString]];
178 [self updateInterface];
179
180 // automatically connect to the local cheater
181 if ( _connectsOnOpen ) {
182 [self ibSetLocalCheater:nil];
183 }
184
185 ChazLog( @"superview: %@", [[ibSearchVariableTable superview] superview] );
186 }
187
188
189 // #############################################################################
190 #pragma mark Handling Files
191 // #############################################################################
192
193 - (NSData *)dataRepresentationOfType:(NSString *)type
194 {
195 return [NSArchiver archivedDataWithRootObject:_cheatData];
196 }
197
198 - (BOOL)loadDataRepresentation:(NSData *)data ofType:(NSString *)type
199 {
200 [_cheatData release];
201 _cheatData = nil;
202
203 if ( [type isEqualToString:@"Cheat Document"] ) {
204 NS_DURING
205 _cheatData = [[NSUnarchiver unarchiveObjectWithData:data] retain];
206 NS_HANDLER
207 if ( !_cheatData ) {
208 // alert the user of the unparsable file
209 NSBeep();
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] );
211 return NO;
212 }
213 NS_ENDHANDLER
214 }
215
216 [self updateInterface];
217
218 return YES;
219 }
220
221
222 // #############################################################################
223 #pragma mark Service Finding
224 // #############################################################################
225
226 + (NSArray *)cheatServices
227 {
228 return [NSArray arrayWithArray:_tc_cheat_services];
229 }
230
231
232 - (void)_cheatServiceFound:(NSNotification *)note
233 {
234 NSMenuItem *menuItem;
235 NSNetService *item = [note object];
236
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];
244 [menuItem release];
245 }
246
247 - (void)_cheatServiceRemoved:(NSNotification *)note
248 {
249 NSNetService *item = [note object];
250
251 // remove the service from the menu
252 [self removeServerWithObject:item];
253 }
254
255
256 // using the service browser
257 + (void)_documentCreated
258 {
259 _tc_document_count++;
260
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];
271 }
272 }
273
274 + (void)_documentDestroyed
275 {
276 _tc_document_count--;
277
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;
285 }
286 }
287
288
289 // NSNetServiceBrowser delegate methods
290 + (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)browser
291 {
292 ChazLog( @"service browser will search" );
293 }
294
295 + (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)browser
296 {
297 // if the browser stops we assume it needs to die.
298 ChazLog( @"service browser did stop search" );
299 [browser release];
300 }
301
302 + (void)netServiceBrowser:(NSNetServiceBrowser *)browser didNotSearch:(NSDictionary *)errorDict
303 {
304 ChazLog( @"service browser failed with error code: %i", [[errorDict objectForKey:NSNetServicesErrorCode] intValue] );
305 }
306
307 + (void)netServiceBrowser:(NSNetServiceBrowser *)browser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
308 {
309 ChazLog( @"service browser found service: %@", [aNetService name] );
310
311 // ignore if this is the local server.
312 if ( [[(AppController *)NSApp cheatServer] isListening] &&
313 [[aNetService name] isEqualToString:[[NSUserDefaults standardUserDefaults] objectForKey:TCBroadcastNamePref]] ) {
314 return;
315 }
316
317 [_tc_cheat_services addObject:aNetService];
318 // send a notification for the new service
319 [[NSNotificationCenter defaultCenter] postNotificationName:TCServiceFoundNote object:aNetService];
320 }
321
322 + (void)netServiceBrowser:(NSNetServiceBrowser *)browser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
323 {
324 ChazLog( @"service browser removed service: %@", [aNetService name] );
325
326 [_tc_cheat_services removeObject:aNetService];
327 // send a notification for the new service
328 [[NSNotificationCenter defaultCenter] postNotificationName:TCServiceRemovedNote object:aNetService];
329 }
330
331
332 // #############################################################################
333 #pragma mark Changing Mode
334 // #############################################################################
335
336 - (void)setMode:(TCDocumentMode)mode
337 {
338 // if the nib isn't loaded, change the mode
339 if ( !ibWindow ) {
340 _mode = mode;
341 }
342 }
343
344
345 - (void)switchToCheatMode
346 {
347 NSResponder *responder = [ibWindow firstResponder];
348
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];
354 }
355
356 if ( _mode == TCCheatMode ) {
357 return;
358 }
359 _mode = TCCheatMode;
360 [self _switchTo:ibCheatContentView from:ibSearchContentView];
361
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];
368 }
369 else {
370 [ibWindow makeFirstResponder:_lastResponder];
371 }
372 _lastResponder = responder;
373 }
374
375 - (void)switchToSearchMode
376 {
377 NSResponder *responder = [ibWindow firstResponder];
378
379 if ( [responder isKindOfClass:[NSText class]] ) {
380 responder = [(NSText *)responder delegate];
381 }
382
383 if ( _mode == TCSearchMode ) {
384 return;
385 }
386 _mode = TCSearchMode;
387 [self _switchTo:ibSearchContentView from:ibCheatContentView];
388
389 // update the next key view
390 [ibProcessPopup setNextKeyView:ibSearchTypePopup];
391 // update current key view
392 if ( !_lastResponder || _lastResponder == ibWindow ) {
393 [ibWindow makeFirstResponder:ibSearchValueField];
394 }
395 else {
396 [ibWindow makeFirstResponder:_lastResponder];
397 }
398 _lastResponder = responder;
399 }
400
401
402 - (void)_switchTo:(NSView *)destination from:(NSView *)source
403 {
404 NSView *contentView = [ibWindow contentView];
405 NSRect frame = [source frame];
406 NSImage *fadeImage = nil;
407
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];
412
413 // create the image object
414 fadeImage = [[NSImage alloc] initWithSize:frame.size];
415 [fadeImage addRepresentation:imageRep];
416
417 if ( _fadeView ) {
418 // remove the old fade view
419 [_fadeView removeFromSuperview];
420 [_fadeView release];
421 }
422
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];
431
432 [fadeImage release];
433 }
434
435 // update view size of incoming view
436 [destination setFrame:frame];
437 // replace the views
438 [contentView replaceSubview:source with:destination];
439 }
440
441
442 // FadeView Delegate
443 - (void)fadeViewFinishedAnimation:(FadeView *)theView
444 {
445 [_fadeView removeFromSuperview];
446 [_fadeView release];
447 _fadeView = nil;
448 }
449
450
451 // #############################################################################
452 #pragma mark Accessors
453 // #############################################################################
454
455 - (NSString *)defaultStatusString
456 {
457 if ( !_cheater ) {
458 return @"Not Connected";
459 }
460 return [NSString stringWithFormat:@"Connected to %@.", [_cheater hostAddress]];
461 }
462
463 - (BOOL)isLoadedFromFile
464 {
465 return ([self fileName] != nil);
466 }
467
468
469 - (void)addServer:(NSMenuItem *)item
470 {
471 NSMenu *serverMenu = [ibServerPopup menu];
472
473 if ( item ) {
474 if ( [serverMenu numberOfItems] <= 2 ) {
475 // separator line
476 [serverMenu addItem:[NSMenuItem separatorItem]];
477 }
478 [serverMenu addItem:item];
479 }
480 }
481
482 - (void)removeServerWithObject:(id)serverObject
483 {
484 NSMenu *serverMenu = [ibServerPopup menu];
485
486 if ( serverObject ) {
487 [serverMenu removeItemWithRepresentedObject:serverObject];
488 if ( [serverMenu numberOfItems] == 3 ) {
489 // separator line
490 [serverMenu removeItemAtIndex:2];
491 }
492 }
493 }
494
495
496 // #############################################################################
497 #pragma mark Interface
498 // #############################################################################
499
500 - (void)_setupInitialInterface
501 {
502 NSMenu *serverMenu;
503 NSMenuItem *menuItem;
504
505 NSArray *cheatServices;
506 unsigned i, len;
507
508 // create and set the server popup menu
509 serverMenu = [[NSMenu alloc] init];
510 [serverMenu setAutoenablesItems:YES];
511 // add menu items
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];
519 [menuItem release];
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];
528 [menuItem release];
529 // set the menu
530 [ibServerPopup setMenu:serverMenu];
531 [serverMenu release];
532
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];
544 [menuItem release];
545 }
546
547 [ibSearchVariableTable setDoubleAction:@selector(ibAddSearchVariable:)];
548 [ibSearchVariableTable setCanDelete:NO];
549
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];
553
554 // we use undoing/redoing for reverting search results
555 [self setHasUndoManager:NO];
556 }
557
558 - (void)updateInterface
559 {
560 if ( _cheatData )
561 {
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]]];
569 }
570 else {
571 [ibCheatInfoText setStringValue:[NSString stringWithFormat:@"%@ %@ - %@",
572 [_cheatData gameName],
573 [_cheatData gameVersion],
574 [_cheatData cheatInfo]]];
575 }
576 }
577 else {
578 [ibCheatInfoText setStringValue:[_cheatData cheatInfo]];
579 }
580
581 [ibCheatRepeatButton setState:[_cheatData repeats]];
582 [ibCheatRepeatField setDoubleValue:[_cheatData repeatInterval]];
583 }
584
585 // if we're connected...
586 if ( _cheater )
587 {
588 if ( _status == TCIdleStatus )
589 {
590 // WINDOW
591 [ibServerPopup setEnabled:YES];
592 [ibProcessPopup setEnabled:YES];
593 // SEARCH MODE
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];
601 }
602 else {
603 [ibSearchValueField setEnabled:NO];
604 }
605 [ibSearchClearButton setEnabled:YES];
606 [ibSearchVariableTable setEnabled:YES];
607 int selectedRows = [ibSearchVariableTable numberOfSelectedRows];
608 if ( selectedRows > 0 ) {
609 [ibSearchVariableButton setEnabled:YES];
610 }
611 else {
612 [ibSearchVariableButton setEnabled:NO];
613 }
614 }
615 else {
616 [ibSearchTypePopup setEnabled:YES];
617 if ( [_searchData isTypeInteger] ) {
618 [ibSearchIntegerSignMatrix setEnabled:YES];
619 }
620 else {
621 [ibSearchIntegerSignMatrix setEnabled:NO];
622 }
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];
629 }
630 if ( [_searchData variableType] != TCString ) {
631 [ibSearchOperatorPopup setEnabled:YES];
632 }
633 else {
634 [ibSearchOperatorPopup setEnabled:NO];
635 }
636 [ibSearchButton setTitle:@"Search"];
637 [ibSearchButton setAction:@selector(ibSearch:)];
638 [ibSearchButton setKeyEquivalent:@""];
639 [ibSearchButton setEnabled:YES];
640 // CHEAT MODE
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"];
652 }
653 else {
654 [ibCheatButton setKeyEquivalent:@""];
655 }
656 }
657 else {
658 [ibCheatButton setEnabled:NO];
659 }
660 }
661 else
662 {
663 // WINDOW
664 [ibServerPopup setEnabled:NO];
665 [ibProcessPopup setEnabled:NO];
666 // SEARCH MODE
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];
675 // CHEAT MODE
676 [ibCheatVariableTable setEnabled:NO];
677 [ibCheatRepeatButton setEnabled:NO];
678 [ibCheatRepeatAuxText setTextColor:[NSColor disabledControlTextColor]];
679 [ibCheatRepeatField setEnabled:NO];
680
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"];
690 }
691 else {
692 [ibCheatButton setKeyEquivalent:@""];
693 }
694 [ibCheatButton setEnabled:NO];
695 }
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];
705 }
706 else {
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"];
715 }
716 else {
717 [ibCheatButton setKeyEquivalent:@""];
718 }
719 [ibCheatButton setEnabled:NO];
720 }
721 }
722 }
723 else
724 {
725 // WINDOW
726 [ibServerPopup setEnabled:YES];
727 [ibProcessPopup setEnabled:NO];
728 // SEARCH MODE
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];
738 // CHEAT MODE
739 [ibCheatVariableTable setEnabled:NO];
740 [ibCheatRepeatButton setEnabled:NO];
741 [ibCheatRepeatAuxText setTextColor:[NSColor disabledControlTextColor]];
742 [ibCheatRepeatField setEnabled:NO];
743 [ibCheatButton setEnabled:NO];
744 }
745 }
746
747
748 - (void)setActualResults:(unsigned)count
749 {
750 unsigned recieved = [_searchData numberOfResults];
751
752 if ( count == 0 ) {
753 [ibSearchVariableTable setToolTip:@""];
754 }
755 else if ( recieved == count ) {
756 if ( count == 1 ) {
757 [ibSearchVariableTable setToolTip:[NSString stringWithFormat:@"Displaying one result."]];
758 }
759 else {
760 [ibSearchVariableTable setToolTip:[NSString stringWithFormat:@"Displaying %i results.", count]];
761 }
762 }
763 else if ( recieved < count ) {
764 [ibSearchVariableTable setToolTip:[NSString stringWithFormat:@"Displaying %i of %i results.", recieved, count]];
765 }
766 }
767
768
769 - (NSString *)displayName
770 {
771 // override the default window title if there is a custom one
772 NSString *title = [_cheatData windowTitle];
773
774 if ( !title || [title isEqualToString:@""] ) {
775 return [super displayName];
776 }
777 return title;
778 }
779
780 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem
781 {
782 //ChazLog( @"validate menuitem: %@", [menuItem title] );
783
784 // the undo/redo
785 if ( [menuItem action] == @selector(ibUndo:) ) {
786 if ( [_searchData undoesLeft] > 0 ) {
787 return YES;
788 }
789 else {
790 return NO;
791 }
792 }
793 if ( [menuItem action] == @selector(ibRedo:) ) {
794 if ( [_searchData redoesLeft] > 0 ) {
795 return YES;
796 }
797 else {
798 return NO;
799 }
800 }
801
802 // the add variables items
803 if ( [menuItem action] == @selector(ibAddCheatVariable:) && _status == TCCheatingStatus ) {
804 return NO;
805 }
806
807 // the 'pause' menu item
808 if ( [menuItem tag] == 1000 ) {
809 if ( !_cheater ) {
810 return NO;
811 }
812 if ( _isTargetPaused ) {
813 [menuItem setTitle:@"Resume Target"];
814 [menuItem setAction:@selector(ibResumeTarget:)];
815 }
816 else {
817 [menuItem setTitle:@"Pause Target"];
818 [menuItem setAction:@selector(ibPauseTarget:)];
819 }
820 }
821
822 // the 'memory dump' menu item
823 else if ( [menuItem tag] == 1001 ) {
824 if ( !_cheater || _status != TCIdleStatus ) {
825 return NO;
826 }
827 }
828 // the 'mode switch' menu item
829 else if ( [menuItem tag] == 1002 ) {
830 if ( _mode == TCSearchMode ) {
831 [menuItem setTitle:@"Show Cheat Mode"];
832 }
833 else /* _mode == TCCheatMode */ {
834 [menuItem setTitle:@"Show Search Mode"];
835 }
836 }
837 // the 'edit variables' menu item
838 else if ( [menuItem tag] == 1003 ) {
839 return (_mode == TCCheatMode && [ibCheatVariableTable selectedRow] != -1);
840 }
841 // the 'clear search' menu item
842 else if ( [menuItem tag] == 1004 ) {
843 return [ibSearchClearButton isEnabled];
844 }
845 // the cancel menu item
846 else if ( [menuItem tag] == 1005 ) {
847 if ( !_cheater || _isCancelingTask ) {
848 return NO;
849 }
850 if ( _status == TCSearchingStatus ) {
851 [menuItem setTitle:@"Cancel Search"];
852 [menuItem setAction:@selector(ibCancelSearch:)];
853 }
854 else if ( _status == TCCheatingStatus ) {
855 [menuItem setTitle:@"Stop Cheat"];
856 [menuItem setAction:@selector(ibStopCheat:)];
857 }
858 else if ( _status == TCDumpingStatus ) {
859 [menuItem setTitle:@"Cancel Dump"];
860 [menuItem setAction:@selector(ibCancelDump:)];
861 }
862 else {
863 return NO;
864 }
865 }
866
867 return [super validateMenuItem:menuItem];
868 }
869
870
871 - (void)setDocumentChanged
872 {
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];
878 }
879 }
880
881
882 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
883 {
884 if ( aTableView == ibCheatVariableTable ) {
885 return [_cheatData variableCount];
886 }
887 else if ( aTableView == ibSearchVariableTable ) {
888 return [_searchData numberOfResults];
889 }
890 return 0;
891 }
892
893 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
894 {
895 NSString *identifier = [aTableColumn identifier];
896
897 if ( aTableView == ibCheatVariableTable ) {
898 Variable *variable = [_cheatData variableAtIndex:rowIndex];
899
900 if ( [identifier isEqualToString:@"enable"] ) {
901 return [NSNumber numberWithBool:[variable isEnabled]];
902 }
903 else if ( [identifier isEqualToString:@"variable"] ) {
904 return [variable typeString];
905 }
906 else if ( [identifier isEqualToString:@"address"] ) {
907 return [variable addressString];
908 }
909 else if ( [identifier isEqualToString:@"value"] ) {
910 return [variable stringValue];
911 }
912 }
913 else if ( aTableView == ibSearchVariableTable ) {
914 return [_searchData stringForRow:rowIndex];
915 }
916 return @"";
917 }
918
919 - (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
920 {
921 NSString *identifier = [aTableColumn identifier];
922
923 if ( aTableView == ibCheatVariableTable ) {
924 Variable *variable = [_cheatData variableAtIndex:rowIndex];
925
926 if ( [identifier isEqualToString:@"address"] ) {
927 if ( [variable setAddressString:anObject] ) {
928 [self setDocumentChanged];
929 }
930 }
931 else if ( [identifier isEqualToString:@"value"] ) {
932 if ( [variable setStringValue:anObject] ) {
933 [self setDocumentChanged];
934 }
935 }
936 }
937 }
938
939 - (void)tableViewSelectionDidChange:(NSNotification *)aNotification
940 {
941 NSTableView *aTableView = [aNotification object];
942
943 if ( aTableView == ibSearchVariableTable ) {
944 int selectedRows = [aTableView numberOfSelectedRows];
945 if ( selectedRows > 1 ) {
946 [ibSearchVariableButton setTitle:@"Add Variables"];
947 }
948 else if ( selectedRows == 1 ) {
949 [ibSearchVariableButton setTitle:@"Add Variable"];
950 }
951 }
952 }
953
954
955 - (BOOL)tableViewDidReceiveEnterKey:(NSTableView *)tableView
956 {
957 if ( tableView == ibSearchVariableTable ) {
958 [ibSearchVariableButton performClick:nil];
959 return YES;
960 }
961 return NO;
962 }
963
964 - (BOOL)tableViewDidReceiveSpaceKey:(NSTableView *)tableView
965 {
966 if ( tableView == ibCheatVariableTable ) {
967 [self ibSetVariableEnabled:nil];
968 return YES;
969 }
970 return NO;
971 }
972
973 - (NSString *)tableViewPasteboardType:(NSTableView *)tableView
974 {
975 return @"TCVariablePboardType";
976 }
977
978 - (NSData *)tableView:(NSTableView *)tableView copyRows:(NSArray *)rows
979 {
980 NSMutableArray *vars;
981 int i, top;
982
983 top = [rows count];
984 vars = [[NSMutableArray alloc] initWithCapacity:top];
985
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]];
991 }
992 }
993 else {
994 for ( i = 0; i < top; i++ ) {
995 unsigned index = [[rows objectAtIndex:i] unsignedIntValue];
996 [vars addObject:[_cheatData variableAtIndex:index]];
997 }
998 }
999
1000 return [NSArchiver archivedDataWithRootObject:[vars autorelease]];
1001 }
1002
1003 - (void)tableView:(NSTableView *)tableView pasteRowsWithData:(NSData *)rowData
1004 {
1005 NSArray *vars = [NSUnarchiver unarchiveObjectWithData:rowData];
1006 int i, top, lastRow;
1007
1008 if ( tableView == ibSearchVariableTable ) {
1009 NSBeep();
1010 return;
1011 }
1012
1013 top = [vars count];
1014 for ( i = 0; i < top; i++ ) {
1015 Variable *var = [vars objectAtIndex:i];
1016 [_cheatData addVariable:var];
1017 }
1018
1019 lastRow = [_cheatData variableCount]-1;
1020 [tableView reloadData];
1021 if ( MacOSXVersion() >= 0x1030 ) {
1022 [tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:lastRow] byExtendingSelection:NO];
1023 }
1024 else {
1025 [tableView selectRow:lastRow byExtendingSelection:NO];
1026 }
1027 [tableView scrollRowToVisible:lastRow];
1028
1029 [self setDocumentChanged];
1030 [self updateInterface];
1031 }
1032
1033 - (void)tableView:(NSTableView *)tableView deleteRows:(NSArray *)rows
1034 {
1035 int i, len;
1036
1037 if ( tableView == ibCheatVariableTable ) {
1038 len = [rows count];
1039 for ( i = len-1; i >= 0; i-- ) {
1040 [_cheatData removeVariableAtIndex:[[rows objectAtIndex:i] unsignedIntValue]];
1041 }
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];
1047 }
1048 else {
1049 [tableView selectRow:len byExtendingSelection:NO];
1050 }
1051 }
1052 [tableView reloadData];
1053
1054 [self setDocumentChanged];
1055 [self updateInterface];
1056 }
1057 }
1058
1059 // VariableTable Delegate
1060 - (void)tableView:(NSTableView *)aTableView didChangeVisibleRows:(NSRange)rows
1061 {
1062 ChazLog( @"new visible rows: %@", NSStringFromRange( rows ) );
1063 if ( [_searchData valuesLoaded] ) {
1064 [self watchVariables];
1065 }
1066 }
1067
1068
1069 // #############################################################################
1070 #pragma mark Utility
1071 // #############################################################################
1072
1073 + (void)setGlobalTarget:(Process *)target
1074 {
1075 [target retain];
1076 [_tc_target release];
1077 _tc_target = target;
1078 }
1079
1080 + (Process *)globalTarget
1081 {
1082 return _tc_target;
1083 }
1084
1085
1086 - (void)showError:(NSString *)error
1087 {
1088 NSColor *red = [NSColor colorWithCalibratedRed:0.7 green:0.0 blue:0.0 alpha:1.0];
1089 NSBeep();
1090 [ibStatusText setDefaultStatus:[self defaultStatusString]];
1091 [ibStatusText setTemporaryStatus:[NSString stringWithFormat:@"Error: %@", error] color:red duration:7.0];
1092 }
1093
1094
1095 - (BOOL)shouldConnectWithServer:(NSMenuItem *)item
1096 {
1097 id serverObject;
1098
1099 if ( _resolvingService ) {
1100 // don't connect if a service is being resolved
1101 [ibServerPopup selectItemAtIndex:[ibServerPopup indexOfItemWithRepresentedObject:_resolvingService]];
1102 return NO;
1103 }
1104
1105 if ( [item respondsToSelector:@selector(representedObject)] ) {
1106 serverObject = [item representedObject];
1107 }
1108 else {
1109 serverObject = [NSNull null];
1110 }
1111
1112 if ( [_serverObject isEqual:serverObject] ) {
1113 // already connected, don't connect
1114 return NO;
1115 }
1116 return YES;
1117 }
1118
1119 - (void)selectConnectedCheater
1120 {
1121 int index = [ibServerPopup indexOfItemWithRepresentedObject:_serverObject];
1122 if ( index != -1 ) {
1123 [ibServerPopup selectItemAtIndex:index];
1124 }
1125 }
1126
1127 - (void)connectWithServer:(NSMenuItem *)item
1128 {
1129 id serverObject;
1130
1131 if ( [item respondsToSelector:@selector(representedObject)] ) {
1132 serverObject = [item representedObject];
1133 }
1134 else {
1135 serverObject = [NSNull null];
1136 }
1137
1138 // save a reference to the server object
1139 [serverObject retain];
1140 [_serverObject release];
1141 _serverObject = serverObject;
1142 }
1143
1144 - (void)disconnectFromCheater
1145 {
1146 NSMenu *blankMenu;
1147
1148 // don't do anything if we are already disconnected
1149 if ( !_cheater ) {
1150 return;
1151 }
1152
1153 _status = TCIdleStatus;
1154
1155 // clear the search
1156 [_searchData clearResults];
1157
1158 //[ibSearchVariableTable reloadData]; // this can cause a crash, so commenting it out for now.
1159 // clear the selected process
1160 [_process release];
1161 _process = nil;
1162 [_serverObject release];
1163 _serverObject = nil;
1164
1165 if ( ![self isLoadedFromFile] ) {
1166 [_cheatData setProcess:nil];
1167 }
1168
1169 // clear the process menu
1170 blankMenu = [[NSMenu alloc] initWithTitle:@""];
1171 [blankMenu addItemWithTitle:@"" action:NULL keyEquivalent:@""];
1172 [ibProcessPopup setMenu:blankMenu];
1173 [blankMenu release];
1174
1175 // kill the connection
1176 [_cheater disconnect];
1177 [_cheater release];
1178 _cheater = nil;
1179 }
1180
1181
1182 - (void)setConnectOnOpen:(BOOL)flag
1183 {
1184 _connectsOnOpen = flag;
1185 }
1186
1187 - (void)connectWithURL:(NSString *)url
1188 {
1189 NSMenu *serverMenu = [ibServerPopup menu];
1190 NSURL *theUrl = [NSURL URLWithString:url];
1191
1192 NSString *host;
1193 int port;
1194
1195 NSData *addrData;
1196 int indexIfAlreadyExists;
1197
1198 host = [theUrl host];
1199 port = [[theUrl port] intValue];
1200
1201 if ( !host ) {
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 );
1204 goto FAILURE;
1205 }
1206
1207 // use default port number
1208 if ( !port ) {
1209 port = TCDefaultListenPort;
1210 }
1211
1212 addrData = [MySocket addressWithHost:host port:port];
1213 if ( !addrData ) {
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 );
1216 goto FAILURE;
1217 }
1218
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];
1229 // select new item
1230 [self ibSetCustomCheater:menuItem];
1231 // cleanup
1232 [menuItem release];
1233 }
1234 else {
1235 // select matching item
1236 [self ibSetCustomCheater:[serverMenu itemAtIndex:indexIfAlreadyExists]];
1237 }
1238
1239 FAILURE:;
1240 [self selectConnectedCheater];
1241 }
1242
1243
1244 - (void)watchVariables
1245 {
1246 NSRange range;
1247
1248 if ( [[NSUserDefaults standardUserDefaults] boolForKey:TCDisplayValuesPref] ) {
1249 float interval = [[NSUserDefaults standardUserDefaults] floatForKey:TCValueUpdatePref];
1250
1251 range = [ibSearchVariableTable visibleRows];
1252 [_cheater watchVariablesAtIndex:range.location count:range.length interval:interval];
1253 }
1254 else {
1255 [_cheater stopWatchingVariables];
1256 }
1257 }
1258
1259
1260 // #############################################################################
1261 #pragma mark Notifications
1262 // #############################################################################
1263
1264 - (void)_displayValuesPrefChanged:(NSNotification *)note
1265 {
1266 [self watchVariables];
1267 }
1268
1269 - (void)_windowOnTopPrefChanged:(NSNotification *)note
1270 {
1271 ChazLog( @"_windowOnTopPrefChanged" );
1272 if ( [[NSUserDefaults standardUserDefaults] boolForKey:TCWindowsOnTopPref] ) {
1273 [ibWindow setLevel:NSPopUpMenuWindowLevel];
1274 }
1275 else {
1276 [ibWindow setLevel:NSNormalWindowLevel];
1277 }
1278 }
1279
1280 - (void)_hitsDisplayedPrefChanged:(NSNotification *)note
1281 {
1282 // send preferences to the cheater
1283 [_cheater limitReturnedResults:[[NSUserDefaults standardUserDefaults] integerForKey:TCHitsDisplayedPref]];
1284 }
1285
1286
1287 @end
This page took 0.090946 seconds and 4 git commands to generate.