]> Dogcows Code - chaz/thecheat/blob - LocalCheater.m
The Cheat 1.2.3
[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 _searchTask = [[ThreadedTask alloc] initWithFunction:function
255 context:context
256 delegate:self];
257
258 if ( ![_searchTask run] ) {
259 // task didn't run
260 [_searchTask release];
261 _searchTask = nil;
262 [_delegate cheater:self didFailLastRequest:@"Internal error."];
263 }
264 }
265 else {
266 [_delegate cheater:self didFailLastRequest:@"Invalid search parameters."];
267 }
268
269 [context release];
270 }
271
272
273 - (void)searchLastValuesComparison:(TCSearchOperator)op
274 {
275 SearchContext *context;
276 void *function;
277
278 if ( _searchTask ) {
279 ChazLog( @"there is already a search task" );
280 return;
281 }
282
283 if ( [_searchResults count] > 0 ) {
284 context = [[SearchContext alloc] initWithLastContext:[_searchResults lastObject] searchOperator:op];
285 }
286 else {
287 ChazLog( @"doing a searchLastValues search without previous results..." );
288 [_delegate cheater:self didFailLastRequest:@"Invalid search."];
289 return;
290 }
291
292 function = [context iterationFunction];
293 if ( function ) {
294 _searchTask = [[ThreadedTask alloc] initWithFunction:function
295 context:context
296 delegate:self];
297
298 if ( ![_searchTask run] ) {
299 // task didn't run
300 [_searchTask release];
301 _searchTask = nil;
302 [_delegate cheater:self didFailLastRequest:@"Internal error."];
303 }
304 }
305 else {
306 [_delegate cheater:self didFailLastRequest:@"Invalid search parameters."];
307 }
308
309 [context release];
310 }
311
312 - (void)cancelSearch
313 {
314 [_searchTask cancelWithoutWaiting];
315 }
316
317 - (void)clearSearch
318 {
319 [self _clearSearch];
320 [_delegate cheaterDidClearSearch:self];
321 }
322
323
324 - (void)getMemoryDump
325 {
326 if ( _dumpTask ) {
327 ChazLog( @"there is already a dump task" );
328 return;
329 }
330
331 _dumpTask = [[ThreadedTask alloc] initWithFunction:_MemoryDumpTask
332 context:[[[DumpContext alloc] initWithPID:[_target pid]] autorelease]
333 delegate:self];
334
335 if ( ![_dumpTask run] ) {
336 [_dumpTask release];
337 _dumpTask = nil;
338 [_delegate cheater:self didFailLastRequest:@"Internal error."];
339 }
340 }
341
342 int _MemoryDumpTask( ThreadedTask *task, unsigned iteration )
343 {
344 DumpContext *context = [task context];
345 VMRegion region = VMNextRegionWithAttributes( context->process, context->lastRegion, VMREGION_READABLE );
346
347 if ( VMRegionIsNotNull( region ) ) {
348 [context->dump appendData:VMRegionData( region )];
349
350 context->lastRegion = region;
351
352 // continue looping
353 return 1;
354 }
355 else {
356 // no more regions, exit
357 return 0;
358 }
359 }
360
361 - (void)cancelMemoryDump
362 {
363 [_dumpTask cancelWithoutWaiting];
364 }
365
366
367 - (void)makeVariableChanges:(NSArray *)variables repeat:(BOOL)doRepeat interval:(NSTimeInterval)repeatInterval
368 {
369 unsigned changes;
370
371 [self stopChangingVariables];
372
373 // repeat the changes if necessary
374 if ( doRepeat ) {
375 if ( _shouldCopy ) {
376 _cheatVariables = [variables copy];
377 }
378 else {
379 _cheatVariables = [variables retain];
380 }
381 _cheatTimer = [[NSTimer scheduledTimerWithTimeInterval:repeatInterval
382 target:self
383 selector:@selector(_changeTimer:)
384 userInfo:nil
385 repeats:YES] retain];
386 [[NSRunLoop currentRunLoop] addTimer:_cheatTimer forMode:NSEventTrackingRunLoopMode];
387 [[NSRunLoop currentRunLoop] addTimer:_cheatTimer forMode:NSModalPanelRunLoopMode];
388 }
389
390 // change variables
391 changes = [self _doChange:variables];
392 [_delegate cheater:self didChangeVariables:changes];
393 }
394
395 - (void)stopChangingVariables
396 {
397 if ( _cheatVariables ) {
398 [_cheatTimer invalidate];
399 [_cheatTimer release];
400 _cheatTimer = nil;
401 [_cheatVariables release];
402 _cheatVariables = nil;
403
404 // report back to delegate
405 [_delegate cheaterDidStopChangingVariables:self];
406 }
407 }
408
409
410 - (void)undo
411 {
412 SearchContext *searchContext;
413 TCArray variables;
414 TCArray values;
415
416 if ( searchContext = [_searchResults lastObject] ) {
417 [_savedResults addObject:searchContext];
418 [_searchResults removeLastObject];
419
420 [self stopWatchingVariables];
421
422 if ( searchContext = [_searchResults lastObject] ) {
423 if ( _shouldCopy ) {
424 variables = TCArrayCopyElements( searchContext->addresses, _returnLimit );
425 values = TCArrayCopyElements( searchContext->values, _returnLimit );
426 }
427 else {
428 variables = TCArrayCopyContainer( searchContext->addresses, _returnLimit );
429 values = TCArrayCopyContainer( searchContext->values, _returnLimit );
430 }
431
432 [_delegate cheater:self didRevertToVariables:variables actualAmount:TCArrayElementCount( searchContext->addresses )];
433 [_delegate cheater:self didFindValues:values];
434 }
435
436 [_delegate cheaterDidUndo:self];
437 }
438 }
439
440 - (void)redo
441 {
442 SearchContext *searchContext;
443 TCArray variables;
444 TCArray values;
445
446 [self stopWatchingVariables];
447
448 if ( searchContext = [_savedResults lastObject] ) {
449 [_searchResults addObject:searchContext];
450 [_savedResults removeLastObject];
451
452 if ( _shouldCopy ) {
453 variables = TCArrayCopyElements( searchContext->addresses, _returnLimit );
454 values = TCArrayCopyElements( searchContext->values, _returnLimit );
455 }
456 else {
457 variables = TCArrayCopyContainer( searchContext->addresses, _returnLimit );
458 values = TCArrayCopyContainer( searchContext->values, _returnLimit );
459 }
460
461 [_delegate cheater:self didRevertToVariables:variables actualAmount:TCArrayElementCount( searchContext->addresses )];
462 [_delegate cheater:self didFindValues:values];
463 [_delegate cheaterDidRedo:self];
464 }
465 }
466
467
468 - (void)watchVariablesAtIndex:(unsigned)index count:(unsigned)count interval:(NSTimeInterval)checkInterval
469 {
470 SearchContext *context;
471 unsigned i, top;
472
473 ChazLog( @"watchVariablesAtIndex:.." );
474
475 [self stopWatchingVariables];
476
477 if ( count == 0 ) {
478 ChazLog( @"invalid watch parameters: 0 count" );
479 return;
480 }
481
482 if ( context = [_searchResults lastObject] ) {
483 TCArray addresses = context->addresses;
484 TCArray values = context->values;
485 // check the index & count
486 if ( index + count <= TCArrayElementCount( addresses ) ) {
487 // save current values
488 NSMutableArray *vars = [[NSMutableArray alloc] initWithCapacity:count];
489 top = index + count;
490 for ( i = index; i < top; i++ ) {
491 Variable *var = [[Variable alloc] initWithType:[context variableType] integerSign:[context integerSign]];
492 [var setAddress:*(TCAddress *)TCArrayElementAtIndex( addresses, i )];
493 [var setValue:TCArrayElementAtIndex( values, i ) size:TCArrayElementSize(values)];
494 [vars addObject:var];
495 [var release];
496 }
497 _watchVariables = [[NSArray arrayWithArray:vars] retain];
498 [vars release];
499
500 _watchRange = NSMakeRange( index, count );
501 _watchTimer = [[NSTimer scheduledTimerWithTimeInterval:checkInterval
502 target:self
503 selector:@selector(_watchTimer:)
504 userInfo:nil
505 repeats:YES] retain];
506 [[NSRunLoop currentRunLoop] addTimer:_watchTimer forMode:NSEventTrackingRunLoopMode];
507 [[NSRunLoop currentRunLoop] addTimer:_watchTimer forMode:NSModalPanelRunLoopMode];
508
509 // do a watch check right now
510 [self _doWatch];
511 }
512 else {
513 ChazLog( @"invalid watch parameters" );
514 }
515 }
516 }
517
518 - (void)stopWatchingVariables
519 {
520 if ( _watchVariables ) {
521 [_watchTimer invalidate];
522 [_watchTimer release];
523 _watchTimer = nil;
524 [_watchVariables release];
525 _watchVariables = nil;
526 }
527 }
528
529
530 // #############################################################################
531 #pragma mark ThreadedTaskDelegate
532 // #############################################################################
533
534 - (void)threadedTaskFinished:(ThreadedTask *)theTask
535 {
536 id context = [theTask context];
537
538 [self stopWatchingVariables];
539
540 ChazLog( @"threaded task finished" );
541
542 if ( [context isKindOfClass:[SearchContext class]] ) {
543 SearchContext *searchContext = context;
544 TCArray variables;
545 TCArray values;
546
547 if ( _shouldCopy ) {
548 variables = TCArrayCopyElements( searchContext->addresses, _returnLimit );
549 values = TCArrayCopyElements( searchContext->values, _returnLimit );
550 }
551 else {
552 variables = TCArrayCopyContainer( searchContext->addresses, _returnLimit );
553 values = TCArrayCopyContainer( searchContext->values, _returnLimit );
554 }
555
556 [_searchResults addObject:searchContext];
557 [_searchTask release];
558 _searchTask = nil;
559
560 [_delegate cheater:self didFindVariables:variables actualAmount:TCArrayElementCount( searchContext->addresses )];
561 [_delegate cheater:self didFindValues:values];
562 }
563 else if ( [context isKindOfClass:[DumpContext class]] ) {
564 DumpContext *dumpContext = context;
565 [_delegate cheater:self didDumpMemory:dumpContext->dump];
566 [_dumpTask release];
567 _dumpTask = nil;
568 }
569 }
570
571 - (void)threadedTaskCancelled:(ThreadedTask *)theTask
572 {
573 id context = [theTask context];
574 ChazLog( @"threaded task cancelled" );
575
576 if ( [context isKindOfClass:[SearchContext class]] ) {
577 [_delegate cheaterDidCancelSearch:self];
578 [_searchTask release];
579 _searchTask = nil;
580 }
581 else if ( [context isKindOfClass:[DumpContext class]] ) {
582 [_delegate cheaterDidCancelMemoryDump:self];
583 [_dumpTask release];
584 _dumpTask = nil;
585 }
586 }
587
588 - (void)threadedTask:(ThreadedTask *)theTask failedWithErrorCode:(int)errorCode
589 {
590 id context = [theTask context];
591 ChazLog( @"threaded task failed with code: %i", errorCode );
592
593 if ( [context isKindOfClass:[SearchContext class]] ) {
594 [_delegate cheater:self didFailLastRequest:[NSString stringWithFormat:@"Search failed with error: %i", errorCode]];
595 [_searchTask release];
596 _searchTask = nil;
597 }
598 else if ( [context isKindOfClass:[DumpContext class]] ) {
599 [_delegate cheater:self didFailLastRequest:[NSString stringWithFormat:@"Dump failed with error: %i", errorCode]];
600 [_dumpTask release];
601 _dumpTask = nil;
602 }
603 }
604
605 - (void)threadedTask:(ThreadedTask *)theTask reportedProgress:(int)theProgress
606 {
607 [_delegate cheater:self didReportProgress:theProgress];
608 }
609
610
611 // #############################################################################
612 #pragma mark Private Methods
613 // #############################################################################
614
615 - (void)_clearSearch
616 {
617 [self stopWatchingVariables];
618 [self cancelSearch];
619
620 // empty the results array
621 [_searchResults removeAllObjects];
622 [_savedResults removeAllObjects];
623 }
624
625 - (BOOL)_pauseTarget
626 {
627 // attempt to pause
628 if ( VMStopProcess( [_target pid] ) ) {
629 _isTargetPaused = YES;
630 return YES;
631 }
632 _isTargetPaused = NO;
633 return NO;
634 }
635
636 - (BOOL)_resumeTarget
637 {
638 if ( VMContinueProcess( [_target pid] ) ) {
639 _isTargetPaused = NO;
640 return YES;
641 }
642 _isTargetPaused = YES;
643 return NO;
644 }
645
646
647 - (void)_applicationLaunched:(NSNotification *)note
648 {
649 NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
650 NSDictionary *application = [note userInfo];
651 NSString *bundleID;
652
653 // don't allow The Cheat to be cheated
654 bundleID = [application objectForKey:@"NSApplicationBundleIdentifier"];
655 if ( bundleID && [bundleID isEqualToString:[[NSBundle mainBundle] bundleIdentifier]] ) {
656 return;
657 }
658
659 Process *process = [[Process alloc] initWithName:[application objectForKey:@"NSApplicationName"]
660 version:ApplicationVersion( [application objectForKey:@"NSApplicationPath"] )
661 icon:[workspace iconForFile:[application objectForKey:@"NSApplicationPath"]]
662 pid:[[application objectForKey:@"NSApplicationProcessIdentifier"] intValue]];
663 if ( ![_processes containsObject:process] ) {
664 [_processes addObject:process];
665 // inform the delegate of the new process
666 [_delegate cheater:self didAddProcess:process];
667 }
668 // cleanup
669 [process release];
670 }
671
672 - (void)_applicationTerminated:(NSNotification *)note
673 {
674 NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
675 NSDictionary *application = [note userInfo];
676
677 Process *process = [[Process alloc] initWithName:[application objectForKey:@"NSApplicationName"]
678 version:ApplicationVersion( [application objectForKey:@"NSApplicationPath"] )
679 icon:[workspace iconForFile:[application objectForKey:@"NSApplicationPath"]]
680 pid:[[application objectForKey:@"NSApplicationProcessIdentifier"] intValue]];
681
682 // if this is the current process, take appropriate actions
683 if ( [_target isEqual:process] ) {
684 [self cancelSearch];
685 [self clearSearch];
686 [self cancelMemoryDump];
687 [self stopChangingVariables];
688 [_delegate cheater:self didRemoveProcess:process];
689 [_delegate cheater:self didFailLastRequest:@"The target quit."];
690 }
691 else {
692 // inform the delegate of the removed process
693 [_delegate cheater:self didRemoveProcess:process];
694 }
695 [_processes removeObject:process];
696
697 // cleanup
698 [process release];
699 }
700
701
702 - (unsigned)_doChange:(NSArray *)variables
703 {
704 unsigned i, top;
705 unsigned successes = 0;
706
707 top = [variables count];
708 for ( i = 0; i < top; i++ ) {
709 Variable *variable = [variables objectAtIndex:i];
710
711 if ( VMWriteBytes( [_target pid], [variable address], [variable value], [variable valueSize] ) ) {
712 successes++;
713 }
714 }
715 return successes;
716 }
717
718 - (void)_changeTimer:(NSTimer *)timer
719 {
720 unsigned changes = [self _doChange:_cheatVariables];
721 [_delegate cheater:self didChangeVariables:changes];
722 }
723
724
725 - (void)_doWatch
726 {
727 unsigned i, top;
728 char value[TC_MAX_VAR_SIZE];
729 vm_size_t size;
730
731 top = [_watchVariables count];
732 for ( i = 0; i < top; i++ ) {
733 Variable *variable = [_watchVariables objectAtIndex:i];
734
735 size = [variable valueSize];
736 if ( VMReadBytes( [_target pid], [variable address], value, &size ) ) {
737 // check if memory changed
738 if ( memcmp( value, [variable value], size ) != 0 ) {
739 [variable setValue:value];
740 // inform delegate of the change
741 [_delegate cheater:self variableAtIndex:_watchRange.location+i didChangeTo:variable];
742 }
743 }
744 }
745 }
746
747 - (void)_watchTimer:(NSTimer *)timer
748 {
749 [self _doWatch];
750 }
751
752
753 @end
This page took 0.063896 seconds and 4 git commands to generate.