Find file
Fetching contributors…
Cannot retrieve contributors at this time
775 lines (631 sloc) 20.5 KB
/*
* The Cheat - The legendary universal game trainer for Mac OS X.
* http://www.brokenzipper.com/trac/wiki/TheCheat
*
* Copyright (c) 2003-2011, Charles McGarvey et al.
*
* Distributable under the terms and conditions of the 2-clause BSD
* license; see the file COPYING for the legal text of the license.
*/
#import "LocalCheater.h"
#include "cheat_global.h"
// memory dump function
int _MemoryDumpTask( ThreadedTask *task, unsigned iteration );
@interface LocalCheater ( Private )
// internal methods so they can be used throughout the class
- (void)_clearSearch;
- (BOOL)_pauseTarget; // returns TRUE for success
- (BOOL)_resumeTarget; // returns TRUE for success
// handling app launching/quitting
- (void)_applicationLaunched:(NSNotification *)note;
- (void)_applicationTerminated:(NSNotification *)note;
// cheating
- (unsigned)_doChange:(NSArray *)variables;
- (void)_changeTimer:(NSTimer *)timer;
// watch variables
- (void)_doWatch;
- (void)_watchTimer:(NSTimer *)timer;
// tasks
- (int)_memoryDumpTask:(ThreadedTask *)task iteration:(unsigned)iteration;
@end
@implementation LocalCheater
- (id)init
{
if ( self = [super init] ) {
NSNotificationCenter *nc= [[NSWorkspace sharedWorkspace] notificationCenter];
// register for app launch/quit notifications
[nc addObserver:self selector:@selector(_applicationLaunched:) name:NSWorkspaceDidLaunchApplicationNotification object:nil];
[nc addObserver:self selector:@selector(_applicationTerminated:) name:NSWorkspaceDidTerminateApplicationNotification object:nil];
_searchResults = [[NSMutableArray alloc] init];
_savedResults = [[NSMutableArray alloc] init];
_returnLimit = -1;
}
return self;
}
- (void)dealloc
{
[(NSNotificationCenter *)[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
// make sure the process isn't left paused
[self _resumeTarget];
// release local objects
[_target release];
[self _clearSearch];
// make sure everything is cancelled.
[_searchTask cancelAndRemoveDelegate];
[_dumpTask cancelAndRemoveDelegate];
[self stopChangingVariables];
[self stopWatchingVariables];
[_searchTask release];
[_dumpTask release];
[_processes release];
[super dealloc];
}
- (BOOL)shouldCopy
{
return _shouldCopy;
}
- (void)setShouldCopy:(BOOL)flag
{
_shouldCopy = flag;
}
- (NSString *)hostAddress
{
//return @"127.0.0.1";
return @"this computer";
}
// #############################################################################
#pragma mark Cheater Override
// #############################################################################
- (void)connect
{
if ( !_isConnected ) {
_isConnected = YES;
// return as connected
[_delegate cheaterDidConnect:self];
}
}
- (void)disconnect
{
if ( _isConnected ) {
[_searchTask cancelAndRemoveDelegate];
[_dumpTask cancelAndRemoveDelegate];
[self stopChangingVariables];
[self stopWatchingVariables];
_isConnected = NO;
// return as disconnected
//[_delegate cheaterDidDisconnect:self];
}
}
- (void)authenticateWithPassword:(NSString *)password
{
// being a local cheater, authentication really isn't required.
// return succes every time.
_isAuthenticated = YES;
[_delegate cheaterAcceptedPassword:self];
}
- (void)getProcessList
{
NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
// NSArray *launchedApps = [workspace launchedApplications];
// unsigned i, len = [launchedApps count];
ProcessSerialNumber psn = {0, kNoProcess};
if ( !_processes ) {
//_processes = [[NSMutableArray alloc] initWithCapacity:len];
_processes = [[NSMutableArray alloc] initWithCapacity:1];
}
// compile process array
// for ( i = 0; i < len; i++ ) {
while(/*procNotFound != */!GetNextProcess(&psn)) {
// NSDictionary *application = [launchedApps objectAtIndex:i];
NSDictionary *application = (NSDictionary *)ProcessInformationCopyDictionary(&psn, kProcessDictionaryIncludeAllInformationMask);
void *bundlePath = [application objectForKey:@"BundlePath"];
// don't allow The Cheat to be cheated
// if ( [[application objectForKey:@"NSApplicationBundleIdentifier"] isEqualToString:[[NSBundle mainBundle] bundleIdentifier]] ) {
if ( [[application objectForKey:(NSString *)kCFBundleIdentifierKey] isEqualToString:[[NSBundle mainBundle] bundleIdentifier]] ) {
continue;
}
/*Process *process = [[Process alloc] initWithName:[application objectForKey:@"NSApplicationName"]]
version:ApplicationVersion( [application objectForKey:@"NSApplicationPath"] )
icon:[workspace iconForFile:[application objectForKey:@"NSApplicationPath"]]
pid:[[application objectForKey:@"NSApplicationProcessIdentifier"] intValue]];*/
Process *process = [[Process alloc] initWithName:[application objectForKey:(NSString *)kCFBundleNameKey]
version:ApplicationVersion( bundlePath ? bundlePath: [application objectForKey:(NSString *)kCFBundleExecutableKey] )
icon:[workspace iconForFile:bundlePath ? bundlePath: [application objectForKey:(NSString *)kCFBundleExecutableKey]]
pid:[[application objectForKey:@"pid"] intValue]];
[_processes addObject:process];
[process release];
}
// return process list
[_delegate cheater:self didFindProcesses:[NSArray arrayWithArray:_processes]];
}
- (void)setTarget:(Process *)target
{
// unpause the current target if needed
[self resumeTarget];
// clear the search
[self clearSearch];
// set the new target
[_target release];
if ( _shouldCopy ) {
_target = [target copy];
[_delegate cheater:self didSetTarget:[_target copy]];
}
else {
_target = [target retain];
[_delegate cheater:self didSetTarget:_target];
}
}
- (void)pauseTarget
{
// attempt to pause the current target
if ( !_isTargetPaused && [self _pauseTarget] ) {
[_delegate cheaterPausedTarget:self];
}
else {
[_delegate cheater:self echo:@"This process cannot be paused."];
}
}
- (void)resumeTarget
{
// attempt to resume the current target
if ( _isTargetPaused && [self _resumeTarget] ) {
[_delegate cheaterResumedTarget:self];
}
else {
[_delegate cheater:self echo:@"This process cannot be resumed."];
}
}
- (void)limitReturnedResults:(unsigned)limit
{
_returnLimit = limit;
}
- (void)searchForVariable:(Variable *)var comparison:(TCSearchOperator)op
{
SearchContext *context;
void *function;
if ( _searchTask ) {
ChazLog( @"there is already a search task" );
return;
}
if ( [_searchResults count] > 0 ) {
SearchContext *lastContext = [_searchResults lastObject];
if ( ([var type] == TCString) && ([lastContext->value valueSize] < [var valueSize]) ) {
// string size not good
[_delegate cheater:self didFailLastRequest:@"String is too long."];
return;
}
context = [[SearchContext alloc] initWithLastContext:[_searchResults lastObject] searchOperator:op value:var];
}
else {
context = [[SearchContext alloc] initWithPID:[_target pid] searchOperator:op value:var];
}
function = [context iterationFunction];
if ( function ) {
SearchContext *searchContext = context;
if (searchContext->value->_type != TCFloat && searchContext->value->_type != TCDouble)
{
bigEndianValue(searchContext->value->_value, searchContext->value);
}
_searchTask = [[ThreadedTask alloc] initWithFunction:function
context:context
delegate:self];
if ( ![_searchTask run] ) {
// task didn't run
[_searchTask release];
_searchTask = nil;
[_delegate cheater:self didFailLastRequest:@"Internal error."];
}
}
else {
[_delegate cheater:self didFailLastRequest:@"Invalid search parameters."];
}
[context release];
}
- (void)searchLastValuesComparison:(TCSearchOperator)op
{
SearchContext *context;
void *function;
if ( _searchTask ) {
ChazLog( @"there is already a search task" );
return;
}
if ( [_searchResults count] > 0 ) {
context = [[SearchContext alloc] initWithLastContext:[_searchResults lastObject] searchOperator:op];
}
else {
ChazLog( @"doing a searchLastValues search without previous results..." );
[_delegate cheater:self didFailLastRequest:@"Invalid search."];
return;
}
function = [context iterationFunction];
if ( function ) {
_searchTask = [[ThreadedTask alloc] initWithFunction:function
context:context
delegate:self];
if ( ![_searchTask run] ) {
// task didn't run
[_searchTask release];
_searchTask = nil;
[_delegate cheater:self didFailLastRequest:@"Internal error."];
}
}
else {
[_delegate cheater:self didFailLastRequest:@"Invalid search parameters."];
}
[context release];
}
- (void)cancelSearch
{
[_searchTask cancelWithoutWaiting];
}
- (void)clearSearch
{
[self _clearSearch];
[_delegate cheaterDidClearSearch:self];
}
- (void)getMemoryDump
{
if ( _dumpTask ) {
ChazLog( @"there is already a dump task" );
return;
}
_dumpTask = [[ThreadedTask alloc] initWithFunction:_MemoryDumpTask
context:[[[DumpContext alloc] initWithPID:[_target pid]] autorelease]
delegate:self];
if ( ![_dumpTask run] ) {
[_dumpTask release];
_dumpTask = nil;
[_delegate cheater:self didFailLastRequest:@"Internal error."];
}
}
int _MemoryDumpTask( ThreadedTask *task, unsigned iteration )
{
DumpContext *context = [task context];
VMRegion region = VMNextRegionWithAttributes( context->process, context->lastRegion, VMREGION_READABLE );
if ( VMRegionIsNotNull( region ) ) {
[context->dump appendData:VMRegionData( region )];
context->lastRegion = region;
// continue looping
return 1;
}
else {
// no more regions, exit
return 0;
}
}
- (void)cancelMemoryDump
{
[_dumpTask cancelWithoutWaiting];
}
- (void)makeVariableChanges:(NSArray *)variables repeat:(BOOL)doRepeat interval:(NSTimeInterval)repeatInterval
{
unsigned changes;
[self stopChangingVariables];
// repeat the changes if necessary
if ( doRepeat ) {
if ( _shouldCopy ) {
_cheatVariables = [variables copy];
}
else {
_cheatVariables = [variables retain];
}
_cheatTimer = [[NSTimer scheduledTimerWithTimeInterval:repeatInterval
target:self
selector:@selector(_changeTimer:)
userInfo:nil
repeats:YES] retain];
[[NSRunLoop currentRunLoop] addTimer:_cheatTimer forMode:NSEventTrackingRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:_cheatTimer forMode:NSModalPanelRunLoopMode];
}
// change variables
changes = [self _doChange:variables];
[_delegate cheater:self didChangeVariables:changes];
}
- (void)stopChangingVariables
{
if ( _cheatVariables ) {
[_cheatTimer invalidate];
[_cheatTimer release];
_cheatTimer = nil;
[_cheatVariables release];
_cheatVariables = nil;
// report back to delegate
[_delegate cheaterDidStopChangingVariables:self];
}
}
- (void)undo
{
SearchContext *searchContext;
TCArray variables;
TCArray values;
if ( searchContext = [_searchResults lastObject] ) {
[_savedResults addObject:searchContext];
[_searchResults removeLastObject];
[self stopWatchingVariables];
if ( searchContext = [_searchResults lastObject] ) {
if ( _shouldCopy ) {
variables = TCArrayCopyElements( searchContext->addresses, _returnLimit );
values = TCArrayCopyElements( searchContext->values, _returnLimit );
}
else {
variables = TCArrayCopyContainer( searchContext->addresses, _returnLimit );
values = TCArrayCopyContainer( searchContext->values, _returnLimit );
}
[_delegate cheater:self didRevertToVariables:variables actualAmount:TCArrayElementCount( searchContext->addresses )];
[_delegate cheater:self didFindValues:values];
}
[_delegate cheaterDidUndo:self];
}
}
- (void)redo
{
SearchContext *searchContext;
TCArray variables;
TCArray values;
[self stopWatchingVariables];
if ( searchContext = [_savedResults lastObject] ) {
[_searchResults addObject:searchContext];
[_savedResults removeLastObject];
if ( _shouldCopy ) {
variables = TCArrayCopyElements( searchContext->addresses, _returnLimit );
values = TCArrayCopyElements( searchContext->values, _returnLimit );
}
else {
variables = TCArrayCopyContainer( searchContext->addresses, _returnLimit );
values = TCArrayCopyContainer( searchContext->values, _returnLimit );
}
[_delegate cheater:self didRevertToVariables:variables actualAmount:TCArrayElementCount( searchContext->addresses )];
[_delegate cheater:self didFindValues:values];
[_delegate cheaterDidRedo:self];
}
}
- (void)watchVariablesAtIndex:(unsigned)index count:(unsigned)count interval:(NSTimeInterval)checkInterval
{
SearchContext *context;
unsigned i, top;
ChazLog( @"watchVariablesAtIndex:.." );
[self stopWatchingVariables];
if ( count == 0 ) {
ChazLog( @"invalid watch parameters: 0 count" );
return;
}
if ( context = [_searchResults lastObject] ) {
TCArray addresses = context->addresses;
TCArray values = context->values;
// check the index & count
if ( index + count <= TCArrayElementCount( addresses ) ) {
// save current values
NSMutableArray *vars = [[NSMutableArray alloc] initWithCapacity:count];
top = index + count;
for ( i = index; i < top; i++ ) {
Variable *var = [[Variable alloc] initWithType:[context variableType] integerSign:[context integerSign]];
[var setProcess:_target];
[var setAddress:*(TCAddress *)TCArrayElementAtIndex( addresses, i )];
[var setValue:TCArrayElementAtIndex(values, i) size:TCArrayElementSize(values)];
[vars addObject:var];
[var release];
}
_watchVariables = [[NSArray arrayWithArray:vars] retain];
[vars release];
_watchRange = NSMakeRange( index, count );
_watchTimer = [[NSTimer scheduledTimerWithTimeInterval:checkInterval
target:self
selector:@selector(_watchTimer:)
userInfo:nil
repeats:YES] retain];
[[NSRunLoop currentRunLoop] addTimer:_watchTimer forMode:NSEventTrackingRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:_watchTimer forMode:NSModalPanelRunLoopMode];
// do a watch check right now
[self _doWatch];
}
else {
ChazLog( @"invalid watch parameters" );
}
}
}
- (void)stopWatchingVariables
{
if ( _watchVariables ) {
[_watchTimer invalidate];
[_watchTimer release];
_watchTimer = nil;
[_watchVariables release];
_watchVariables = nil;
}
}
// #############################################################################
#pragma mark ThreadedTaskDelegate
// #############################################################################
- (void)threadedTaskFinished:(ThreadedTask *)theTask
{
id context = [theTask context];
[self stopWatchingVariables];
ChazLog( @"threaded task finished" );
if ( [context isKindOfClass:[SearchContext class]] ) {
SearchContext *searchContext = context;
TCArray variables;
TCArray values;
if ( _shouldCopy ) {
variables = TCArrayCopyElements( searchContext->addresses, _returnLimit );
values = TCArrayCopyElements( searchContext->values, _returnLimit );
}
else {
variables = TCArrayCopyContainer( searchContext->addresses, _returnLimit );
values = TCArrayCopyContainer( searchContext->values, _returnLimit );
}
[_searchResults addObject:searchContext];
[_searchTask release];
_searchTask = nil;
[_delegate cheater:self didFindVariables:variables actualAmount:TCArrayElementCount( searchContext->addresses )];
[_delegate cheater:self didFindValues:values];
}
else if ( [context isKindOfClass:[DumpContext class]] ) {
DumpContext *dumpContext = context;
[_delegate cheater:self didDumpMemory:dumpContext->dump];
[_dumpTask release];
_dumpTask = nil;
}
}
- (void)threadedTaskCancelled:(ThreadedTask *)theTask
{
id context = [theTask context];
ChazLog( @"threaded task cancelled" );
if ( [context isKindOfClass:[SearchContext class]] ) {
[_delegate cheaterDidCancelSearch:self];
[_searchTask release];
_searchTask = nil;
}
else if ( [context isKindOfClass:[DumpContext class]] ) {
[_delegate cheaterDidCancelMemoryDump:self];
[_dumpTask release];
_dumpTask = nil;
}
}
- (void)threadedTask:(ThreadedTask *)theTask failedWithErrorCode:(int)errorCode
{
id context = [theTask context];
ChazLog( @"threaded task failed with code: %i", errorCode );
if ( [context isKindOfClass:[SearchContext class]] ) {
[_delegate cheater:self didFailLastRequest:[NSString stringWithFormat:@"Search failed with error: %i", errorCode]];
[_searchTask release];
_searchTask = nil;
}
else if ( [context isKindOfClass:[DumpContext class]] ) {
[_delegate cheater:self didFailLastRequest:[NSString stringWithFormat:@"Dump failed with error: %i", errorCode]];
[_dumpTask release];
_dumpTask = nil;
}
}
- (void)threadedTask:(ThreadedTask *)theTask reportedProgress:(int)theProgress
{
[_delegate cheater:self didReportProgress:theProgress];
}
// #############################################################################
#pragma mark Private Methods
// #############################################################################
- (void)_clearSearch
{
[self stopWatchingVariables];
[self cancelSearch];
// empty the results array
[_searchResults removeAllObjects];
[_savedResults removeAllObjects];
}
- (BOOL)_pauseTarget
{
// attempt to pause
if ( VMStopProcess( [_target pid] ) ) {
_isTargetPaused = YES;
return YES;
}
_isTargetPaused = NO;
return NO;
}
- (BOOL)_resumeTarget
{
if ( VMContinueProcess( [_target pid] ) ) {
_isTargetPaused = NO;
return YES;
}
_isTargetPaused = YES;
return NO;
}
- (void)_applicationLaunched:(NSNotification *)note
{
NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
NSDictionary *application = [note userInfo];
NSString *bundleID;
// don't allow The Cheat to be cheated
bundleID = [application objectForKey:@"NSApplicationBundleIdentifier"];
if ( bundleID && [bundleID isEqualToString:[[NSBundle mainBundle] bundleIdentifier]] ) {
return;
}
Process *process = [[Process alloc] initWithName:[application objectForKey:@"NSApplicationName"]
version:ApplicationVersion( [application objectForKey:@"NSApplicationPath"] )
icon:[workspace iconForFile:[application objectForKey:@"NSApplicationPath"]]
pid:[[application objectForKey:@"NSApplicationProcessIdentifier"] intValue]];
if ( ![_processes containsObject:process] ) {
[_processes addObject:process];
// inform the delegate of the new process
[_delegate cheater:self didAddProcess:process];
}
// cleanup
[process release];
}
- (void)_applicationTerminated:(NSNotification *)note
{
NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
NSDictionary *application = [note userInfo];
Process *process = [[Process alloc] initWithName:[application objectForKey:@"NSApplicationName"]
version:ApplicationVersion( [application objectForKey:@"NSApplicationPath"] )
icon:[workspace iconForFile:[application objectForKey:@"NSApplicationPath"]]
pid:[[application objectForKey:@"NSApplicationProcessIdentifier"] intValue]];
// if this is the current process, take appropriate actions
if ( [_target isEqual:process] ) {
[self cancelSearch];
[self clearSearch];
[self cancelMemoryDump];
[self stopChangingVariables];
[_delegate cheater:self didRemoveProcess:process];
[_delegate cheater:self didFailLastRequest:@"The target quit."];
}
else {
// inform the delegate of the removed process
[_delegate cheater:self didRemoveProcess:process];
}
[_processes removeObject:process];
// cleanup
[process release];
}
- (unsigned)_doChange:(NSArray *)variables
{
unsigned i, top;
unsigned successes = 0;
top = [variables count];
for ( i = 0; i < top; i++ ) {
Variable *variable = [variables objectAtIndex:i];
if ([[variable process] pid] != [_target pid])
{
[variable setProcess:_target];
}
char buffer[variable->_size];
memcpy(buffer, variable->_value, variable->_size);
bigEndianValue(buffer, variable);
if ( VMWriteBytes( [_target pid], [variable address], buffer, [variable valueSize] ) )
{
successes++;
}
}
return successes;
}
- (void)_changeTimer:(NSTimer *)timer
{
unsigned changes = [self _doChange:_cheatVariables];
[_delegate cheater:self didChangeVariables:changes];
}
- (void)_doWatch
{
unsigned i, top;
char value[TC_MAX_VAR_SIZE];
mach_vm_size_t size;
top = [_watchVariables count];
for ( i = 0; i < top; i++ ) {
Variable *variable = [_watchVariables objectAtIndex:i];
size = [variable valueSize];
if ( VMReadBytes( [_target pid], [variable address], value, &size ) ) {
bigEndianValue(value, variable);
// check if memory changed
if (memcmp(value, variable->_value, size) != 0)
{
[variable setValue:value];
// inform delegate of the change
[_delegate cheater:self variableAtIndex:_watchRange.location+i didChangeTo:variable];
}
}
}
}
- (void)_watchTimer:(NSTimer *)timer
{
[self _doWatch];
}
@end