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 SearchContext
*searchContext
= context
;
255 if (searchContext
->value
->_type
!= TCFloat
&& searchContext
->value
->_type
!= TCDouble
)
257 bigEndianValue(searchContext
->value
->_value
, searchContext
->value
);
260 _searchTask
= [[ThreadedTask alloc
] initWithFunction
:function
264 if ( ![_searchTask run
] ) {
266 [_searchTask release
];
268 [_delegate cheater
:self didFailLastRequest
:@
"Internal error."];
272 [_delegate cheater
:self didFailLastRequest
:@
"Invalid search parameters."];
279 - (void)searchLastValuesComparison
:(TCSearchOperator
)op
281 SearchContext
*context
;
285 ChazLog( @
"there is already a search task" );
289 if ( [_searchResults count
] > 0 ) {
290 context
= [[SearchContext alloc
] initWithLastContext
:[_searchResults lastObject
] searchOperator
:op
];
293 ChazLog( @
"doing a searchLastValues search without previous results..." );
294 [_delegate cheater
:self didFailLastRequest
:@
"Invalid search."];
298 function
= [context iterationFunction
];
300 _searchTask
= [[ThreadedTask alloc
] initWithFunction
:function
304 if ( ![_searchTask run
] ) {
306 [_searchTask release
];
308 [_delegate cheater
:self didFailLastRequest
:@
"Internal error."];
312 [_delegate cheater
:self didFailLastRequest
:@
"Invalid search parameters."];
320 [_searchTask cancelWithoutWaiting
];
326 [_delegate cheaterDidClearSearch
:self];
330 - (void)getMemoryDump
333 ChazLog( @
"there is already a dump task" );
337 _dumpTask
= [[ThreadedTask alloc
] initWithFunction
:_MemoryDumpTask
338 context
:[[[DumpContext alloc
] initWithPID
:[_target pid
]] autorelease
]
341 if ( ![_dumpTask run
] ) {
344 [_delegate cheater
:self didFailLastRequest
:@
"Internal error."];
348 int _MemoryDumpTask( ThreadedTask
*task
, unsigned iteration
)
350 DumpContext
*context
= [task context
];
351 VMRegion region
= VMNextRegionWithAttributes( context
->process
, context
->lastRegion
, VMREGION_READABLE
);
353 if ( VMRegionIsNotNull( region
) ) {
354 [context
->dump appendData
:VMRegionData( region
)];
356 context
->lastRegion
= region
;
362 // no more regions, exit
367 - (void)cancelMemoryDump
369 [_dumpTask cancelWithoutWaiting
];
373 - (void)makeVariableChanges
:(NSArray
*)variables repeat
:(BOOL)doRepeat interval
:(NSTimeInterval
)repeatInterval
377 [self stopChangingVariables
];
379 // repeat the changes if necessary
382 _cheatVariables
= [variables copy
];
385 _cheatVariables
= [variables retain
];
387 _cheatTimer
= [[NSTimer scheduledTimerWithTimeInterval
:repeatInterval
389 selector
:@selector(_changeTimer
:)
391 repeats
:YES
] retain
];
392 [[NSRunLoop currentRunLoop
] addTimer
:_cheatTimer forMode
:NSEventTrackingRunLoopMode
];
393 [[NSRunLoop currentRunLoop
] addTimer
:_cheatTimer forMode
:NSModalPanelRunLoopMode
];
397 changes
= [self _doChange
:variables
];
398 [_delegate cheater
:self didChangeVariables
:changes
];
401 - (void)stopChangingVariables
403 if ( _cheatVariables
) {
404 [_cheatTimer invalidate
];
405 [_cheatTimer release
];
407 [_cheatVariables release
];
408 _cheatVariables
= nil;
410 // report back to delegate
411 [_delegate cheaterDidStopChangingVariables
:self];
418 SearchContext
*searchContext
;
422 if ( searchContext
= [_searchResults lastObject
] ) {
423 [_savedResults addObject
:searchContext
];
424 [_searchResults removeLastObject
];
426 [self stopWatchingVariables
];
428 if ( searchContext
= [_searchResults lastObject
] ) {
430 variables
= TCArrayCopyElements( searchContext
->addresses
, _returnLimit
);
431 values
= TCArrayCopyElements( searchContext
->values
, _returnLimit
);
434 variables
= TCArrayCopyContainer( searchContext
->addresses
, _returnLimit
);
435 values
= TCArrayCopyContainer( searchContext
->values
, _returnLimit
);
438 [_delegate cheater
:self didRevertToVariables
:variables actualAmount
:TCArrayElementCount( searchContext
->addresses
)];
439 [_delegate cheater
:self didFindValues
:values
];
442 [_delegate cheaterDidUndo
:self];
448 SearchContext
*searchContext
;
452 [self stopWatchingVariables
];
454 if ( searchContext
= [_savedResults lastObject
] ) {
455 [_searchResults addObject
:searchContext
];
456 [_savedResults removeLastObject
];
459 variables
= TCArrayCopyElements( searchContext
->addresses
, _returnLimit
);
460 values
= TCArrayCopyElements( searchContext
->values
, _returnLimit
);
463 variables
= TCArrayCopyContainer( searchContext
->addresses
, _returnLimit
);
464 values
= TCArrayCopyContainer( searchContext
->values
, _returnLimit
);
467 [_delegate cheater
:self didRevertToVariables
:variables actualAmount
:TCArrayElementCount( searchContext
->addresses
)];
468 [_delegate cheater
:self didFindValues
:values
];
469 [_delegate cheaterDidRedo
:self];
474 - (void)watchVariablesAtIndex
:(unsigned)index count
:(unsigned)count interval
:(NSTimeInterval
)checkInterval
476 SearchContext
*context
;
479 ChazLog( @
"watchVariablesAtIndex:.." );
481 [self stopWatchingVariables
];
484 ChazLog( @
"invalid watch parameters: 0 count" );
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
];
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
];
504 _watchVariables
= [[NSArray arrayWithArray
:vars
] retain
];
507 _watchRange
= NSMakeRange( index
, count
);
508 _watchTimer
= [[NSTimer scheduledTimerWithTimeInterval
:checkInterval
510 selector
:@selector(_watchTimer
:)
512 repeats
:YES
] retain
];
513 [[NSRunLoop currentRunLoop
] addTimer
:_watchTimer forMode
:NSEventTrackingRunLoopMode
];
514 [[NSRunLoop currentRunLoop
] addTimer
:_watchTimer forMode
:NSModalPanelRunLoopMode
];
516 // do a watch check right now
520 ChazLog( @
"invalid watch parameters" );
525 - (void)stopWatchingVariables
527 if ( _watchVariables
) {
528 [_watchTimer invalidate
];
529 [_watchTimer release
];
531 [_watchVariables release
];
532 _watchVariables
= nil;
537 // #############################################################################
538 #pragma mark ThreadedTaskDelegate
539 // #############################################################################
541 - (void)threadedTaskFinished
:(ThreadedTask
*)theTask
543 id context
= [theTask context
];
545 [self stopWatchingVariables
];
547 ChazLog( @
"threaded task finished" );
549 if ( [context isKindOfClass
:[SearchContext
class]] ) {
550 SearchContext
*searchContext
= context
;
555 variables
= TCArrayCopyElements( searchContext
->addresses
, _returnLimit
);
556 values
= TCArrayCopyElements( searchContext
->values
, _returnLimit
);
559 variables
= TCArrayCopyContainer( searchContext
->addresses
, _returnLimit
);
560 values
= TCArrayCopyContainer( searchContext
->values
, _returnLimit
);
563 [_searchResults addObject
:searchContext
];
564 [_searchTask release
];
567 [_delegate cheater
:self didFindVariables
:variables actualAmount
:TCArrayElementCount( searchContext
->addresses
)];
568 [_delegate cheater
:self didFindValues
:values
];
570 else if ( [context isKindOfClass
:[DumpContext
class]] ) {
571 DumpContext
*dumpContext
= context
;
572 [_delegate cheater
:self didDumpMemory
:dumpContext
->dump
];
578 - (void)threadedTaskCancelled
:(ThreadedTask
*)theTask
580 id context
= [theTask context
];
581 ChazLog( @
"threaded task cancelled" );
583 if ( [context isKindOfClass
:[SearchContext
class]] ) {
584 [_delegate cheaterDidCancelSearch
:self];
585 [_searchTask release
];
588 else if ( [context isKindOfClass
:[DumpContext
class]] ) {
589 [_delegate cheaterDidCancelMemoryDump
:self];
595 - (void)threadedTask
:(ThreadedTask
*)theTask failedWithErrorCode
:(int)errorCode
597 id context
= [theTask context
];
598 ChazLog( @
"threaded task failed with code: %i", errorCode
);
600 if ( [context isKindOfClass
:[SearchContext
class]] ) {
601 [_delegate cheater
:self didFailLastRequest
:[NSString stringWithFormat
:@
"Search failed with error: %i", errorCode
]];
602 [_searchTask release
];
605 else if ( [context isKindOfClass
:[DumpContext
class]] ) {
606 [_delegate cheater
:self didFailLastRequest
:[NSString stringWithFormat
:@
"Dump failed with error: %i", errorCode
]];
612 - (void)threadedTask
:(ThreadedTask
*)theTask reportedProgress
:(int)theProgress
614 [_delegate cheater
:self didReportProgress
:theProgress
];
618 // #############################################################################
619 #pragma mark Private Methods
620 // #############################################################################
624 [self stopWatchingVariables
];
627 // empty the results array
628 [_searchResults removeAllObjects
];
629 [_savedResults removeAllObjects
];
635 if ( VMStopProcess( [_target pid
] ) ) {
636 _isTargetPaused
= YES
;
639 _isTargetPaused
= NO
;
643 - (BOOL)_resumeTarget
645 if ( VMContinueProcess( [_target pid
] ) ) {
646 _isTargetPaused
= NO
;
649 _isTargetPaused
= YES
;
654 - (void)_applicationLaunched
:(NSNotification
*)note
656 NSWorkspace
*workspace
= [NSWorkspace sharedWorkspace
];
657 NSDictionary
*application
= [note userInfo
];
660 // don't allow The Cheat to be cheated
661 bundleID
= [application objectForKey
:@
"NSApplicationBundleIdentifier"];
662 if ( bundleID
&& [bundleID isEqualToString
:[[NSBundle mainBundle
] bundleIdentifier
]] ) {
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
];
679 - (void)_applicationTerminated
:(NSNotification
*)note
681 NSWorkspace
*workspace
= [NSWorkspace sharedWorkspace
];
682 NSDictionary
*application
= [note userInfo
];
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
]];
689 // if this is the current process, take appropriate actions
690 if ( [_target isEqual
:process
] ) {
693 [self cancelMemoryDump
];
694 [self stopChangingVariables
];
695 [_delegate cheater
:self didRemoveProcess
:process
];
696 [_delegate cheater
:self didFailLastRequest
:@
"The target quit."];
699 // inform the delegate of the removed process
700 [_delegate cheater
:self didRemoveProcess
:process
];
702 [_processes removeObject
:process
];
709 - (unsigned)_doChange
:(NSArray
*)variables
712 unsigned successes
= 0;
714 top
= [variables count
];
715 for ( i
= 0; i
< top
; i
++ ) {
716 Variable
*variable
= [variables objectAtIndex
:i
];
718 if ([[variable process
] pid
] != [_target pid
])
720 [variable setProcess
:_target
];
723 char buffer
[variable
->_size
];
724 memcpy(buffer
, variable
->_value
, variable
->_size
);
725 bigEndianValue(buffer
, variable
);
727 if ( VMWriteBytes( [_target pid
], [variable address
], buffer
, [variable valueSize
] ) )
735 - (void)_changeTimer
:(NSTimer
*)timer
737 unsigned changes
= [self _doChange
:_cheatVariables
];
738 [_delegate cheater
:self didChangeVariables
:changes
];
745 char value
[TC_MAX_VAR_SIZE
];
748 top
= [_watchVariables count
];
749 for ( i
= 0; i
< top
; i
++ ) {
750 Variable
*variable
= [_watchVariables objectAtIndex
:i
];
752 size
= [variable valueSize
];
753 if ( VMReadBytes( [_target pid
], [variable address
], value
, &size
) ) {
754 bigEndianValue(value
, variable
);
756 // check if memory changed
757 if (memcmp(value
, variable
->_value
, size
) != 0)
759 [variable setValue
:value
];
760 // inform delegate of the change
761 [_delegate cheater
:self variableAtIndex
:_watchRange.location
+i didChangeTo
:variable
];
767 - (void)_watchTimer
:(NSTimer
*)timer