]> Dogcows Code - chaz/thecheat/blobdiff - LocalCheater.m
The Cheat 1.2
[chaz/thecheat] / LocalCheater.m
diff --git a/LocalCheater.m b/LocalCheater.m
new file mode 100644 (file)
index 0000000..9403806
--- /dev/null
@@ -0,0 +1,753 @@
+
+// **********************************************************************
+// The Cheat - A universal game cheater for Mac OS X
+// (C) 2003-2005 Chaz McGarvey (BrokenZipper)
+// 
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 1, or (at your option)
+// any later version.
+// 
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+// 
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+// 
+
+#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];
+       
+       if ( !_processes ) {
+               _processes = [[NSMutableArray alloc] initWithCapacity:len];
+       }
+       
+       // compile process array
+       for ( i = 0; i < len; i++ ) {
+               NSDictionary *application = [launchedApps objectAtIndex:i];
+               
+               // don't allow The Cheat to be cheated
+               if ( [[application objectForKey:@"NSApplicationBundleIdentifier"] 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]];
+               [_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 ) {
+               _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 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 ( VMWriteBytes( [_target pid], [variable address], [variable value], [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];
+       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 ) ) {
+                       // 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
This page took 0.034943 seconds and 4 git commands to generate.