#include #include #include #include "error.h" #include "tracker.h" #include "gamemem.h" #define assert(condition) ((condition) ? ((void) 0) : PrintConsoleString("%s@%s:%s",#condition,__FILE__, __LINE__)) #define kMaxLocalGames 32 int gNumLocalGames=0; int gLocalGameListUpdate=0; tGameListEntry gLocalGames[kMaxLocalGames]; static void MyResolveCallback(CFNetServiceRef service, CFStreamError* error, void* info); #define kServiceType CFSTR("_redline._udp.") #define kMyDefaultDomain CFSTR("local.") CFMutableArrayRef gServiceArrayRef; CFMutableDictionaryRef gServiceDictionary; CFNetServiceBrowserRef gServiceBrowserRef = NULL; CFNetServiceRef gRegisteredService = NULL; CFNetServiceRef gServiceBeingResolved = NULL; UInt32 gNextNameIndexToResolve = 0; CFStringRef gTextRecord; Boolean gDone; Boolean gContinue; Boolean gResolve; Boolean gBrowserTaskActive = FALSE; Boolean gResolveProcessActive = FALSE; Boolean gTaskRegistered = FALSE; void InitLocalTracker() { OSStatus err = noErr; gServiceDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, NULL); if (gServiceDictionary == NULL) err = coreFoundationUnknownErr; if (err == noErr) { gServiceArrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); if (gServiceDictionary == NULL) { err = coreFoundationUnknownErr; } } //if (err == noErr) // err = LoadNetServicesForCFM(); //return err; } static void MyCancelRegistration() { if(gRegisteredService != NULL) { CFNetServiceUnscheduleFromRunLoop(gRegisteredService, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); CFNetServiceSetClient(gRegisteredService, NULL, NULL); CFNetServiceCancel(gRegisteredService); CFRelease(gRegisteredService); gRegisteredService = NULL; } } static void MyRegisterCallback(CFNetServiceRef theService, CFStreamError* error, void* info) { if (error->domain == kCFStreamErrorDomainNetServices) { switch(error->error) { case kCFNetServicesErrorCollision: /* Somebody else on the network has registered a service with the same name and type as the service we tried to register */ PrintConsoleString("A kCFNetServicesErrorCollision occured - will quit now\n"); break; default: /* some other error occurred */ PrintConsoleString("Some other kCFNetServicesError occurred %d will quit now\n", error->error); break; } // as an error occurred, clean up the CFNetServiceRef object MyCancelRegistration(); // you don't really need to quit, but the following boolean will cause the main to quit. gDone = true; } return; } static void MyCancelResolve() { assert(gServiceBeingResolved != NULL); CFNetServiceUnscheduleFromRunLoop(gServiceBeingResolved, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); CFNetServiceSetClient(gServiceBeingResolved, NULL, NULL); CFNetServiceCancel(gServiceBeingResolved); CFRelease(gServiceBeingResolved); gServiceBeingResolved = NULL; return; } static void MyResolveService(CFStringRef name, CFStringRef type, CFStringRef domain) { CFNetServiceClientContext clientContext = { 0, NULL, NULL, NULL, NULL }; CFStreamError error; assert(name != NULL); assert(type != NULL); assert(domain != NULL); if (gServiceBeingResolved) { /* This app only allows one resolve at a time, but CFNetServices places no restrictions on the number of simultaneous resolves. Because most resolves will happen instantaneously after calling CFNetServiceResolve, if we end up canceling a previous resolve, it's probably because the previous service became unreachable. */ PrintConsoleString("Resolve canceled\n"); MyCancelResolve(); } gServiceBeingResolved = CFNetServiceCreate(kCFAllocatorDefault, domain, type, name, 0); assert(gServiceBeingResolved != NULL); CFNetServiceSetClient(gServiceBeingResolved, MyResolveCallback, &clientContext); CFNetServiceScheduleWithRunLoop(gServiceBeingResolved, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); if (CFNetServiceResolve(gServiceBeingResolved, &error) == false) { // Something went wrong so lets clean up. CFNetServiceUnscheduleFromRunLoop(gServiceBeingResolved, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); CFNetServiceSetClient(gServiceBeingResolved, NULL, NULL); CFRelease(gServiceBeingResolved); gServiceBeingResolved = NULL; PrintConsoleString("CFNetServiceResolve returned (domain = %d, error = %ld)\n", error.domain, error.error); } return; } static void ContinueResolveProcess(void) { CFStringRef serviceNameRef; if (gDone == true) { gResolveProcessActive = FALSE; } else { if (CFArrayGetCount(gServiceArrayRef) > gNextNameIndexToResolve) { serviceNameRef = (CFStringRef)CFArrayGetValueAtIndex(gServiceArrayRef, gNextNameIndexToResolve++); MyResolveService(serviceNameRef, kServiceType, kMyDefaultDomain); } else gResolveProcessActive = FALSE; } } #define U32Swap(value) ((value)=EndianU32_BtoN(value)) #define U16Swap(value) ((value)=EndianU16_BtoN(value)) static void MyResolveCallback(CFNetServiceRef service, CFStreamError* error, void* info) { CFArrayRef addresses; CFStringRef addressString; CFStringRef serviceNameRef; sockaddr *socketAddress; sockaddr_in *socketAddress_in; UInt32 inaddr; UInt16 port; char servicename[64]; CFIndex namelen = sizeof(servicename); UInt32 index; /* In situations where the service you're resolving is advertising on multiple interfaces, like Ethernet and AirPort, your Resolve callback may be called twice, once for each IP address. Chances are that both of these IP addresses represent the same service running on the same machine, so we cancel the Resolve after getting the first callback because you only need one address to connect to the service. However, it would also be possible that two different machines are advertising the same service name, with one being on Ethernet and one on AirPort. In that situation, one of the machines will be unreachable, or more likly, everytime we call Resolve, we may connect to a different machine. The odds of this happening are extremely small. */ assert(service != NULL); addresses = CFNetServiceGetAddressing(service); assert(addresses != NULL); serviceNameRef = CFNetServiceGetName(service); assert(serviceNameRef != NULL); if (CFStringGetCString(serviceNameRef, servicename, namelen, kCFStringEncodingMacRoman)) { servicename[namelen]='\0'; for(int i=0;isa_family == AF_INET) break; } if (socketAddress) { switch(socketAddress->sa_family) { case AF_INET: CFStringGetCString(serviceNameRef, servicename, namelen, kCFStringEncodingMacRoman); socketAddress_in = (struct sockaddr_in *)socketAddress; inaddr = *((UInt32*)(&socketAddress_in->sin_addr)); port = *((UInt16*)(&socketAddress_in->sin_port)); U32Swap(inaddr); U16Swap(port); sprintf(gLocalGames[i].host, "%d.%d.%d.%d:%d",inaddr>>24,(inaddr>>16)&0xFF,(inaddr>>8)&0xFF,inaddr&0xFF,port); gLocalGames[i].loaded=true; //PrintConsoleString("Got Host Info: %s",gLocalGames[i].host); // Since we got an IPv4 address, this would be a good place to cancel the resolve. gLocalGameListUpdate++; MyCancelResolve(); return; case AF_INET6: PrintConsoleString("Resolver called with IPV6 address\n"); /* If we got here, it probably means that the "addresses" array only had one sockaddr in it and it was IPv6. We don't cancel the resolve just yet since this sample expects to print out the IPV4 address. Just continue and the resolver will call back with the IPV4 address instance. */ break; } } } } // see if there are more entities to resolve ContinueResolveProcess(); return; } static Boolean MyRegisterService(CFStringRef name, CFStringRef type, CFStringRef domain, UInt32 port, CFStringRef txtRecord) { CFNetServiceClientContext context = {0, NULL, NULL, NULL, NULL }; CFStreamError error; assert(name != NULL); assert(type != NULL); assert(domain != NULL); /* As an alternative to specifying a "name" to register, you could use an empty string like CFSTR(""), and that would cause the system to automatically substitute the "Computer Name" from the Sharing preference panel. Another benefit of using an empty string is that the system will automatically handle name collisions for us by appending a digit to the end of the name, thus our Callback would never be called in the event of a name collision. Beginning with Mac OS X 10.2.4, using an empty string will even handle situations where the user changes the "Computer Name" in the preference panel. */ gRegisteredService = CFNetServiceCreate(kCFAllocatorDefault, domain, type, name, port); assert(gRegisteredService != NULL); // register the service asynchronously. CFNetServiceSetClient(gRegisteredService, MyRegisterCallback, &context); CFNetServiceScheduleWithRunLoop(gRegisteredService, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); gTaskRegistered = CFNetServiceRegister(gRegisteredService, &error); if (gTaskRegistered == FALSE) { // a problem happened with calling CFNetServiceRegister so unschedule the service from // the runloop CFNetServiceUnscheduleFromRunLoop(gRegisteredService, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); CFNetServiceSetClient(gRegisteredService, NULL, NULL); CFRelease(gRegisteredService); gRegisteredService = NULL; } return gTaskRegistered; } static void StartResolveProcess(void) { CFStringRef serviceNameRef; // Make sure that this process can't be entered multiple times if (gResolveProcessActive == FALSE) { gResolveProcessActive = TRUE; gNextNameIndexToResolve = 0; if (CFArrayGetCount(gServiceArrayRef) > gNextNameIndexToResolve) { serviceNameRef = (CFStringRef)CFArrayGetValueAtIndex(gServiceArrayRef, gNextNameIndexToResolve++); MyResolveService(serviceNameRef, kServiceType, kMyDefaultDomain); } else gResolveProcessActive = FALSE; } } static void MyAddService(CFNetServiceRef service, CFOptionFlags flags) { int referenceCount; CFStringRef serviceNameRef; char servicename[256]; CFIndex namelen = sizeof(servicename); Boolean addName = FALSE; /* You should do reference counting of each service because if the computer has two network interfaces set up, like Ethernet and AirPort, you may get notified about the same service twice, once from each interface. You probably don't want both items to be shown to the user. */ assert(service != NULL); serviceNameRef = CFNetServiceGetName(service); assert(serviceNameRef != NULL); assert(gServiceDictionary != NULL); if (CFStringGetCString(serviceNameRef, servicename, namelen, kCFStringEncodingMacRoman)) { servicename[namelen]='\0'; for(int i=0;i