// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Project: The Cheat // // File: MyDocument.m // Created: Sun Sep 07 2003 // // Copyright: 2003 Chaz McGarvey. All rights reserved. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #import "MyDocument.h" #import "CheatClient.h" // Internal Functions void TCPlaySound( NSString *name ); @implementation MyDocument - (id)init { if ( self = [super init] ) { NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; // initialize stuff sockfd = -1; serverList = [[NSMutableArray alloc] init]; addressList = [[NSMutableArray alloc] init]; // set up the network browser browser = [[NSNetServiceBrowser alloc] init]; [browser setDelegate:self]; [browser searchForServicesOfType:@"_cheat._tcp." inDomain:@"local."]; // notifications to receive [nc addObserver:self selector:@selector(listenerStarted:) name:@"TCListenerStarted" object:nil]; [nc addObserver:self selector:@selector(listenerStopped:) name:@"TCListenerStopped" object:nil]; [nc addObserver:self selector:@selector(windowsOnTopChanged:) name:@"TCWindowsOnTopChanged" object:nil]; [self connectToLocal]; } return self; } - (NSString *)windowNibName { return @"MyDocument"; } - (NSString *)displayName { return [NSString stringWithFormat:@"The Cheat %i", TCGlobalDocumentCount++]; } - (void)windowControllerDidLoadNib:(NSWindowController *)controller { [super windowControllerDidLoadNib:controller]; [self initialInterfaceSetup]; } - (void)close { // closing the window will automatically disconnect the client from the server, // but if the application is quitting, the client may not get a chance to exit. // this _should_ be OK. [self disconnect]; // clean up status timer stuff. // we do this here because we don't want the timer to fire after the window is gone // since we need to use the window in that method. [savedStatusColor release], savedStatusColor = nil; [savedStatusText release], savedStatusText = nil; [statusTextTimer invalidate]; [statusTextTimer release], statusTextTimer = nil; [super close]; } - (void)initialInterfaceSetup { NSString *localName = @"Local"; //[NSString stringWithFormat:@"%@ (local)", TCGlobalBroadcastName]; // misc window settings [cheatWindow useOptimizedDrawing:YES]; [cheatWindow setFrameAutosaveName:@"TCCheatWindow"]; // set options if ( TCGlobalWindowsOnTop ) { [cheatWindow setLevel:NSPopUpMenuWindowLevel]; } // set up the server menu default items [serverMenu removeAllItems]; [serverMenu addItemWithTitle:@"Not Connected" action:@selector(serverMenuDisconnect:) keyEquivalent:@""]; [serverMenu addItemWithTitle:localName action:@selector(serverMenuLocal:) keyEquivalent:@""]; [processMenu removeAllItems]; // give tags to the menu items. [[typeMenu itemWithTitle:@"Integer"] setTag:TYPE_INTEGER]; [[typeMenu itemWithTitle:@"String"] setTag:TYPE_STRING]; [[typeMenu itemWithTitle:@"Decimal"] setTag:TYPE_DECIMAL]; [[typeMenu itemWithTitle:@"Unknown Value"] setTag:TYPE_UNKNOWN]; [[stringSizeMenu itemWithTitle:@"8-bit"] setTag:SIZE_8_BIT]; [[integerSizeMenu itemWithTitle:@"char"] setTag:SIZE_8_BIT]; [[integerSizeMenu itemWithTitle:@"short"] setTag:SIZE_16_BIT]; [[integerSizeMenu itemWithTitle:@"long"] setTag:SIZE_32_BIT]; [[decimalSizeMenu itemWithTitle:@"float"] setTag:SIZE_32_BIT]; [[decimalSizeMenu itemWithTitle:@"double"] setTag:SIZE_64_BIT]; // set default state [statusText setStringValue:@""]; [self setStatusDisconnected]; // change sheet initial interface. [changeSecondsCombo setEnabled:NO]; } - (void)updateSearchButton { TCtype type = [typePopup indexOfSelectedItem]; if ( type != TYPE_UNKNOWN ) { if ( [[searchTextField stringValue] isEqualToString:@""] ) { [searchButton setEnabled:NO]; } else { [searchButton setEnabled:YES]; } } else { [searchButton setEnabled:YES]; } } - (void)updatePauseButton { if ( !targetPaused ) { [pauseButton setTitle:@"Pause Target"]; } else { [pauseButton setTitle:@"Resume Target"]; } } - (void)updateSearchBoxes { TCtype type = [typePopup indexOfSelectedItem]; if ( type != TYPE_UNKNOWN ) { [searchTextField setEnabled:YES]; [searchRadioMatrix setEnabled:NO]; } else { [searchTextField setEnabled:NO]; [searchRadioMatrix setEnabled:YES]; } } - (void)updateChangeButton { if ( addressSelected ) { [changeButton setEnabled:YES]; } else { [changeButton setEnabled:NO]; } } - (void)setStatusDisconnected { lastStatus = status; status = STATUS_DISCONNECTED; [serverPopup setEnabled:YES]; [pauseButton setTitle:@"Pause Target"]; [pauseButton setEnabled:NO]; [processPopup setEnabled:NO]; [typePopup setEnabled:NO]; [sizePopup setEnabled:NO]; [searchTextField setEnabled:NO]; [searchRadioMatrix setEnabled:NO]; [searchButton setEnabled:NO]; [clearSearchButton setEnabled:NO]; [self setStatusText:@"Not Connected" duration:0]; [statusBar stopAnimation:self]; [addressTable setEnabled:NO]; [changeButton setTitle:@"ChangeÉ"]; [changeButton setEnabled:NO]; [[serverMenu itemAtIndex:0] setTitle:@"Not Connected"]; } - (void)setStatusConnected { lastStatus = status; status = STATUS_CONNECTED; [serverPopup setEnabled:YES]; [self updatePauseButton]; [pauseButton setEnabled:YES]; [processPopup setEnabled:YES]; [typePopup setEnabled:YES]; [sizePopup setEnabled:YES]; [self updateSearchBoxes]; [self updateSearchButton]; [clearSearchButton setEnabled:NO]; [self setStatusText:@"Connected" duration:0]; [statusBar stopAnimation:self]; [addressTable setEnabled:NO]; [changeButton setTitle:@"ChangeÉ"]; [changeButton setEnabled:NO]; [[serverMenu itemAtIndex:0] setTitle:@"Disconnect"]; } - (void)setStatusCheating { lastStatus = status; status = STATUS_CHEATING; [serverPopup setEnabled:YES]; [self updatePauseButton]; [pauseButton setEnabled:YES]; [processPopup setEnabled:NO]; [typePopup setEnabled:NO]; [sizePopup setEnabled:NO]; [self updateSearchBoxes]; [self updateSearchButton]; [clearSearchButton setEnabled:YES]; if ( searchResultsAmount < TCMaxSearchResults ) { if ( searchResultsAmount == 1 ) { [self setStatusText:[NSString stringWithFormat:@"Results: %i", searchResultsAmount] duration:0 color:[NSColor colorWithCalibratedRed:0.0f green:0.5f blue:0.0f alpha:1.0f]]; } else if ( searchResultsAmount == 0 ) { [self setStatusText:[NSString stringWithFormat:@"Results: %i", searchResultsAmount] duration:0 color:[NSColor colorWithCalibratedRed:0.5f green:0.0f blue:0.0f alpha:1.0f]]; } else { [self setStatusText:[NSString stringWithFormat:@"Results: %i", searchResultsAmount] duration:0]; } } else { [self setStatusText:[NSString stringWithFormat:@"Results: >%i", TCMaxSearchResults] duration:0]; } [statusBar stopAnimation:self]; [addressTable setEnabled:YES]; [changeButton setTitle:@"ChangeÉ"]; [self updateChangeButton]; [[serverMenu itemAtIndex:0] setTitle:@"Disconnect"]; } - (void)setStatusSearching { lastStatus = status; status = STATUS_SEARCHING; [serverPopup setEnabled:NO]; [self updatePauseButton]; [pauseButton setEnabled:NO]; [processPopup setEnabled:NO]; [typePopup setEnabled:NO]; [sizePopup setEnabled:NO]; [searchTextField setEnabled:NO]; [searchRadioMatrix setEnabled:NO]; [searchButton setEnabled:NO]; [clearSearchButton setEnabled:NO]; [self setStatusText:@"SearchingÉ" duration:0]; [statusBar startAnimation:self]; [addressTable setEnabled:NO]; [changeButton setTitle:@"ChangeÉ"]; [changeButton setEnabled:NO]; [[serverMenu itemAtIndex:0] setTitle:@"Disconnect"]; } - (void)setStatusChanging { lastStatus = status; status = STATUS_CHANGING; if ( lastStatus != STATUS_CHANGING_CONTINUOUSLY ) { [serverPopup setEnabled:NO]; [self updatePauseButton]; [pauseButton setEnabled:NO]; [processPopup setEnabled:NO]; [typePopup setEnabled:NO]; [sizePopup setEnabled:NO]; [searchTextField setEnabled:NO]; [searchRadioMatrix setEnabled:NO]; [searchButton setEnabled:NO]; [clearSearchButton setEnabled:NO]; [statusBar startAnimation:self]; [addressTable setEnabled:NO]; [changeButton setEnabled:NO]; [[serverMenu itemAtIndex:0] setTitle:@"Disconnect"]; } } - (void)setStatusChangingLater { lastStatus = status; status = STATUS_CHANGING_LATER; [serverPopup setEnabled:NO]; [self updatePauseButton]; [pauseButton setEnabled:NO]; [processPopup setEnabled:NO]; [typePopup setEnabled:NO]; [sizePopup setEnabled:NO]; [searchTextField setEnabled:NO]; [searchRadioMatrix setEnabled:NO]; [searchButton setEnabled:NO]; [clearSearchButton setEnabled:NO]; [self setStatusText:@"Changing LaterÉ" duration:0]; [statusBar startAnimation:self]; [addressTable setEnabled:NO]; [changeButton setTitle:@"Cancel Change"]; [changeButton setEnabled:YES]; [[serverMenu itemAtIndex:0] setTitle:@"Disconnect"]; } - (void)setStatusChangingContinuously { lastStatus = status; status = STATUS_CHANGING_CONTINUOUSLY; [serverPopup setEnabled:NO]; [self updatePauseButton]; [pauseButton setEnabled:YES]; [processPopup setEnabled:NO]; [typePopup setEnabled:NO]; [sizePopup setEnabled:NO]; [searchTextField setEnabled:NO]; [searchRadioMatrix setEnabled:NO]; [searchButton setEnabled:NO]; [clearSearchButton setEnabled:NO]; [self setStatusText:@"Repeating ChangeÉ" duration:0]; [statusBar startAnimation:self]; [addressTable setEnabled:NO]; [changeButton setTitle:@"Stop Change"]; [changeButton setEnabled:YES]; [[serverMenu itemAtIndex:0] setTitle:@"Disconnect"]; } - (void)setStatusUndoing { lastStatus = status; status = STATUS_UNDOING; [serverPopup setEnabled:NO]; [self updatePauseButton]; [pauseButton setEnabled:NO]; [processPopup setEnabled:NO]; [typePopup setEnabled:NO]; [sizePopup setEnabled:NO]; [searchTextField setEnabled:NO]; [searchRadioMatrix setEnabled:NO]; [searchButton setEnabled:NO]; [clearSearchButton setEnabled:NO]; [self setStatusText:@"UndoingÉ" duration:0]; [statusBar startAnimation:self]; [addressTable setEnabled:NO]; [changeButton setTitle:@"ChangeÉ"]; [changeButton setEnabled:NO]; [[serverMenu itemAtIndex:0] setTitle:@"Disconnect"]; } - (void)setStatusRedoing { lastStatus = status; status = STATUS_REDOING; [serverPopup setEnabled:NO]; [self updatePauseButton]; [pauseButton setEnabled:NO]; [processPopup setEnabled:NO]; [typePopup setEnabled:NO]; [sizePopup setEnabled:NO]; [searchTextField setEnabled:NO]; [searchRadioMatrix setEnabled:NO]; [searchButton setEnabled:NO]; [clearSearchButton setEnabled:NO]; [self setStatusText:@"RedoingÉ" duration:0]; [statusBar startAnimation:self]; [addressTable setEnabled:NO]; [changeButton setTitle:@"ChangeÉ"]; [changeButton setEnabled:NO]; [[serverMenu itemAtIndex:0] setTitle:@"Disconnect"]; } - (void)setStatusToLast { switch ( lastStatus ) { case STATUS_DISCONNECTED: [self setStatusDisconnected]; break; case STATUS_CONNECTED: [self setStatusConnected]; break; case STATUS_CHEATING: [self setStatusCheating]; break; case STATUS_SEARCHING: [self setStatusSearching]; break; case STATUS_CHANGING: [self setStatusChanging]; break; case STATUS_CHANGING_LATER: [self setStatusChangingLater]; break; case STATUS_CHANGING_CONTINUOUSLY: [self setStatusChangingContinuously]; break; case STATUS_UNDOING: [self setStatusUndoing]; break; case STATUS_REDOING: [self setStatusRedoing]; break; } } - (void)setStatusText:(NSString *)msg duration:(NSTimeInterval)seconds { [self setStatusText:msg duration:seconds color:[NSColor blackColor]]; } - (void)setStatusText:(NSString *)msg duration:(NSTimeInterval)seconds color:(NSColor *)color { if ( statusTextTimer ) { [statusTextTimer invalidate]; [statusTextTimer release], statusTextTimer = nil; } else { [savedStatusText release]; [savedStatusColor release]; savedStatusText = [[statusText stringValue] retain]; savedStatusColor = [[statusText textColor] retain]; } [statusText setTextColor:color]; [statusText setStringValue:msg]; if ( seconds != 0.0 ) { statusTextTimer = [[NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(statusTextTimer:) userInfo:nil repeats:NO] retain]; } } - (void)statusTextTimer:(NSTimer *)timer { [statusText setTextColor:savedStatusColor]; [statusText setStringValue:savedStatusText]; [savedStatusColor release], savedStatusColor = nil; [savedStatusText release], savedStatusText = nil; [statusTextTimer invalidate]; [statusTextTimer release], statusTextTimer = nil; } - (void)connectToLocal { NSString *localName = @"Local"; //[NSString stringWithFormat:@"%@ (local)", TCGlobalBroadcastName]; // depending on how the listener is listening, we need to use different means to connect to local if ( TCGlobalListening ) { if ( TCGlobalAllowRemote ) { struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htonl( TCGlobalListenPort ); addr.sin_addr.s_addr = INADDR_ANY; [self connectToServer:[NSData dataWithBytes:&addr length:sizeof(addr)] name:localName]; } else { struct sockaddr_un addr; addr.sun_family = AF_UNIX; strncpy( addr.sun_path, TCDefaultListenPath, 103 ); [self connectToServer:[NSData dataWithBytes:&addr length:sizeof(addr)] name:localName]; } } } - (void)connectToServer:(NSData *)addr name:(NSString *)name { everConnected = YES; if ( connection ) { [self disconnect]; waitingToConnect = YES; connectionAddress = [addr retain]; connectionName = [name retain]; } else { connection = [[CheatClient clientWithDelegate:self server:addr name:name] retain]; connectionAddress = [addr retain]; connectionName = [name retain]; } [self setStatusConnected]; } - (void)disconnect { if ( connection ) { [connection release], connection = nil; close( sockfd ); [self clearSearch]; [connectionAddress release], connectionAddress = nil; [connectionName release], connectionName = nil; [processMenu removeAllItems]; [serverPopup selectItemAtIndex:0]; [self setStatusDisconnected]; } } - (void)sendProcessListRequest { PacketHeader header; int length = sizeof(header); header.checksum = RandomChecksum(); header.function = 1; header.size = 0; if ( SendBuffer( sockfd, (char *)(&header), &length ) == -1 || length != sizeof(header) ) { NSLog( @"sendProcessListRequest failed on socket %i", sockfd ); } } - (void)sendClearSearch { PacketHeader header; int length = sizeof(header); header.checksum = RandomChecksum(); header.function = 3; header.size = 0; if ( SendBuffer( sockfd, (char *)(&header), &length ) == -1 || length != sizeof(header) ) { NSLog( @"sendClearSearch failed on socket %i", sockfd ); } } - (void)sendSearch:(char const *)data size:(int)size { PacketHeader header; int length = sizeof(header) + size; int lengthAfter = length; char *buffer, *ptr; header.checksum = RandomChecksum(); header.function = 5; header.size = size; if ( (buffer = (char *)malloc( length )) == NULL ) { NSLog( @"sendSearch:size: failed" ); } ptr = buffer; COPY_TO_BUFFER( ptr, &header, sizeof(header) ); COPY_TO_BUFFER( ptr, data, size ); if ( SendBuffer( sockfd, buffer, &lengthAfter ) == -1 || lengthAfter != length ) { NSLog( @"sendSearch:size: failed" ); } free( buffer ); } - (void)sendChange:(char const *)data size:(int)size { PacketHeader header; int length = sizeof(header) + size; int lengthAfter = length; char *buffer, *ptr; header.checksum = RandomChecksum(); header.function = 8; header.size = size; if ( (buffer = (char *)malloc( length )) == NULL ) { NSLog( @"sendChange:size: failed" ); } ptr = buffer; COPY_TO_BUFFER( ptr, &header, sizeof(header) ); COPY_TO_BUFFER( ptr, data, size ); if ( SendBuffer( sockfd, buffer, &lengthAfter ) == -1 || lengthAfter != length ) { NSLog( @"sendChange:size: failed" ); } free( buffer ); } - (void)sendPauseTarget; { PacketHeader header; int length = sizeof(header); header.checksum = RandomChecksum(); header.function = 10; header.size = 0; if ( SendBuffer( sockfd, (char *)(&header), &length ) == -1 || length != sizeof(header) ) { NSLog( @"sendPauseTarget failed" ); } } - (void)sendVariableValueRequest { } - (void)sendUndoRequest { PacketHeader header; int length = sizeof(header); header.checksum = RandomChecksum(); header.function = 14; header.size = 0; if ( SendBuffer( sockfd, (char *)(&header), &length ) == -1 || length != sizeof(header) ) { NSLog( @"sendUndoRequest failed" ); } } - (void)sendRedoRequest { PacketHeader header; int length = sizeof(header); header.checksum = RandomChecksum(); header.function = 16; header.size = 0; if ( SendBuffer( sockfd, (char *)(&header), &length ) == -1 || length != sizeof(header) ) { NSLog( @"sendRedoRequest failed" ); } } - (void)sendSetTargetPID:(int)pid { PacketHeader header; int length = sizeof(header) + sizeof(u_int32_t); int lengthAfter = length; u_int32_t tarPID = (u_int32_t)pid; char *buffer, *ptr; header.checksum = RandomChecksum(); header.function = 18; header.size = sizeof(u_int32_t); if ( (buffer = (char *)malloc( length )) == NULL ) { NSLog( @"sendSetTargetPID: failed" ); } ptr = buffer; COPY_TO_BUFFER( ptr, &header, sizeof(header) ); COPY_TO_BUFFER( ptr, &tarPID, sizeof(tarPID) ); if ( SendBuffer( sockfd, buffer, &lengthAfter ) == -1 || lengthAfter != length ) { NSLog( @"sendSetTargetPID: failed" ); } free( buffer ); } - (void)receivedProcessList:(NSData *)data { NSMenuItem *item; u_int32_t processCount = 0; char *ptr = (char *)[data bytes]; int i, max; COPY_FROM_BUFFER( &processCount, ptr, sizeof(processCount) ); max = (int)processCount; for ( i = 0; i < max; i++ ) { u_int32_t pid; NSString *name; COPY_FROM_BUFFER( &pid, ptr, sizeof(pid) ); name = [NSString stringWithCString:ptr], ptr += [name length] + 1; item = [[NSMenuItem alloc] initWithTitle:name action:@selector(processMenuItem:) keyEquivalent:@""]; [item setTag:(int)pid]; [processMenu addItem:[item autorelease]]; } } - (void)receivedSearchFinished { if ( searchResultsAmount == 1 ) { TCPlaySound( @"Submarine" ); } else if ( searchResultsAmount == 0 ) { TCPlaySound( @"Basso" ); } [self setStatusToLast]; //[self setStatusText:@"Search Finished" duration:1.5]; [cheatWindow makeFirstResponder:searchTextField]; } - (void)receivedVariableList:(NSData *)data { char *ptr = (char *)[data bytes]; [self destroyResults]; COPY_FROM_BUFFER( &searchResultsAmount, ptr, sizeof(searchResultsAmount) ); if ( searchResultsAmount > 0 ) { int memSize = TCAddressSize*searchResultsAmount; if ( (searchResults = (TCaddress *)malloc( memSize )) == NULL ) { NSLog( @"receivedVariableList failed: malloc failed" ); searchResultsAmount = 0; return; } COPY_FROM_BUFFER( searchResults, ptr, memSize ); } [addressTable reloadData]; } - (void)receivedChangeFinished { [self setStatusToLast]; if ( status == STATUS_CHANGING_CONTINUOUSLY ) { [self setStatusText:@"Change Occured" duration:1.5]; } else { TCPlaySound( @"Tink" ); } } - (void)receivedError:(NSData *)data { u_int32_t fatal; NSString *msg; char *ptr = (char *)[data bytes]; COPY_FROM_BUFFER( &fatal, ptr, sizeof(fatal) ); msg = [NSString stringWithCString:ptr]; // alert the user. [self handleErrorMessage:msg fatal:fatal]; } - (void)receivedUndoFinished { [self setStatusToLast]; } - (void)receivedRedoFinished { [self setStatusToLast]; } - (void)receivedUndoRedoStatus:(NSData *)data { char *ptr = (char *)[data bytes]; COPY_FROM_BUFFER( &undoCount, ptr, sizeof(undoCount) ); COPY_FROM_BUFFER( &redoCount, ptr, sizeof(redoCount) ); NSLog( @"UNDO: %i, REDO: %i", undoCount, redoCount ); } - (void)receivedAppLaunched:(NSData *)data { NSMenuItem *item; char *ptr = (char *)[data bytes]; u_int32_t pid; NSString *name; COPY_FROM_BUFFER( &pid, ptr, sizeof(pid) ); name = [NSString stringWithCString:ptr], ptr += [name length] + 1; item = [[NSMenuItem alloc] initWithTitle:name action:@selector(processMenuItem:) keyEquivalent:@""]; [item setTag:(int)pid]; [processMenu addItem:[item autorelease]]; } - (void)receivedAppQuit:(NSData *)data { u_int32_t pid; char *ptr = (char *)[data bytes]; COPY_FROM_BUFFER( &pid, ptr, sizeof(pid) ); [processMenu removeItemWithTag:pid]; } - (void)receivedTargetQuit { [self clearSearch]; [self sendClearSearch]; // tell the server that the first app is now the target. targetPID = [[processMenu itemAtIndex:0] tag]; [self sendSetTargetPID:targetPID]; // alert the user. [self handleErrorMessage:@"The application that was being cheated has quit." fatal:NO]; [self setStatusConnected]; } - (void)receivedPauseFinished:(NSData *)data { char *ptr = (char *)[data bytes]; COPY_FROM_BUFFER( &targetPaused, ptr, sizeof(targetPaused) ); if ( targetPaused ) { [self setStatusText:@"Target Paused" duration:1.5]; } else { [self setStatusText:@"Target Resumed" duration:1.5]; } [self updatePauseButton]; } /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% Searching & Changing %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ - (void)search { TCtype type = [[typePopup selectedItem] tag]; TCsize size = [[sizePopup selectedItem] tag]; char *data, *ptr; int dataSize = sizeof(type) + sizeof(size); data = (char *)malloc( dataSize ); ptr = data; // copy the size and type of the variable. COPY_TO_BUFFER( ptr, &type, sizeof(type) ); COPY_TO_BUFFER( ptr, &size, sizeof(size) ); // switch to cheating mode if this is the first search. if ( status == STATUS_CONNECTED ) { [self setStatusCheating]; } // copy the value to search for. switch ( type ) { case TYPE_STRING: { switch ( size ) { case SIZE_8_BIT: { NSString *string = [searchTextField stringValue]; int stringLength = [string length] + 1; data = (char *)realloc( data, dataSize + stringLength ); ptr = data + dataSize; dataSize += stringLength; COPY_TO_BUFFER( ptr, [string cString], stringLength ); } break; } } break; case TYPE_INTEGER: { switch ( size ) { case SIZE_8_BIT: { int8_t value = [searchTextField intValue]; data = (char *)realloc( data, dataSize + sizeof(value) ); ptr = data + dataSize; dataSize += sizeof(value); COPY_TO_BUFFER( ptr, &value, sizeof(value) ); } break; case SIZE_16_BIT: { int16_t value = [searchTextField intValue]; data = (char *)realloc( data, dataSize + sizeof(value) ); ptr = data + dataSize; dataSize += sizeof(value); COPY_TO_BUFFER( ptr, &value, sizeof(value) ); } break; case SIZE_32_BIT: { int32_t value = [searchTextField intValue]; data = (char *)realloc( data, dataSize + sizeof(value) ); ptr = data + dataSize; dataSize += sizeof(value); COPY_TO_BUFFER( ptr, &value, sizeof(value) ); } break; } } break; case TYPE_DECIMAL: { switch ( size ) { case SIZE_32_BIT: { float value = [searchTextField floatValue]; data = (char *)realloc( data, dataSize + sizeof(value) ); ptr = data + dataSize; dataSize += sizeof(value); COPY_TO_BUFFER( ptr, &value, sizeof(value) ); } break; case SIZE_64_BIT: { double value = [searchTextField doubleValue]; data = (char *)realloc( data, dataSize + sizeof(value) ); ptr = data + dataSize; dataSize += sizeof(value); COPY_TO_BUFFER( ptr, &value, sizeof(value) ); } break; } } break; case TYPE_UNKNOWN: { u_int32_t value = 0;//[searchTextField intValue]; data = (char *)realloc( data, dataSize + sizeof(value) ); ptr = data + dataSize; dataSize += sizeof(value); COPY_TO_BUFFER( ptr, &value, sizeof(value) ); } break; } [self sendSearch:data size:dataSize]; free( data ); [self setStatusSearching]; } - (void)change { TCtype type = [[typePopup selectedItem] tag]; TCsize size = [[sizePopup selectedItem] tag]; NSArray *selectedAddresses = [[addressTable selectedRowEnumerator] allObjects]; int i, addressCount = [selectedAddresses count]; char *data, *ptr; int dataSize = sizeof(type) + sizeof(size) + sizeof(addressCount) + TCAddressSize*addressCount; data = (char *)malloc( dataSize ); ptr = data; // copy the size and type of the variable. COPY_TO_BUFFER( ptr, &type, sizeof(type) ); COPY_TO_BUFFER( ptr, &size, sizeof(size) ); // copy the amount and the list of addresses to change. COPY_TO_BUFFER( ptr, &addressCount, sizeof(addressCount) ); for ( i = 0; i < addressCount; i++ ) { COPY_TO_BUFFER( ptr, &((TCaddress *)searchResults)[ [[selectedAddresses objectAtIndex:i] intValue] ], sizeof(TCaddress) ); } // copy the new value. switch ( type ) { case TYPE_STRING: { switch ( size ) { case SIZE_8_BIT: { NSString *string = [changeTextField stringValue]; int stringLength = [string length] + 1; data = (char *)realloc( data, dataSize + stringLength ); ptr = data + dataSize; dataSize += stringLength; COPY_TO_BUFFER( ptr, [string cString], stringLength ); } break; } } break; case TYPE_INTEGER: { switch ( size ) { case SIZE_8_BIT: { int8_t value = [changeTextField intValue]; data = (char *)realloc( data, dataSize + sizeof(value) ); ptr = data + dataSize; dataSize += sizeof(value); COPY_TO_BUFFER( ptr, &value, sizeof(value) ); } break; case SIZE_16_BIT: { int16_t value = [changeTextField intValue]; data = (char *)realloc( data, dataSize + sizeof(value) ); ptr = data + dataSize; dataSize += sizeof(value); COPY_TO_BUFFER( ptr, &value, sizeof(value) ); } break; case SIZE_32_BIT: { int32_t value = [changeTextField intValue]; data = (char *)realloc( data, dataSize + sizeof(value) ); ptr = data + dataSize; dataSize += sizeof(value); COPY_TO_BUFFER( ptr, &value, sizeof(value) ); } break; } } break; case TYPE_DECIMAL: { switch ( size ) { case SIZE_32_BIT: { float value = [changeTextField floatValue]; data = (char *)realloc( data, dataSize + sizeof(value) ); ptr = data + dataSize; dataSize += sizeof(value); COPY_TO_BUFFER( ptr, &value, sizeof(value) ); } break; case SIZE_64_BIT: { double value = [changeTextField doubleValue]; data = (char *)realloc( data, dataSize + sizeof(value) ); ptr = data + dataSize; dataSize += sizeof(value); COPY_TO_BUFFER( ptr, &value, sizeof(value) ); } break; } } break; case TYPE_UNKNOWN: { u_int32_t value = 0;//[searchTextField intValue]; data = (char *)realloc( data, dataSize + sizeof(value) ); ptr = data + dataSize; dataSize += sizeof(value); COPY_TO_BUFFER( ptr, &value, sizeof(value) ); } break; } [self sendChange:data size:dataSize]; free( data ); [self setStatusChanging]; } - (void)changeSheet:(NSWindow *)sheet returned:(int)returned context:(void *)context { if ( returned == 1 ) { if ( [recurringChangeButton state] == NSOnState ) { float seconds = [changeSecondsCombo floatValue]; [self setStatusChangingContinuously]; [self change]; changeTimer = [[NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(changeTimer:) userInfo:nil repeats:YES] retain]; } else { [self change]; } } } - (void)changeTimer:(NSTimer *)timer { [self change]; } /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% Cheat Window Interface %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ - (IBAction)typePopup:(id)sender { switch ( [typePopup indexOfSelectedItem] ) { case TYPE_STRING: [sizePopup setMenu:stringSizeMenu]; break; case TYPE_INTEGER: case TYPE_UNKNOWN: [sizePopup setMenu:integerSizeMenu]; break; case TYPE_DECIMAL: [sizePopup setMenu:decimalSizeMenu]; break; } [self updateSearchBoxes]; [self updateSearchButton]; } - (IBAction)searchButton:(id)sender { [self search]; } - (IBAction)clearSearchButton:(id)sender { [self clearSearch]; [self setStatusConnected]; [self setStatusText:@"Search Cleared" duration:1.5]; [self sendClearSearch]; } - (IBAction)changeButton:(id)sender { [changeTimer invalidate]; [changeTimer release], changeTimer = nil; if ( status == STATUS_CHANGING_CONTINUOUSLY ) { [self setStatusCheating]; } else if ( status == STATUS_CHEATING ) { [NSApp beginSheet:changeSheet modalForWindow:cheatWindow modalDelegate:self didEndSelector:@selector(changeSheet:returned:context:) contextInfo:NULL]; //[NSApp runModalForWindow:changeSheet]; //[NSApp endSheet:changeSheet]; //[changeSheet orderOut:self]; } } - (IBAction)serverMenuItem:(id)sender { NSData *data = [[[serverList objectAtIndex:[sender tag]] addresses] objectAtIndex:0]; /* struct sockaddr_in addr; [data getBytes:&addr];*/ [self connectToServer:data name:[serverPopup titleOfSelectedItem]]; } - (IBAction)serverMenuDisconnect:(id)sender { [self disconnect]; } - (IBAction)serverMenuLocal:(id)sender { [self connectToLocal]; } - (IBAction)processMenuItem:(id)sender { targetPID = [sender tag]; [self sendSetTargetPID:targetPID]; [self setStatusText:[NSString stringWithFormat:@"PID: %i", targetPID] duration:0]; } - (IBAction)pauseButton:(id)sender { [self sendPauseTarget]; } - (void)undoMenu:(id)sender { if ( undoCount == 1 ) { [self clearSearchButton:self]; } else { [self sendUndoRequest]; [self setStatusUndoing]; } } - (void)redoMenu:(id)sender { [self sendRedoRequest]; [self setStatusRedoing]; } - (BOOL)respondsToSelector:(SEL)aSelector { if ( aSelector == @selector(undoMenu:) ) { if ( status == STATUS_CHEATING && undoCount > 0 ) { return YES; } else { return NO; } } if ( aSelector == @selector(redoMenu:) ) { if ( status == STATUS_CHEATING && redoCount > 0 ) { return YES; } else { return NO; } } return [super respondsToSelector:aSelector]; } /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% Change Sheet Interface %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ - (IBAction)cancelButton:(id)sender { [changeSheet orderOut:sender]; [NSApp endSheet:changeSheet returnCode:0]; //[NSApp stopModal]; } - (IBAction)okButton:(id)sender { [changeSheet orderOut:sender]; [NSApp endSheet:changeSheet returnCode:1]; //[NSApp stopModal]; } - (IBAction)recurringChangeButton:(id)sender { if ( [recurringChangeButton state] == NSOnState ) { [changeSecondsCombo setEnabled:YES]; } else { [changeSecondsCombo setEnabled:NO]; } } /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% Cleaning Up %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ - (void)clearSearch { undoCount = 0; redoCount = 0; targetPaused = NO; [changeTimer invalidate]; [changeTimer release], changeTimer = nil; [self destroyResults]; [addressTable reloadData]; } - (void)destroyResults { if ( searchResultsAmount > 0 ) { free( searchResults ); searchResultsAmount = 0; } } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [self disconnect]; [browser release]; [serverList release]; [addressList release]; // clean up status timer stuff [savedStatusColor release]; [savedStatusText release]; [statusTextTimer invalidate]; [statusTextTimer release]; [changeTimer invalidate]; [changeTimer release]; [self destroyResults]; [super dealloc]; } /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% TCListener Notifications %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ - (void)listenerStarted:(NSNotification *)note { if ( !everConnected ) { [self connectToLocal]; } } - (void)listenerStopped:(NSNotification *)note { } /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% TCWindowsOnTopChanged Notification %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ - (void)windowsOnTopChanged:(NSNotification *)note { if ( TCGlobalWindowsOnTop ) { [cheatWindow setLevel:NSPopUpMenuWindowLevel]; } else { [cheatWindow setLevel:NSNormalWindowLevel]; } } /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% TCWindowsOnTopChanged Notification %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ - (void)handleErrorMessage:(NSString *)msg fatal:(BOOL)fatal { // close the change sheet if it's open. if ( [cheatWindow attachedSheet] ) { [changeSheet orderOut:self]; [NSApp endSheet:changeSheet returnCode:0]; } // show message. NSBeginAlertSheet( fatal? @"Fatal Error":@"Error", @"OK", nil, nil, cheatWindow, nil, nil, nil, 0, msg ); } /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% ClientDelegate %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ - (void)clientConnectedWithSocket:(int)sock name:(NSString *)name { // the client is reporting that a connection has been made. sockfd = sock; [self sendProcessListRequest]; [serverPopup selectItemWithTitle:name]; [self setStatusConnected]; } - (void)clientDisconnected { // if there is a pending connection, connect now. if ( waitingToConnect ) { waitingToConnect = NO; connection = [[CheatClient clientWithDelegate:self server:connectionAddress name:connectionName] retain]; } // if our connection variable is still valid, we were disconnected unexpectedly. else if ( connection ) { [self disconnect]; NSBeginAlertSheet( @"Network Failure", @"OK", nil, nil, cheatWindow, nil, nil, nil, 0, @"The server has disconnected you." ); } } - (void)clientError:(NSString *)error message:(NSString *)message { NSBeginAlertSheet( error, @"OK", nil, nil, cheatWindow, nil, nil, nil, 0, message ); } /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% NSToolbar Delegate %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ /* *** A toolbar is no longer used, but the code still remains for possible future use. *** - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag { NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier]; if ( [itemIdentifier isEqualToString:@"Disconnect"] ) { disconnectButton = item; [item setLabel:@"Disconnect"]; [item setPaletteLabel:[item label]]; [item setImage:[NSImage imageNamed:@"disconnect"]]; [item setTarget:self]; [item setToolTip:@"Click here to pause or unpause the program being cheated."]; } else if ( [itemIdentifier isEqualToString:@"ServerPopup"] ) { NSRect fRect = [typePopup frame]; NSSize fSize = NSMakeSize( FLT_MAX, fRect.size.height ); NSMenuItem *menu = [[NSMenuItem alloc] initWithTitle:@"Server" action:@selector(serverPopup:) keyEquivalent:@""]; [menu setSubmenu:[serverPopup menu]]; [item setLabel:@"Server"]; [item setPaletteLabel:[item label]]; [item setView:serverPopup]; [item setMinSize:fRect.size]; [item setMaxSize:fSize]; [item setMenuFormRepresentation:[menu autorelease]]; [item autorelease]; } return item; } - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar { return [NSArray arrayWithObjects:NSToolbarSeparatorItemIdentifier, NSToolbarSpaceItemIdentifier, NSToolbarFlexibleSpaceItemIdentifier, NSToolbarCustomizeToolbarItemIdentifier, @"Disconnect", @"ServerPopup", nil]; } - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar { return [NSArray arrayWithObjects:@"Disconnect", @"ServerPopup", nil]; }*/ /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% NSTableView Data Source/Delegate %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ - (void)controlTextDidChange:(NSNotification *)aNotification { [self updateSearchButton]; } /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% NSTableView Data Source/Delegate %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ - (int)numberOfRowsInTableView:(NSTableView *)table { return (searchResultsAmount <= TCMaxSearchResults) ? searchResultsAmount : TCMaxSearchResults; } - (id)tableView:(NSTableView *)table objectValueForTableColumn:(NSTableColumn *)column row:(int)row { return [NSString stringWithFormat:@"%0.8X", ((TCaddress *)searchResults)[row]]; } - (void)tableView:(NSTableView *) setObjectValue:(id)object forTableColumn:(NSTableColumn *)column row:(int)row { return; } - (void)tableViewSelectionDidChange:(NSNotification *)note { if ( [addressTable selectedRow] != -1 ) { addressSelected = YES; } else { addressSelected = NO; } [self updateChangeButton]; } /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%% NSNetServiceBrowser Delegate %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ - (void)netServiceBrowser:(NSNetServiceBrowser *)browser didFindService:(NSNetService *)service moreComing:(BOOL)more { // a server has broadcast; not much use until it's resolved. [service setDelegate:self]; [service resolve]; } - (void)netServiceBrowser:(NSNetServiceBrowser *)browser didRemoveService:(NSNetService *)service moreComing:(BOOL)more { [serverMenu removeAllItemsWithTitle:[service name]]; // if this is the last broadcast server, take away the divider. if ( [serverMenu numberOfItems] == 3 ) { [serverMenu removeItemAtIndex:2]; } } - (void)netServiceDidResolveAddress:(NSNetService *)service { NSString *name = [service name]; int tag = [serverList count]; NSMenuItem *item; if ( [serverMenu itemWithTitle:name] == nil ) { item = [[NSMenuItem alloc] initWithTitle:[service name] action:@selector(serverMenuItem:) keyEquivalent:@""]; [item setTag:tag]; // if this is the first server, add a divider. if ( [serverMenu numberOfItems] <= 2 ) { [serverMenu addItem:[NSMenuItem separatorItem]]; } [serverList addObject:service]; [serverMenu addItem:[item autorelease]]; // select the item if we are already connected to the server. // this could happen if the server rebroadcast as a different name. if ( connection && [[[service addresses] objectAtIndex:0] isEqualToData:connectionAddress] ) { [serverPopup selectItemWithTitle:[service name]]; } } } @end // Internal Functions void TCPlaySound( NSString *name ) { if ( TCGlobalPlaySounds ) { [[NSSound soundNamed:name] play]; } }