491 lines
16 KiB
C++
491 lines
16 KiB
C++
|
#include <stdio.h>
|
||
|
#include <sys/socket.h>
|
||
|
#include <netinet/in.h>
|
||
|
#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)
|
||
|
{
|
||
|
(const void*)serviceNameRef = 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;i<gNumLocalGames;i++)
|
||
|
if(_stricmp(servicename,gLocalGames[i].name)==0)
|
||
|
{
|
||
|
// Iterate through addresses until we find an IPv4 address
|
||
|
for (index = 0; index < CFArrayGetCount(addresses); index++)
|
||
|
{
|
||
|
socketAddress = (struct sockaddr *)CFDataGetBytePtr((__CFData*)CFArrayGetValueAtIndex(addresses, index));
|
||
|
if (socketAddress && socketAddress->sa_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)
|
||
|
{
|
||
|
(const void*)serviceNameRef = 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<gNumLocalGames;i++)
|
||
|
if(_stricmp(servicename,gLocalGames[i].name)==0)
|
||
|
return;
|
||
|
|
||
|
strcpy(gLocalGames[gNumLocalGames].name,servicename);
|
||
|
gLocalGames[gNumLocalGames].numPlayers=-1;
|
||
|
gLocalGames[gNumLocalGames].loaded=false;
|
||
|
gLocalGames[gNumLocalGames].mapRef=kFileErr;
|
||
|
MyResolveService(serviceNameRef,kServiceType,kMyDefaultDomain);
|
||
|
gNumLocalGames++;
|
||
|
gLocalGameListUpdate++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
MyRemoveService(CFNetServiceRef service, CFOptionFlags flags)
|
||
|
{
|
||
|
int referenceCount;
|
||
|
CFRange range;
|
||
|
CFIndex index;
|
||
|
CFStringRef serviceNameRef;
|
||
|
char servicename[256];
|
||
|
CFIndex namelen = sizeof servicename;
|
||
|
CFIndex result;
|
||
|
|
||
|
assert(service != NULL);
|
||
|
|
||
|
serviceNameRef = CFNetServiceGetName(service);
|
||
|
|
||
|
assert(serviceNameRef != NULL);
|
||
|
if (CFStringGetCString(serviceNameRef, servicename, namelen, kCFStringEncodingMacRoman))
|
||
|
{
|
||
|
servicename[namelen]='\0';
|
||
|
for(int i=0;i<gNumLocalGames;i++)
|
||
|
if(_stricmp(servicename,gLocalGames[i].name)==0)
|
||
|
{
|
||
|
MemoryMove(gLocalGames+i,gLocalGames+i+1,(gNumLocalGames-i-1)*sizeof(tGameListEntry));
|
||
|
gNumLocalGames--;
|
||
|
gLocalGameListUpdate++;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
MyBrowserCallback(CFNetServiceBrowserRef browser, CFOptionFlags flags, CFTypeRef domainOrService, CFStreamError* error, void* info)
|
||
|
{
|
||
|
if (flags & kCFNetServiceFlagIsDomain)
|
||
|
return;
|
||
|
|
||
|
/* You should do as little work as possible while in the Browser Callback because the mDNSResponder will timeout
|
||
|
if you don't extract items out of the Mach message queue fast enough. If a timeout occurs, you will see
|
||
|
a message in the Console that looks like... "mDNSResponder[260]: 10531: Browser(_afpovertcp._tcp.local.)
|
||
|
stopped accepting Mach messages (browse)" and from that point, your browsing will no longer return any results. */
|
||
|
|
||
|
if (flags & kCFNetServiceFlagRemove)
|
||
|
{
|
||
|
MyRemoveService((CFNetServiceRef)domainOrService, flags);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
MyAddService((CFNetServiceRef)domainOrService, flags);
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static OSStatus
|
||
|
MyStartBrowsingForServices(CFStringRef type, CFStringRef domain)
|
||
|
{
|
||
|
CFNetServiceClientContext clientContext = { 0, NULL, NULL, NULL, NULL };
|
||
|
CFStreamError error;
|
||
|
Boolean result;
|
||
|
OSStatus err = noErr;
|
||
|
|
||
|
assert(type != NULL);
|
||
|
assert(domain != NULL);
|
||
|
|
||
|
gServiceBrowserRef = CFNetServiceBrowserCreate(kCFAllocatorDefault, MyBrowserCallback, &clientContext);
|
||
|
if (gServiceBrowserRef == NULL)
|
||
|
{
|
||
|
err = memFullErr;
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CFNetServiceBrowserScheduleWithRunLoop(gServiceBrowserRef, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
|
||
|
|
||
|
result = CFNetServiceBrowserSearchForServices(gServiceBrowserRef, domain, type, &error);
|
||
|
|
||
|
if (result == FALSE)
|
||
|
{
|
||
|
// Something went wrong so lets clean up.
|
||
|
CFNetServiceBrowserUnscheduleFromRunLoop(gServiceBrowserRef, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
|
||
|
CFRelease(gServiceBrowserRef);
|
||
|
gServiceBrowserRef = NULL;
|
||
|
|
||
|
PrintConsoleString("CFNetServiceBrowserSearchForServices returned (domain = %d, error = %ld)\n", error.domain, error.error);
|
||
|
err = error.error;
|
||
|
}
|
||
|
else
|
||
|
// indicate that the browser task is active
|
||
|
gBrowserTaskActive = TRUE;
|
||
|
}
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
MyStopBrowsingForServices()
|
||
|
{
|
||
|
CFStreamError streamerror;
|
||
|
assert(gServiceBrowserRef != NULL);
|
||
|
|
||
|
CFNetServiceBrowserStopSearch(gServiceBrowserRef, &streamerror);
|
||
|
CFNetServiceBrowserUnscheduleFromRunLoop(gServiceBrowserRef, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
|
||
|
CFNetServiceBrowserInvalidate(gServiceBrowserRef);
|
||
|
CFRelease(gServiceBrowserRef);
|
||
|
gServiceBrowserRef = NULL;
|
||
|
|
||
|
assert(gServiceDictionary != NULL);
|
||
|
CFDictionaryRemoveAllValues(gServiceDictionary);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
void LocalTrackerStartAdvertising(char *gameName)
|
||
|
{
|
||
|
MyRegisterService(CFStringCreateWithCString(NULL,gameName,kCFStringEncodingMacRoman),kServiceType,kMyDefaultDomain,33333,NULL);
|
||
|
}
|
||
|
|
||
|
void LocalTrackerStopAdvertising()
|
||
|
{
|
||
|
MyCancelRegistration();
|
||
|
}
|
||
|
|
||
|
void LocalTrackerSearchGamesInit()
|
||
|
{
|
||
|
//HandleError()
|
||
|
MyStartBrowsingForServices(kServiceType,kMyDefaultDomain);
|
||
|
}
|
||
|
|
||
|
void LocalTrackerSearchGamesDone()
|
||
|
{
|
||
|
MyStopBrowsingForServices();
|
||
|
}
|