3 * The Cheat - The legendary universal game trainer for Mac OS X.
4 * http://www.brokenzipper.com/trac/wiki/TheCheat
6 * Copyright (c) 2003-2011, Charles McGarvey et al.
8 * Distributable under the terms and conditions of the 2-clause BSD
9 * license; see the file COPYING for the legal text of the license.
12 #import "LocalCheater.h"
14 #include "cheat_global.h"
17 // memory dump function
18 int _MemoryDumpTask( ThreadedTask
*task
, unsigned iteration
);
21 @interface LocalCheater ( Private
)
23 // internal methods so they can be used throughout the class
25 - (BOOL)_pauseTarget
; // returns TRUE for success
26 - (BOOL)_resumeTarget
; // returns TRUE for success
27 // handling app launching/quitting
28 - (void)_applicationLaunched
:(NSNotification
*)note
;
29 - (void)_applicationTerminated
:(NSNotification
*)note
;
31 - (unsigned)_doChange
:(NSArray
*)variables
;
32 - (void)_changeTimer
:(NSTimer
*)timer
;
35 - (void)_watchTimer
:(NSTimer
*)timer
;
37 - (int)_memoryDumpTask
:(ThreadedTask
*)task iteration
:(unsigned)iteration
;
42 @implementation LocalCheater
47 if ( self = [super init
] ) {
48 NSNotificationCenter
*nc
= [[NSWorkspace sharedWorkspace
] notificationCenter
];
50 // register for app launch/quit notifications
51 [nc addObserver
:self selector
:@selector(_applicationLaunched
:) name
:NSWorkspaceDidLaunchApplicationNotification object
:nil];
52 [nc addObserver
:self selector
:@selector(_applicationTerminated
:) name
:NSWorkspaceDidTerminateApplicationNotification object
:nil];
54 _searchResults
= [[NSMutableArray alloc
] init
];
55 _savedResults
= [[NSMutableArray alloc
] init
];
63 [(NSNotificationCenter
*)[[NSWorkspace sharedWorkspace
] notificationCenter
] removeObserver
:self];
65 // make sure the process isn't left paused
68 // release local objects
72 // make sure everything is cancelled.
73 [_searchTask cancelAndRemoveDelegate
];
74 [_dumpTask cancelAndRemoveDelegate
];
75 [self stopChangingVariables
];
76 [self stopWatchingVariables
];
78 [_searchTask release
];
92 - (void)setShouldCopy
:(BOOL)flag
98 - (NSString
*)hostAddress
100 //return @"127.0.0.1";
101 return @
"this computer";
105 // #############################################################################
106 #pragma mark Cheater Override
107 // #############################################################################
111 if ( !_isConnected
) {
113 // return as connected
114 [_delegate cheaterDidConnect
:self];
120 if ( _isConnected
) {
121 [_searchTask cancelAndRemoveDelegate
];
122 [_dumpTask cancelAndRemoveDelegate
];
123 [self stopChangingVariables
];
124 [self stopWatchingVariables
];
127 // return as disconnected
128 //[_delegate cheaterDidDisconnect:self];
132 - (void)authenticateWithPassword
:(NSString
*)password
134 // being a local cheater, authentication really isn't required.
135 // return succes every time.
136 _isAuthenticated
= YES
;
137 [_delegate cheaterAcceptedPassword
:self];
141 - (void)getProcessList
143 NSWorkspace
*workspace
= [NSWorkspace sharedWorkspace
];
144 // NSArray *launchedApps = [workspace launchedApplications];
145 // unsigned i, len = [launchedApps count];
146 ProcessSerialNumber psn
= {0, kNoProcess
};
149 //_processes = [[NSMutableArray alloc] initWithCapacity:len];
150 _processes
= [[NSMutableArray alloc
] initWithCapacity
:1];
153 // compile process array
154 // for ( i = 0; i < len; i++ ) {
155 while(/*procNotFound != */!GetNextProcess(&psn
)) {
156 // NSDictionary *application = [launchedApps objectAtIndex:i];
157 NSDictionary
*application
= (NSDictionary
*)ProcessInformationCopyDictionary(&psn
, kProcessDictionaryIncludeAllInformationMask
);
158 void *bundlePath
= [application objectForKey
:@
"BundlePath"];
160 // don't allow The Cheat to be cheated
161 // if ( [[application objectForKey:@"NSApplicationBundleIdentifier"] isEqualToString:[[NSBundle mainBundle] bundleIdentifier]] ) {
162 if ( [[application objectForKey
:(NSString
*)kCFBundleIdentifierKey
] isEqualToString
:[[NSBundle mainBundle
] bundleIdentifier
]] ) {
166 /*Process *process = [[Process alloc] initWithName:[application objectForKey:@"NSApplicationName"]]
167 version:ApplicationVersion( [application objectForKey:@"NSApplicationPath"] )
168 icon:[workspace iconForFile:[application objectForKey:@"NSApplicationPath"]]
169 pid:[[application objectForKey:@"NSApplicationProcessIdentifier"] intValue]];*/
170 Process
*process
= [[Process alloc
] initWithName
:[application objectForKey
:(NSString
*)kCFBundleNameKey
]
171 version
:ApplicationVersion( bundlePath ? bundlePath
: [application objectForKey
:(NSString
*)kCFBundleExecutableKey
] )
172 icon
:[workspace iconForFile
:bundlePath ? bundlePath
: [application objectForKey
:(NSString
*)kCFBundleExecutableKey
]]
173 pid
:[[application objectForKey
:@
"pid"] intValue
]];
174 [_processes addObject
:process
];
178 // return process list
179 [_delegate cheater
:self didFindProcesses
:[NSArray arrayWithArray
:_processes
]];
183 - (void)setTarget
:(Process
*)target
185 // unpause the current target if needed
189 // set the new target
193 _target
= [target copy
];
194 [_delegate cheater
:self didSetTarget
:[_target copy
]];
197 _target
= [target retain
];
198 [_delegate cheater
:self didSetTarget
:_target
];
204 // attempt to pause the current target
205 if ( !_isTargetPaused
&& [self _pauseTarget
] ) {
206 [_delegate cheaterPausedTarget
:self];
209 [_delegate cheater
:self echo
:@
"This process cannot be paused."];
215 // attempt to resume the current target
216 if ( _isTargetPaused
&& [self _resumeTarget
] ) {
217 [_delegate cheaterResumedTarget
:self];
220 [_delegate cheater
:self echo
:@
"This process cannot be resumed."];
225 - (void)limitReturnedResults
:(unsigned)limit
227 _returnLimit
= limit
;
230 - (void)searchForVariable
:(Variable
*)var comparison
:(TCSearchOperator
)op
232 SearchContext
*context
;
236 ChazLog( @
"there is already a search task" );
240 if ( [_searchResults count
] > 0 ) {
241 SearchContext
*lastContext
= [_searchResults lastObject
];
242 if ( ([var type
] == TCString
) && ([lastContext
->value valueSize
] < [var valueSize
]) ) {
243 // string size not good
244 [_delegate cheater
:self didFailLastRequest
:@
"String is too long."];
247 context
= [[SearchContext alloc
] initWithLastContext
:[_searchResults lastObject
] searchOperator
:op value
:var
];
250 context
= [[SearchContext alloc
] initWithPID
:[_target pid
] searchOperator
:op value
:var
];
253 function
= [context iterationFunction
];
255 SearchContext
*searchContext
= context
;
256 if (searchContext
->value
->_type
!= TCFloat
&& searchContext
->value
->_type
!= TCDouble
)
258 bigEndianValue(searchContext
->value
->_value
, searchContext
->value
);
261 _searchTask
= [[ThreadedTask alloc
] initWithFunction
:function
265 if ( ![_searchTask run
] ) {
267 [_searchTask release
];
269 [_delegate cheater
:self didFailLastRequest
:@
"Internal error."];
273 [_delegate cheater
:self didFailLastRequest
:@
"Invalid search parameters."];
280 - (void)searchLastValuesComparison
:(TCSearchOperator
)op
282 SearchContext
*context
;
286 ChazLog( @
"there is already a search task" );
290 if ( [_searchResults count
] > 0 ) {
291 context
= [[SearchContext alloc
] initWithLastContext
:[_searchResults lastObject
] searchOperator
:op
];
294 ChazLog( @
"doing a searchLastValues search without previous results..." );
295 [_delegate cheater
:self didFailLastRequest
:@
"Invalid search."];
299 function
= [context iterationFunction
];
301 _searchTask
= [[ThreadedTask alloc
] initWithFunction
:function
305 if ( ![_searchTask run
] ) {
307 [_searchTask release
];
309 [_delegate cheater
:self didFailLastRequest
:@
"Internal error."];
313 [_delegate cheater
:self didFailLastRequest
:@
"Invalid search parameters."];
321 [_searchTask cancelWithoutWaiting
];
327 [_delegate cheaterDidClearSearch
:self];
331 - (void)getMemoryDump
334 ChazLog( @
"there is already a dump task" );
338 _dumpTask
= [[ThreadedTask alloc
] initWithFunction
:_MemoryDumpTask
339 context
:[[[DumpContext alloc
] initWithPID
:[_target pid
]] autorelease
]
342 if ( ![_dumpTask run
] ) {
345 [_delegate cheater
:self didFailLastRequest
:@
"Internal error."];
349 int _MemoryDumpTask( ThreadedTask
*task
, unsigned iteration
)
351 DumpContext
*context
= [task context
];
352 VMRegion region
= VMNextRegionWithAttributes( context
->process
, context
->lastRegion
, VMREGION_READABLE
);
354 if ( VMRegionIsNotNull( region
) ) {
355 [context
->dump appendData
:VMRegionData( region
)];
357 context
->lastRegion
= region
;
363 // no more regions, exit
368 - (void)cancelMemoryDump
370 [_dumpTask cancelWithoutWaiting
];
374 - (void)makeVariableChanges
:(NSArray
*)variables repeat
:(BOOL)doRepeat interval
:(NSTimeInterval
)repeatInterval
378 [self stopChangingVariables
];
380 // repeat the changes if necessary
383 _cheatVariables
= [variables copy
];
386 _cheatVariables
= [variables retain
];
388 _cheatTimer
= [[NSTimer scheduledTimerWithTimeInterval
:repeatInterval
390 selector
:@selector(_changeTimer
:)
392 repeats
:YES
] retain
];
393 [[NSRunLoop currentRunLoop
] addTimer
:_cheatTimer forMode
:NSEventTrackingRunLoopMode
];
394 [[NSRunLoop currentRunLoop
] addTimer
:_cheatTimer forMode
:NSModalPanelRunLoopMode
];
398 changes
= [self _doChange
:variables
];
399 [_delegate cheater
:self didChangeVariables
:changes
];
402 - (void)stopChangingVariables
404 if ( _cheatVariables
) {
405 [_cheatTimer invalidate
];
406 [_cheatTimer release
];
408 [_cheatVariables release
];
409 _cheatVariables
= nil;
411 // report back to delegate
412 [_delegate cheaterDidStopChangingVariables
:self];
419 SearchContext
*searchContext
;
423 if ( (searchContext
= [_searchResults lastObject
]) ) {
424 [_savedResults addObject
:searchContext
];
425 [_searchResults removeLastObject
];
427 [self stopWatchingVariables
];
429 if ( (searchContext
= [_searchResults lastObject
]) ) {
431 variables
= TCArrayCopyElements( searchContext
->addresses
, _returnLimit
);
432 values
= TCArrayCopyElements( searchContext
->values
, _returnLimit
);
435 variables
= TCArrayCopyContainer( searchContext
->addresses
, _returnLimit
);
436 values
= TCArrayCopyContainer( searchContext
->values
, _returnLimit
);
439 [_delegate cheater
:self didRevertToVariables
:variables actualAmount
:TCArrayElementCount( searchContext
->addresses
)];
440 [_delegate cheater
:self didFindValues
:values
];
443 [_delegate cheaterDidUndo
:self];
449 SearchContext
*searchContext
;
453 [self stopWatchingVariables
];
455 if ( (searchContext
= [_savedResults lastObject
]) ) {
456 [_searchResults addObject
:searchContext
];
457 [_savedResults removeLastObject
];
460 variables
= TCArrayCopyElements( searchContext
->addresses
, _returnLimit
);
461 values
= TCArrayCopyElements( searchContext
->values
, _returnLimit
);
464 variables
= TCArrayCopyContainer( searchContext
->addresses
, _returnLimit
);
465 values
= TCArrayCopyContainer( searchContext
->values
, _returnLimit
);
468 [_delegate cheater
:self didRevertToVariables
:variables actualAmount
:TCArrayElementCount( searchContext
->addresses
)];
469 [_delegate cheater
:self didFindValues
:values
];
470 [_delegate cheaterDidRedo
:self];
475 - (void)watchVariablesAtIndex
:(unsigned)index count
:(unsigned)count interval
:(NSTimeInterval
)checkInterval
477 SearchContext
*context
;
480 ChazLog( @
"watchVariablesAtIndex:.." );
482 [self stopWatchingVariables
];
485 ChazLog( @
"invalid watch parameters: 0 count" );
489 if ( (context
= [_searchResults lastObject
]) ) {
490 TCArray addresses
= context
->addresses
;
491 TCArray values
= context
->values
;
492 // check the index & count
493 if ( index
+ count
<= TCArrayElementCount( addresses
) ) {
494 // save current values
495 NSMutableArray
*vars
= [[NSMutableArray alloc
] initWithCapacity
:count
];
497 for ( i
= index
; i
< top
; i
++ ) {
498 Variable
*var
= [[Variable alloc
] initWithType
:[context variableType
] integerSign
:[context integerSign
]];
499 [var setProcess
:_target
];
500 [var setAddress
:*(TCAddress
*)TCArrayElementAtIndex( addresses
, i
)];
501 [var setValue
:TCArrayElementAtIndex(values
, i
) size
:TCArrayElementSize(values
)];
502 [vars addObject
:var
];
505 _watchVariables
= [[NSArray arrayWithArray
:vars
] retain
];
508 _watchRange
= NSMakeRange( index
, count
);
509 _watchTimer
= [[NSTimer scheduledTimerWithTimeInterval
:checkInterval
511 selector
:@selector(_watchTimer
:)
513 repeats
:YES
] retain
];
514 [[NSRunLoop currentRunLoop
] addTimer
:_watchTimer forMode
:NSEventTrackingRunLoopMode
];
515 [[NSRunLoop currentRunLoop
] addTimer
:_watchTimer forMode
:NSModalPanelRunLoopMode
];
517 // do a watch check right now
521 ChazLog( @
"invalid watch parameters" );
526 - (void)stopWatchingVariables
528 if ( _watchVariables
) {
529 [_watchTimer invalidate
];
530 [_watchTimer release
];
532 [_watchVariables release
];
533 _watchVariables
= nil;
538 // #############################################################################
539 #pragma mark ThreadedTaskDelegate
540 // #############################################################################
542 - (void)threadedTaskFinished
:(ThreadedTask
*)theTask
544 id context
= [theTask context
];
546 [self stopWatchingVariables
];
548 ChazLog( @
"threaded task finished" );
550 if ( [context isKindOfClass
:[SearchContext
class]] ) {
551 SearchContext
*searchContext
= context
;
556 variables
= TCArrayCopyElements( searchContext
->addresses
, _returnLimit
);
557 values
= TCArrayCopyElements( searchContext
->values
, _returnLimit
);
560 variables
= TCArrayCopyContainer( searchContext
->addresses
, _returnLimit
);
561 values
= TCArrayCopyContainer( searchContext
->values
, _returnLimit
);
564 [_searchResults addObject
:searchContext
];
565 [_searchTask release
];
568 [_delegate cheater
:self didFindVariables
:variables actualAmount
:TCArrayElementCount( searchContext
->addresses
)];
569 [_delegate cheater
:self didFindValues
:values
];
571 else if ( [context isKindOfClass
:[DumpContext
class]] ) {
572 DumpContext
*dumpContext
= context
;
573 [_delegate cheater
:self didDumpMemory
:dumpContext
->dump
];
579 - (void)threadedTaskCancelled
:(ThreadedTask
*)theTask
581 id context
= [theTask context
];
582 ChazLog( @
"threaded task cancelled" );
584 if ( [context isKindOfClass
:[SearchContext
class]] ) {
585 [_delegate cheaterDidCancelSearch
:self];
586 [_searchTask release
];
589 else if ( [context isKindOfClass
:[DumpContext
class]] ) {
590 [_delegate cheaterDidCancelMemoryDump
:self];
596 - (void)threadedTask
:(ThreadedTask
*)theTask failedWithErrorCode
:(int)errorCode
598 id context
= [theTask context
];
599 ChazLog( @
"threaded task failed with code: %i", errorCode
);
601 if ( [context isKindOfClass
:[SearchContext
class]] ) {
602 [_delegate cheater
:self didFailLastRequest
:[NSString stringWithFormat
:@
"Search failed with error: %i", errorCode
]];
603 [_searchTask release
];
606 else if ( [context isKindOfClass
:[DumpContext
class]] ) {
607 [_delegate cheater
:self didFailLastRequest
:[NSString stringWithFormat
:@
"Dump failed with error: %i", errorCode
]];
613 - (void)threadedTask
:(ThreadedTask
*)theTask reportedProgress
:(int)theProgress
615 [_delegate cheater
:self didReportProgress
:theProgress
];
619 // #############################################################################
620 #pragma mark Private Methods
621 // #############################################################################
625 [self stopWatchingVariables
];
628 // empty the results array
629 [_searchResults removeAllObjects
];
630 [_savedResults removeAllObjects
];
636 if ( VMStopProcess( [_target pid
] ) ) {
637 _isTargetPaused
= YES
;
640 _isTargetPaused
= NO
;
644 - (BOOL)_resumeTarget
646 if ( VMContinueProcess( [_target pid
] ) ) {
647 _isTargetPaused
= NO
;
650 _isTargetPaused
= YES
;
655 - (void)_applicationLaunched
:(NSNotification
*)note
657 NSWorkspace
*workspace
= [NSWorkspace sharedWorkspace
];
658 NSDictionary
*application
= [note userInfo
];
661 // don't allow The Cheat to be cheated
662 bundleID
= [application objectForKey
:@
"NSApplicationBundleIdentifier"];
663 if ( bundleID
&& [bundleID isEqualToString
:[[NSBundle mainBundle
] bundleIdentifier
]] ) {
667 Process
*process
= [[Process alloc
] initWithName
:[application objectForKey
:@
"NSApplicationName"]
668 version
:ApplicationVersion( [application objectForKey
:@
"NSApplicationPath"] )
669 icon
:[workspace iconForFile
:[application objectForKey
:@
"NSApplicationPath"]]
670 pid
:[[application objectForKey
:@
"NSApplicationProcessIdentifier"] intValue
]];
671 if ( ![_processes containsObject
:process
] ) {
672 [_processes addObject
:process
];
673 // inform the delegate of the new process
674 [_delegate cheater
:self didAddProcess
:process
];
680 - (void)_applicationTerminated
:(NSNotification
*)note
682 NSWorkspace
*workspace
= [NSWorkspace sharedWorkspace
];
683 NSDictionary
*application
= [note userInfo
];
685 Process
*process
= [[Process alloc
] initWithName
:[application objectForKey
:@
"NSApplicationName"]
686 version
:ApplicationVersion( [application objectForKey
:@
"NSApplicationPath"] )
687 icon
:[workspace iconForFile
:[application objectForKey
:@
"NSApplicationPath"]]
688 pid
:[[application objectForKey
:@
"NSApplicationProcessIdentifier"] intValue
]];
690 // if this is the current process, take appropriate actions
691 if ( [_target isEqual
:process
] ) {
694 [self cancelMemoryDump
];
695 [self stopChangingVariables
];
696 [_delegate cheater
:self didRemoveProcess
:process
];
697 [_delegate cheater
:self didFailLastRequest
:@
"The target quit."];
700 // inform the delegate of the removed process
701 [_delegate cheater
:self didRemoveProcess
:process
];
703 [_processes removeObject
:process
];
710 - (unsigned)_doChange
:(NSArray
*)variables
713 unsigned successes
= 0;
715 top
= [variables count
];
716 for ( i
= 0; i
< top
; i
++ ) {
717 Variable
*variable
= [variables objectAtIndex
:i
];
719 if ([[variable process
] pid
] != [_target pid
])
721 [variable setProcess
:_target
];
724 char buffer
[variable
->_size
];
725 memcpy(buffer
, variable
->_value
, variable
->_size
);
726 bigEndianValue(buffer
, variable
);
728 if ( VMWriteBytes( [_target pid
], [variable address
], buffer
, [variable valueSize
] ) )
736 - (void)_changeTimer
:(NSTimer
*)timer
738 unsigned changes
= [self _doChange
:_cheatVariables
];
739 [_delegate cheater
:self didChangeVariables
:changes
];
746 char value
[TC_MAX_VAR_SIZE
];
749 top
= [_watchVariables count
];
750 for ( i
= 0; i
< top
; i
++ ) {
751 Variable
*variable
= [_watchVariables objectAtIndex
:i
];
753 size
= [variable valueSize
];
754 if ( VMReadBytes( [_target pid
], [variable address
], value
, &size
) ) {
755 bigEndianValue(value
, variable
);
757 // check if memory changed
758 if (memcmp(value
, variable
->_value
, size
) != 0)
760 [variable setValue
:value
];
761 // inform delegate of the change
762 [_delegate cheater
:self variableAtIndex
:_watchRange.location
+i didChangeTo
:variable
];
768 - (void)_watchTimer
:(NSTimer
*)timer