]> Dogcows Code - chaz/thecheat/blob - LocalCheater.m
The Cheat 1.2.4
[chaz/thecheat] / LocalCheater.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 "LocalCheater.h"
22
23 #include "cheat_global.h"
24
25
26 // memory dump function
27 int _MemoryDumpTask( ThreadedTask *task, unsigned iteration );
28
29
30 @interface LocalCheater ( Private )
31
32 // internal methods so they can be used throughout the class
33 - (void)_clearSearch;
34 - (BOOL)_pauseTarget; // returns TRUE for success
35 - (BOOL)_resumeTarget; // returns TRUE for success
36 // handling app launching/quitting
37 - (void)_applicationLaunched:(NSNotification *)note;
38 - (void)_applicationTerminated:(NSNotification *)note;
39 // cheating
40 - (unsigned)_doChange:(NSArray *)variables;
41 - (void)_changeTimer:(NSTimer *)timer;
42 // watch variables
43 - (void)_doWatch;
44 - (void)_watchTimer:(NSTimer *)timer;
45 // tasks
46 - (int)_memoryDumpTask:(ThreadedTask *)task iteration:(unsigned)iteration;
47
48 @end
49
50
51 @implementation LocalCheater
52
53
54 - (id)init
55 {
56 if ( self = [super init] ) {
57 NSNotificationCenter *nc= [[NSWorkspace sharedWorkspace] notificationCenter];
58
59 // register for app launch/quit notifications
60 [nc addObserver:self selector:@selector(_applicationLaunched:) name:NSWorkspaceDidLaunchApplicationNotification object:nil];
61 [nc addObserver:self selector:@selector(_applicationTerminated:) name:NSWorkspaceDidTerminateApplicationNotification object:nil];
62
63 _searchResults = [[NSMutableArray alloc] init];
64 _savedResults = [[NSMutableArray alloc] init];
65 _returnLimit = -1;
66 }
67 return self;
68 }
69
70 - (void)dealloc
71 {
72 [(NSNotificationCenter *)[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
73
74 // make sure the process isn't left paused
75 [self _resumeTarget];
76
77 // release local objects
78 [_target release];
79 [self _clearSearch];
80
81 // make sure everything is cancelled.
82 [_searchTask cancelAndRemoveDelegate];
83 [_dumpTask cancelAndRemoveDelegate];
84 [self stopChangingVariables];
85 [self stopWatchingVariables];
86
87 [_searchTask release];
88 [_dumpTask release];
89
90 [_processes release];
91
92 [super dealloc];
93 }
94
95
96 - (BOOL)shouldCopy
97 {
98 return _shouldCopy;
99 }
100
101 - (void)setShouldCopy:(BOOL)flag
102 {
103 _shouldCopy = flag;
104 }
105
106
107 - (NSString *)hostAddress
108 {
109 //return @"127.0.0.1";
110 return @"this computer";
111 }
112
113
114 // #############################################################################
115 #pragma mark Cheater Override
116 // #############################################################################
117
118 - (void)connect
119 {
120 if ( !_isConnected ) {
121 _isConnected = YES;
122 // return as connected
123 [_delegate cheaterDidConnect:self];
124 }
125 }
126
127 - (void)disconnect
128 {
129 if ( _isConnected ) {
130 [_searchTask cancelAndRemoveDelegate];
131 [_dumpTask cancelAndRemoveDelegate];
132 [self stopChangingVariables];
133 [self stopWatchingVariables];
134
135 _isConnected = NO;
136 // return as disconnected
137 //[_delegate cheaterDidDisconnect:self];
138 }
139 }
140
141 - (void)authenticateWithPassword:(NSString *)password
142 {
143 // being a local cheater, authentication really isn't required.
144 // return succes every time.
145 _isAuthenticated = YES;
146 [_delegate cheaterAcceptedPassword:self];
147 }
148
149
150 - (void)getProcessList
151 {
152 NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
153 NSArray *launchedApps = [workspace launchedApplications];
154 unsigned i, len = [launchedApps count];
155
156 if ( !_processes ) {
157 _processes = [[NSMutableArray alloc] initWithCapacity:len];
158 }
159
160 // compile process array
161 for ( i = 0; i < len; i++ ) {
162 NSDictionary *application = [launchedApps objectAtIndex:i];
163
164 // don't allow The Cheat to be cheated
165 if ( [[application objectForKey:@"NSApplicationBundleIdentifier"] isEqualToString:[[NSBundle mainBundle] bundleIdentifier]] ) {
166 continue;
167 }
168
169 Process *process = [[Process alloc] initWithName:[application objectForKey:@"NSApplicationName"]
170 version:ApplicationVersion( [application objectForKey:@"NSApplicationPath"] )
171 icon:[workspace iconForFile:[application objectForKey:@"NSApplicationPath"]]
172 pid:[[application objectForKey:@"NSApplicationProcessIdentifier"] intValue]];
173 [_processes addObject:process];
174 [process release];
175 }
176
177 // return process list
178 [_delegate cheater:self didFindProcesses:[NSArray arrayWithArray:_processes]];
179 }
180
181
182 - (void)setTarget:(Process *)target
183 {
184 // unpause the current target if needed
185 [self resumeTarget];
186 // clear the search
187 [self clearSearch];
188 // set the new target
189 [_target release];
190
191 if ( _shouldCopy ) {
192 _target = [target copy];
193 [_delegate cheater:self didSetTarget:[_target copy]];
194 }
195 else {
196 _target = [target retain];
197 [_delegate cheater:self didSetTarget:_target];
198 }
199 }
200
201 - (void)pauseTarget
202 {
203 // attempt to pause the current target
204 if ( !_isTargetPaused && [self _pauseTarget] ) {
205 [_delegate cheaterPausedTarget:self];
206 }
207 else {
208 [_delegate cheater:self echo:@"This process cannot be paused."];
209 }
210 }
211
212 - (void)resumeTarget
213 {
214 // attempt to resume the current target
215 if ( _isTargetPaused && [self _resumeTarget] ) {
216 [_delegate cheaterResumedTarget:self];
217 }
218 else {
219 [_delegate cheater:self echo:@"This process cannot be resumed."];
220 }
221 }
222
223
224 - (void)limitReturnedResults:(unsigned)limit
225 {
226 _returnLimit = limit;
227 }
228
229 - (void)searchForVariable:(Variable *)var comparison:(TCSearchOperator)op
230 {
231 SearchContext *context;
232 void *function;
233
234 if ( _searchTask ) {
235 ChazLog( @"there is already a search task" );
236 return;
237 }
238
239 if ( [_searchResults count] > 0 ) {
240 SearchContext *lastContext = [_searchResults lastObject];
241 if ( ([var type] == TCString) && ([lastContext->value valueSize] < [var valueSize]) ) {
242 // string size not good
243 [_delegate cheater:self didFailLastRequest:@"String is too long."];
244 return;
245 }
246 context = [[SearchContext alloc] initWithLastContext:[_searchResults lastObject] searchOperator:op value:var];
247 }
248 else {
249 context = [[SearchContext alloc] initWithPID:[_target pid] searchOperator:op value:var];
250 }
251
252 function = [context iterationFunction];
253 if ( function ) {
254 SearchContext *searchContext = context;
255 if (searchContext->value->_type != TCFloat && searchContext->value->_type != TCDouble)
256 {
257 bigEndianValue(searchContext->value->_value, searchContext->value);
258 }
259
260 _searchTask = [[ThreadedTask alloc] initWithFunction:function
261 context:context
262 delegate:self];
263
264 if ( ![_searchTask run] ) {
265 // task didn't run
266 [_searchTask release];
267 _searchTask = nil;
268 [_delegate cheater:self didFailLastRequest:@"Internal error."];
269 }
270 }
271 else {
272 [_delegate cheater:self didFailLastRequest:@"Invalid search parameters."];
273 }
274
275 [context release];
276 }
277
278
279 - (void)searchLastValuesComparison:(TCSearchOperator)op
280 {
281 SearchContext *context;
282 void *function;
283
284 if ( _searchTask ) {
285 ChazLog( @"there is already a search task" );
286 return;
287 }
288
289 if ( [_searchResults count] > 0 ) {
290 context = [[SearchContext alloc] initWithLastContext:[_searchResults lastObject] searchOperator:op];
291 }
292 else {
293 ChazLog( @"doing a searchLastValues search without previous results..." );
294 [_delegate cheater:self didFailLastRequest:@"Invalid search."];
295 return;
296 }
297
298 function = [context iterationFunction];
299 if ( function ) {
300 _searchTask = [[ThreadedTask alloc] initWithFunction:function
301 context:context
302 delegate:self];
303
304 if ( ![_searchTask run] ) {
305 // task didn't run
306 [_searchTask release];
307 _searchTask = nil;
308 [_delegate cheater:self didFailLastRequest:@"Internal error."];
309 }
310 }
311 else {
312 [_delegate cheater:self didFailLastRequest:@"Invalid search parameters."];
313 }
314
315 [context release];
316 }
317
318 - (void)cancelSearch
319 {
320 [_searchTask cancelWithoutWaiting];
321 }
322
323 - (void)clearSearch
324 {
325 [self _clearSearch];
326 [_delegate cheaterDidClearSearch:self];
327 }
328
329
330 - (void)getMemoryDump
331 {
332 if ( _dumpTask ) {
333 ChazLog( @"there is already a dump task" );
334 return;
335 }
336
337 _dumpTask = [[ThreadedTask alloc] initWithFunction:_MemoryDumpTask
338 context:[[[DumpContext alloc] initWithPID:[_target pid]] autorelease]
339 delegate:self];
340
341 if ( ![_dumpTask run] ) {
342 [_dumpTask release];
343 _dumpTask = nil;
344 [_delegate cheater:self didFailLastRequest:@"Internal error."];
345 }
346 }
347
348 int _MemoryDumpTask( ThreadedTask *task, unsigned iteration )
349 {
350 DumpContext *context = [task context];
351 VMRegion region = VMNextRegionWithAttributes( context->process, context->lastRegion, VMREGION_READABLE );
352
353 if ( VMRegionIsNotNull( region ) ) {
354 [context->dump appendData:VMRegionData( region )];
355
356 context->lastRegion = region;
357
358 // continue looping
359 return 1;
360 }
361 else {
362 // no more regions, exit
363 return 0;
364 }
365 }
366
367 - (void)cancelMemoryDump
368 {
369 [_dumpTask cancelWithoutWaiting];
370 }
371
372
373 - (void)makeVariableChanges:(NSArray *)variables repeat:(BOOL)doRepeat interval:(NSTimeInterval)repeatInterval
374 {
375 unsigned changes;
376
377 [self stopChangingVariables];
378
379 // repeat the changes if necessary
380 if ( doRepeat ) {
381 if ( _shouldCopy ) {
382 _cheatVariables = [variables copy];
383 }
384 else {
385 _cheatVariables = [variables retain];
386 }
387 _cheatTimer = [[NSTimer scheduledTimerWithTimeInterval:repeatInterval
388 target:self
389 selector:@selector(_changeTimer:)
390 userInfo:nil
391 repeats:YES] retain];
392 [[NSRunLoop currentRunLoop] addTimer:_cheatTimer forMode:NSEventTrackingRunLoopMode];
393 [[NSRunLoop currentRunLoop] addTimer:_cheatTimer forMode:NSModalPanelRunLoopMode];
394 }
395
396 // change variables
397 changes = [self _doChange:variables];
398 [_delegate cheater:self didChangeVariables:changes];
399 }
400
401 - (void)stopChangingVariables
402 {
403 if ( _cheatVariables ) {
404 [_cheatTimer invalidate];
405 [_cheatTimer release];
406 _cheatTimer = nil;
407 [_cheatVariables release];
408 _cheatVariables = nil;
409
410 // report back to delegate
411 [_delegate cheaterDidStopChangingVariables:self];
412 }
413 }
414
415
416 - (void)undo
417 {
418 SearchContext *searchContext;
419 TCArray variables;
420 TCArray values;
421
422 if ( searchContext = [_searchResults lastObject] ) {
423 [_savedResults addObject:searchContext];
424 [_searchResults removeLastObject];
425
426 [self stopWatchingVariables];
427
428 if ( searchContext = [_searchResults lastObject] ) {
429 if ( _shouldCopy ) {
430 variables = TCArrayCopyElements( searchContext->addresses, _returnLimit );
431 values = TCArrayCopyElements( searchContext->values, _returnLimit );
432 }
433 else {
434 variables = TCArrayCopyContainer( searchContext->addresses, _returnLimit );
435 values = TCArrayCopyContainer( searchContext->values, _returnLimit );
436 }
437
438 [_delegate cheater:self didRevertToVariables:variables actualAmount:TCArrayElementCount( searchContext->addresses )];
439 [_delegate cheater:self didFindValues:values];
440 }
441
442 [_delegate cheaterDidUndo:self];
443 }
444 }
445
446 - (void)redo
447 {
448 SearchContext *searchContext;
449 TCArray variables;
450 TCArray values;
451
452 [self stopWatchingVariables];
453
454 if ( searchContext = [_savedResults lastObject] ) {
455 [_searchResults addObject:searchContext];
456 [_savedResults removeLastObject];
457
458 if ( _shouldCopy ) {
459 variables = TCArrayCopyElements( searchContext->addresses, _returnLimit );
460 values = TCArrayCopyElements( searchContext->values, _returnLimit );
461 }
462 else {
463 variables = TCArrayCopyContainer( searchContext->addresses, _returnLimit );
464 values = TCArrayCopyContainer( searchContext->values, _returnLimit );
465 }
466
467 [_delegate cheater:self didRevertToVariables:variables actualAmount:TCArrayElementCount( searchContext->addresses )];
468 [_delegate cheater:self didFindValues:values];
469 [_delegate cheaterDidRedo:self];
470 }
471 }
472
473
474 - (void)watchVariablesAtIndex:(unsigned)index count:(unsigned)count interval:(NSTimeInterval)checkInterval
475 {
476 SearchContext *context;
477 unsigned i, top;
478
479 ChazLog( @"watchVariablesAtIndex:.." );
480
481 [self stopWatchingVariables];
482
483 if ( count == 0 ) {
484 ChazLog( @"invalid watch parameters: 0 count" );
485 return;
486 }
487
488 if ( context = [_searchResults lastObject] ) {
489 TCArray addresses = context->addresses;
490 TCArray values = context->values;
491 // check the index & count
492 if ( index + count <= TCArrayElementCount( addresses ) ) {
493 // save current values
494 NSMutableArray *vars = [[NSMutableArray alloc] initWithCapacity:count];
495 top = index + count;
496 for ( i = index; i < top; i++ ) {
497 Variable *var = [[Variable alloc] initWithType:[context variableType] integerSign:[context integerSign]];
498 [var setProcess:_target];
499 [var setAddress:*(TCAddress *)TCArrayElementAtIndex( addresses, i )];
500 [var setValue:TCArrayElementAtIndex(values, i) size:TCArrayElementSize(values)];
501 [vars addObject:var];
502 [var release];
503 }
504 _watchVariables = [[NSArray arrayWithArray:vars] retain];
505 [vars release];
506
507 _watchRange = NSMakeRange( index, count );
508 _watchTimer = [[NSTimer scheduledTimerWithTimeInterval:checkInterval
509 target:self
510 selector:@selector(_watchTimer:)
511 userInfo:nil
512 repeats:YES] retain];
513 [[NSRunLoop currentRunLoop] addTimer:_watchTimer forMode:NSEventTrackingRunLoopMode];
514 [[NSRunLoop currentRunLoop] addTimer:_watchTimer forMode:NSModalPanelRunLoopMode];
515
516 // do a watch check right now
517 [self _doWatch];
518 }
519 else {
520 ChazLog( @"invalid watch parameters" );
521 }
522 }
523 }
524
525 - (void)stopWatchingVariables
526 {
527 if ( _watchVariables ) {
528 [_watchTimer invalidate];
529 [_watchTimer release];
530 _watchTimer = nil;
531 [_watchVariables release];
532 _watchVariables = nil;
533 }
534 }
535
536
537 // #############################################################################
538 #pragma mark ThreadedTaskDelegate
539 // #############################################################################
540
541 - (void)threadedTaskFinished:(ThreadedTask *)theTask
542 {
543 id context = [theTask context];
544
545 [self stopWatchingVariables];
546
547 ChazLog( @"threaded task finished" );
548
549 if ( [context isKindOfClass:[SearchContext class]] ) {
550 SearchContext *searchContext = context;
551 TCArray variables;
552 TCArray values;
553
554 if ( _shouldCopy ) {
555 variables = TCArrayCopyElements( searchContext->addresses, _returnLimit );
556 values = TCArrayCopyElements( searchContext->values, _returnLimit );
557 }
558 else {
559 variables = TCArrayCopyContainer( searchContext->addresses, _returnLimit );
560 values = TCArrayCopyContainer( searchContext->values, _returnLimit );
561 }
562
563 [_searchResults addObject:searchContext];
564 [_searchTask release];
565 _searchTask = nil;
566
567 [_delegate cheater:self didFindVariables:variables actualAmount:TCArrayElementCount( searchContext->addresses )];
568 [_delegate cheater:self didFindValues:values];
569 }
570 else if ( [context isKindOfClass:[DumpContext class]] ) {
571 DumpContext *dumpContext = context;
572 [_delegate cheater:self didDumpMemory:dumpContext->dump];
573 [_dumpTask release];
574 _dumpTask = nil;
575 }
576 }
577
578 - (void)threadedTaskCancelled:(ThreadedTask *)theTask
579 {
580 id context = [theTask context];
581 ChazLog( @"threaded task cancelled" );
582
583 if ( [context isKindOfClass:[SearchContext class]] ) {
584 [_delegate cheaterDidCancelSearch:self];
585 [_searchTask release];
586 _searchTask = nil;
587 }
588 else if ( [context isKindOfClass:[DumpContext class]] ) {
589 [_delegate cheaterDidCancelMemoryDump:self];
590 [_dumpTask release];
591 _dumpTask = nil;
592 }
593 }
594
595 - (void)threadedTask:(ThreadedTask *)theTask failedWithErrorCode:(int)errorCode
596 {
597 id context = [theTask context];
598 ChazLog( @"threaded task failed with code: %i", errorCode );
599
600 if ( [context isKindOfClass:[SearchContext class]] ) {
601 [_delegate cheater:self didFailLastRequest:[NSString stringWithFormat:@"Search failed with error: %i", errorCode]];
602 [_searchTask release];
603 _searchTask = nil;
604 }
605 else if ( [context isKindOfClass:[DumpContext class]] ) {
606 [_delegate cheater:self didFailLastRequest:[NSString stringWithFormat:@"Dump failed with error: %i", errorCode]];
607 [_dumpTask release];
608 _dumpTask = nil;
609 }
610 }
611
612 - (void)threadedTask:(ThreadedTask *)theTask reportedProgress:(int)theProgress
613 {
614 [_delegate cheater:self didReportProgress:theProgress];
615 }
616
617
618 // #############################################################################
619 #pragma mark Private Methods
620 // #############################################################################
621
622 - (void)_clearSearch
623 {
624 [self stopWatchingVariables];
625 [self cancelSearch];
626
627 // empty the results array
628 [_searchResults removeAllObjects];
629 [_savedResults removeAllObjects];
630 }
631
632 - (BOOL)_pauseTarget
633 {
634 // attempt to pause
635 if ( VMStopProcess( [_target pid] ) ) {
636 _isTargetPaused = YES;
637 return YES;
638 }
639 _isTargetPaused = NO;
640 return NO;
641 }
642
643 - (BOOL)_resumeTarget
644 {
645 if ( VMContinueProcess( [_target pid] ) ) {
646 _isTargetPaused = NO;
647 return YES;
648 }
649 _isTargetPaused = YES;
650 return NO;
651 }
652
653
654 - (void)_applicationLaunched:(NSNotification *)note
655 {
656 NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
657 NSDictionary *application = [note userInfo];
658 NSString *bundleID;
659
660 // don't allow The Cheat to be cheated
661 bundleID = [application objectForKey:@"NSApplicationBundleIdentifier"];
662 if ( bundleID && [bundleID isEqualToString:[[NSBundle mainBundle] bundleIdentifier]] ) {
663 return;
664 }
665
666 Process *process = [[Process alloc] initWithName:[application objectForKey:@"NSApplicationName"]
667 version:ApplicationVersion( [application objectForKey:@"NSApplicationPath"] )
668 icon:[workspace iconForFile:[application objectForKey:@"NSApplicationPath"]]
669 pid:[[application objectForKey:@"NSApplicationProcessIdentifier"] intValue]];
670 if ( ![_processes containsObject:process] ) {
671 [_processes addObject:process];
672 // inform the delegate of the new process
673 [_delegate cheater:self didAddProcess:process];
674 }
675 // cleanup
676 [process release];
677 }
678
679 - (void)_applicationTerminated:(NSNotification *)note
680 {
681 NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
682 NSDictionary *application = [note userInfo];
683
684 Process *process = [[Process alloc] initWithName:[application objectForKey:@"NSApplicationName"]
685 version:ApplicationVersion( [application objectForKey:@"NSApplicationPath"] )
686 icon:[workspace iconForFile:[application objectForKey:@"NSApplicationPath"]]
687 pid:[[application objectForKey:@"NSApplicationProcessIdentifier"] intValue]];
688
689 // if this is the current process, take appropriate actions
690 if ( [_target isEqual:process] ) {
691 [self cancelSearch];
692 [self clearSearch];
693 [self cancelMemoryDump];
694 [self stopChangingVariables];
695 [_delegate cheater:self didRemoveProcess:process];
696 [_delegate cheater:self didFailLastRequest:@"The target quit."];
697 }
698 else {
699 // inform the delegate of the removed process
700 [_delegate cheater:self didRemoveProcess:process];
701 }
702 [_processes removeObject:process];
703
704 // cleanup
705 [process release];
706 }
707
708
709 - (unsigned)_doChange:(NSArray *)variables
710 {
711 unsigned i, top;
712 unsigned successes = 0;
713
714 top = [variables count];
715 for ( i = 0; i < top; i++ ) {
716 Variable *variable = [variables objectAtIndex:i];
717
718 if ([[variable process] pid] != [_target pid])
719 {
720 [variable setProcess:_target];
721 }
722
723 char buffer[variable->_size];
724 memcpy(buffer, variable->_value, variable->_size);
725 bigEndianValue(buffer, variable);
726
727 if ( VMWriteBytes( [_target pid], [variable address], buffer, [variable valueSize] ) )
728 {
729 successes++;
730 }
731 }
732 return successes;
733 }
734
735 - (void)_changeTimer:(NSTimer *)timer
736 {
737 unsigned changes = [self _doChange:_cheatVariables];
738 [_delegate cheater:self didChangeVariables:changes];
739 }
740
741
742 - (void)_doWatch
743 {
744 unsigned i, top;
745 char value[TC_MAX_VAR_SIZE];
746 vm_size_t size;
747
748 top = [_watchVariables count];
749 for ( i = 0; i < top; i++ ) {
750 Variable *variable = [_watchVariables objectAtIndex:i];
751
752 size = [variable valueSize];
753 if ( VMReadBytes( [_target pid], [variable address], value, &size ) ) {
754 bigEndianValue(value, variable);
755
756 // check if memory changed
757 if (memcmp(value, variable->_value, size) != 0)
758 {
759 [variable setValue:value];
760 // inform delegate of the change
761 [_delegate cheater:self variableAtIndex:_watchRange.location+i didChangeTo:variable];
762 }
763 }
764 }
765 }
766
767 - (void)_watchTimer:(NSTimer *)timer
768 {
769 [self _doWatch];
770 }
771
772
773 @end
This page took 0.061287 seconds and 4 git commands to generate.