X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fthecheat;a=blobdiff_plain;f=MyDocument.m;fp=MyDocument.m;h=a4733ccd5ffde31a00d98f39390a1393ce1038de;hp=0000000000000000000000000000000000000000;hb=42cf7bbe564d70233a0d73baee613f209eb00eb6;hpb=2d60a59a8ad195dd0af8f90c8d5b74a69ce7f4fa diff --git a/MyDocument.m b/MyDocument.m new file mode 100644 index 0000000..a4733cc --- /dev/null +++ b/MyDocument.m @@ -0,0 +1,1737 @@ + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// 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; + + [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É" duration:0]; + [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:@"Repeated 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 ( seconds == 0 ) + { + [statusText setTextColor:color]; + [statusText setStringValue:msg]; + } + else + { + if ( statusTextTimer ) + { + [statusTextTimer invalidate]; + [statusTextTimer release]; + } + else + { + [savedStatusText release]; + [savedStatusColor release]; + savedStatusText = [[statusText stringValue] retain]; + savedStatusColor = [[statusText textColor] retain]; + } + + [statusText setTextColor:color]; + [statusText setStringValue:msg]; + 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 +{ + TCPlaySound( @"Tink" ); + + [self setStatusToLast]; + [self setStatusText:@"Change Finished" duration:1.5]; +} + +- (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]; + } +}