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 "LocalCheater.h"
23 #include "cheat_global.h"
26 // memory dump function
27 int _MemoryDumpTask( ThreadedTask
*task
, unsigned iteration
);
30 @interface LocalCheater ( Private
)
32 // internal methods so they can be used throughout the class
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
;
40 - (unsigned)_doChange
:(NSArray
*)variables
;
41 - (void)_changeTimer
:(NSTimer
*)timer
;
44 - (void)_watchTimer
:(NSTimer
*)timer
;
46 - (int)_memoryDumpTask
:(ThreadedTask
*)task iteration
:(unsigned)iteration
;
51 @implementation LocalCheater
56 if ( self = [super init
] ) {
57 NSNotificationCenter
*nc
= [[NSWorkspace sharedWorkspace
] notificationCenter
];
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];
63 _searchResults
= [[NSMutableArray alloc
] init
];
64 _savedResults
= [[NSMutableArray alloc
] init
];
72 [(NSNotificationCenter
*)[[NSWorkspace sharedWorkspace
] notificationCenter
] removeObserver
:self];
74 // make sure the process isn't left paused
77 // release local objects
81 // make sure everything is cancelled.
82 [_searchTask cancelAndRemoveDelegate
];
83 [_dumpTask cancelAndRemoveDelegate
];
84 [self stopChangingVariables
];
85 [self stopWatchingVariables
];
87 [_searchTask release
];
101 - (void)setShouldCopy
:(BOOL)flag
107 - (NSString
*)hostAddress
109 //return @"127.0.0.1";
110 return @
"this computer";
114 // #############################################################################
115 #pragma mark Cheater Override
116 // #############################################################################
120 if ( !_isConnected
) {
122 // return as connected
123 [_delegate cheaterDidConnect
:self];
129 if ( _isConnected
) {
130 [_searchTask cancelAndRemoveDelegate
];
131 [_dumpTask cancelAndRemoveDelegate
];
132 [self stopChangingVariables
];
133 [self stopWatchingVariables
];
136 // return as disconnected
137 //[_delegate cheaterDidDisconnect:self];
141 - (void)authenticateWithPassword
:(NSString
*)password
143 // being a local cheater, authentication really isn't required.
144 // return succes every time.
145 _isAuthenticated
= YES
;
146 [_delegate cheaterAcceptedPassword
:self];
150 - (void)getProcessList
152 NSWorkspace
*workspace
= [NSWorkspace sharedWorkspace
];
153 NSArray
*launchedApps
= [workspace launchedApplications
];
154 unsigned i
, len
= [launchedApps count
];
157 _processes
= [[NSMutableArray alloc
] initWithCapacity
:len
];
160 // compile process array
161 for ( i
= 0; i
< len
; i
++ ) {
162 NSDictionary
*application
= [launchedApps objectAtIndex
:i
];
164 // don't allow The Cheat to be cheated
165 if ( [[application objectForKey
:@
"NSApplicationBundleIdentifier"] isEqualToString
:[[NSBundle mainBundle
] bundleIdentifier
]] ) {
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
];
177 // return process list
178 [_delegate cheater
:self didFindProcesses
:[NSArray arrayWithArray
:_processes
]];
182 - (void)setTarget
:(Process
*)target
184 // unpause the current target if needed
188 // set the new target
192 _target
= [target copy
];
193 [_delegate cheater
:self didSetTarget
:[_target copy
]];
196 _target
= [target retain
];
197 [_delegate cheater
:self didSetTarget
:_target
];
203 // attempt to pause the current target
204 if ( !_isTargetPaused
&& [self _pauseTarget
] ) {
205 [_delegate cheaterPausedTarget
:self];
208 [_delegate cheater
:self echo
:@
"This process cannot be paused."];
214 // attempt to resume the current target
215 if ( _isTargetPaused
&& [self _resumeTarget
] ) {
216 [_delegate cheaterResumedTarget
:self];
219 [_delegate cheater
:self echo
:@
"This process cannot be resumed."];
224 - (void)limitReturnedResults
:(unsigned)limit
226 _returnLimit
= limit
;
229 - (void)searchForVariable
:(Variable
*)var comparison
:(TCSearchOperator
)op
231 SearchContext
*context
;
235 ChazLog( @
"there is already a search task" );
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."];
246 context
= [[SearchContext alloc
] initWithLastContext
:[_searchResults lastObject
] searchOperator
:op value
:var
];
249 context
= [[SearchContext alloc
] initWithPID
:[_target pid
] searchOperator
:op value
:var
];
252 function
= [context iterationFunction
];
254 _searchTask
= [[ThreadedTask alloc
] initWithFunction
:function
258 if ( ![_searchTask run
] ) {
260 [_searchTask release
];
262 [_delegate cheater
:self didFailLastRequest
:@
"Internal error."];
266 [_delegate cheater
:self didFailLastRequest
:@
"Invalid search parameters."];
273 - (void)searchLastValuesComparison
:(TCSearchOperator
)op
275 SearchContext
*context
;
279 ChazLog( @
"there is already a search task" );
283 if ( [_searchResults count
] > 0 ) {
284 context
= [[SearchContext alloc
] initWithLastContext
:[_searchResults lastObject
] searchOperator
:op
];
287 ChazLog( @
"doing a searchLastValues search without previous results..." );
288 [_delegate cheater
:self didFailLastRequest
:@
"Invalid search."];
292 function
= [context iterationFunction
];
294 _searchTask
= [[ThreadedTask alloc
] initWithFunction
:function
298 if ( ![_searchTask run
] ) {
300 [_searchTask release
];
302 [_delegate cheater
:self didFailLastRequest
:@
"Internal error."];
306 [_delegate cheater
:self didFailLastRequest
:@
"Invalid search parameters."];
314 [_searchTask cancelWithoutWaiting
];
320 [_delegate cheaterDidClearSearch
:self];
324 - (void)getMemoryDump
327 ChazLog( @
"there is already a dump task" );
331 _dumpTask
= [[ThreadedTask alloc
] initWithFunction
:_MemoryDumpTask
332 context
:[[[DumpContext alloc
] initWithPID
:[_target pid
]] autorelease
]
335 if ( ![_dumpTask run
] ) {
338 [_delegate cheater
:self didFailLastRequest
:@
"Internal error."];
342 int _MemoryDumpTask( ThreadedTask
*task
, unsigned iteration
)
344 DumpContext
*context
= [task context
];
345 VMRegion region
= VMNextRegionWithAttributes( context
->process
, context
->lastRegion
, VMREGION_READABLE
);
347 if ( VMRegionIsNotNull( region
) ) {
348 [context
->dump appendData
:VMRegionData( region
)];
350 context
->lastRegion
= region
;
356 // no more regions, exit
361 - (void)cancelMemoryDump
363 [_dumpTask cancelWithoutWaiting
];
367 - (void)makeVariableChanges
:(NSArray
*)variables repeat
:(BOOL)doRepeat interval
:(NSTimeInterval
)repeatInterval
371 [self stopChangingVariables
];
373 // repeat the changes if necessary
376 _cheatVariables
= [variables copy
];
379 _cheatVariables
= [variables retain
];
381 _cheatTimer
= [[NSTimer scheduledTimerWithTimeInterval
:repeatInterval
383 selector
:@selector(_changeTimer
:)
385 repeats
:YES
] retain
];
386 [[NSRunLoop currentRunLoop
] addTimer
:_cheatTimer forMode
:NSEventTrackingRunLoopMode
];
387 [[NSRunLoop currentRunLoop
] addTimer
:_cheatTimer forMode
:NSModalPanelRunLoopMode
];
391 changes
= [self _doChange
:variables
];
392 [_delegate cheater
:self didChangeVariables
:changes
];
395 - (void)stopChangingVariables
397 if ( _cheatVariables
) {
398 [_cheatTimer invalidate
];
399 [_cheatTimer release
];
401 [_cheatVariables release
];
402 _cheatVariables
= nil;
404 // report back to delegate
405 [_delegate cheaterDidStopChangingVariables
:self];
412 SearchContext
*searchContext
;
416 if ( searchContext
= [_searchResults lastObject
] ) {
417 [_savedResults addObject
:searchContext
];
418 [_searchResults removeLastObject
];
420 [self stopWatchingVariables
];
422 if ( searchContext
= [_searchResults lastObject
] ) {
424 variables
= TCArrayCopyElements( searchContext
->addresses
, _returnLimit
);
425 values
= TCArrayCopyElements( searchContext
->values
, _returnLimit
);
428 variables
= TCArrayCopyContainer( searchContext
->addresses
, _returnLimit
);
429 values
= TCArrayCopyContainer( searchContext
->values
, _returnLimit
);
432 [_delegate cheater
:self didRevertToVariables
:variables actualAmount
:TCArrayElementCount( searchContext
->addresses
)];
433 [_delegate cheater
:self didFindValues
:values
];
436 [_delegate cheaterDidUndo
:self];
442 SearchContext
*searchContext
;
446 [self stopWatchingVariables
];
448 if ( searchContext
= [_savedResults lastObject
] ) {
449 [_searchResults addObject
:searchContext
];
450 [_savedResults removeLastObject
];
453 variables
= TCArrayCopyElements( searchContext
->addresses
, _returnLimit
);
454 values
= TCArrayCopyElements( searchContext
->values
, _returnLimit
);
457 variables
= TCArrayCopyContainer( searchContext
->addresses
, _returnLimit
);
458 values
= TCArrayCopyContainer( searchContext
->values
, _returnLimit
);
461 [_delegate cheater
:self didRevertToVariables
:variables actualAmount
:TCArrayElementCount( searchContext
->addresses
)];
462 [_delegate cheater
:self didFindValues
:values
];
463 [_delegate cheaterDidRedo
:self];
468 - (void)watchVariablesAtIndex
:(unsigned)index count
:(unsigned)count interval
:(NSTimeInterval
)checkInterval
470 SearchContext
*context
;
473 ChazLog( @
"watchVariablesAtIndex:.." );
475 [self stopWatchingVariables
];
478 ChazLog( @
"invalid watch parameters: 0 count" );
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
];
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
];
497 _watchVariables
= [[NSArray arrayWithArray
:vars
] retain
];
500 _watchRange
= NSMakeRange( index
, count
);
501 _watchTimer
= [[NSTimer scheduledTimerWithTimeInterval
:checkInterval
503 selector
:@selector(_watchTimer
:)
505 repeats
:YES
] retain
];
506 [[NSRunLoop currentRunLoop
] addTimer
:_watchTimer forMode
:NSEventTrackingRunLoopMode
];
507 [[NSRunLoop currentRunLoop
] addTimer
:_watchTimer forMode
:NSModalPanelRunLoopMode
];
509 // do a watch check right now
513 ChazLog( @
"invalid watch parameters" );
518 - (void)stopWatchingVariables
520 if ( _watchVariables
) {
521 [_watchTimer invalidate
];
522 [_watchTimer release
];
524 [_watchVariables release
];
525 _watchVariables
= nil;
530 // #############################################################################
531 #pragma mark ThreadedTaskDelegate
532 // #############################################################################
534 - (void)threadedTaskFinished
:(ThreadedTask
*)theTask
536 id context
= [theTask context
];
538 [self stopWatchingVariables
];
540 ChazLog( @
"threaded task finished" );
542 if ( [context isKindOfClass
:[SearchContext
class]] ) {
543 SearchContext
*searchContext
= context
;
548 variables
= TCArrayCopyElements( searchContext
->addresses
, _returnLimit
);
549 values
= TCArrayCopyElements( searchContext
->values
, _returnLimit
);
552 variables
= TCArrayCopyContainer( searchContext
->addresses
, _returnLimit
);
553 values
= TCArrayCopyContainer( searchContext
->values
, _returnLimit
);
556 [_searchResults addObject
:searchContext
];
557 [_searchTask release
];
560 [_delegate cheater
:self didFindVariables
:variables actualAmount
:TCArrayElementCount( searchContext
->addresses
)];
561 [_delegate cheater
:self didFindValues
:values
];
563 else if ( [context isKindOfClass
:[DumpContext
class]] ) {
564 DumpContext
*dumpContext
= context
;
565 [_delegate cheater
:self didDumpMemory
:dumpContext
->dump
];
571 - (void)threadedTaskCancelled
:(ThreadedTask
*)theTask
573 id context
= [theTask context
];
574 ChazLog( @
"threaded task cancelled" );
576 if ( [context isKindOfClass
:[SearchContext
class]] ) {
577 [_delegate cheaterDidCancelSearch
:self];
578 [_searchTask release
];
581 else if ( [context isKindOfClass
:[DumpContext
class]] ) {
582 [_delegate cheaterDidCancelMemoryDump
:self];
588 - (void)threadedTask
:(ThreadedTask
*)theTask failedWithErrorCode
:(int)errorCode
590 id context
= [theTask context
];
591 ChazLog( @
"threaded task failed with code: %i", errorCode
);
593 if ( [context isKindOfClass
:[SearchContext
class]] ) {
594 [_delegate cheater
:self didFailLastRequest
:[NSString stringWithFormat
:@
"Search failed with error: %i", errorCode
]];
595 [_searchTask release
];
598 else if ( [context isKindOfClass
:[DumpContext
class]] ) {
599 [_delegate cheater
:self didFailLastRequest
:[NSString stringWithFormat
:@
"Dump failed with error: %i", errorCode
]];
605 - (void)threadedTask
:(ThreadedTask
*)theTask reportedProgress
:(int)theProgress
607 [_delegate cheater
:self didReportProgress
:theProgress
];
611 // #############################################################################
612 #pragma mark Private Methods
613 // #############################################################################
617 [self stopWatchingVariables
];
620 // empty the results array
621 [_searchResults removeAllObjects
];
622 [_savedResults removeAllObjects
];
628 if ( VMStopProcess( [_target pid
] ) ) {
629 _isTargetPaused
= YES
;
632 _isTargetPaused
= NO
;
636 - (BOOL)_resumeTarget
638 if ( VMContinueProcess( [_target pid
] ) ) {
639 _isTargetPaused
= NO
;
642 _isTargetPaused
= YES
;
647 - (void)_applicationLaunched
:(NSNotification
*)note
649 NSWorkspace
*workspace
= [NSWorkspace sharedWorkspace
];
650 NSDictionary
*application
= [note userInfo
];
653 // don't allow The Cheat to be cheated
654 bundleID
= [application objectForKey
:@
"NSApplicationBundleIdentifier"];
655 if ( bundleID
&& [bundleID isEqualToString
:[[NSBundle mainBundle
] bundleIdentifier
]] ) {
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
];
672 - (void)_applicationTerminated
:(NSNotification
*)note
674 NSWorkspace
*workspace
= [NSWorkspace sharedWorkspace
];
675 NSDictionary
*application
= [note userInfo
];
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
]];
682 // if this is the current process, take appropriate actions
683 if ( [_target isEqual
:process
] ) {
686 [self cancelMemoryDump
];
687 [self stopChangingVariables
];
688 [_delegate cheater
:self didRemoveProcess
:process
];
689 [_delegate cheater
:self didFailLastRequest
:@
"The target quit."];
692 // inform the delegate of the removed process
693 [_delegate cheater
:self didRemoveProcess
:process
];
695 [_processes removeObject
:process
];
702 - (unsigned)_doChange
:(NSArray
*)variables
705 unsigned successes
= 0;
707 top
= [variables count
];
708 for ( i
= 0; i
< top
; i
++ ) {
709 Variable
*variable
= [variables objectAtIndex
:i
];
711 if ( VMWriteBytes( [_target pid
], [variable address
], [variable value
], [variable valueSize
] ) ) {
718 - (void)_changeTimer
:(NSTimer
*)timer
720 unsigned changes
= [self _doChange
:_cheatVariables
];
721 [_delegate cheater
:self didChangeVariables
:changes
];
728 char value
[TC_MAX_VAR_SIZE
];
731 top
= [_watchVariables count
];
732 for ( i
= 0; i
< top
; i
++ ) {
733 Variable
*variable
= [_watchVariables objectAtIndex
:i
];
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
];
747 - (void)_watchTimer
:(NSTimer
*)timer