Subscribe to me on YouTube

Coptic Stitch Book Binding

February 8th, 2013 by Cody

Check it out! I totally just bound my first book using coptic stitch method. I followed SeaLemonDIY Coptic Stitch tutorials on youtube. I realized after I finished that I did the edge stitching wrong when looping to the next page. You are suppose to loop two signatures down on the edges (at least I think that’s the idea). Also pulled a little too hard on the stitching a couple of times and ripped some pages a little. All in all, feels pretty solid even with my mistakes. The cover I got from digging through books at Half-Price Books and then gutted it. Inspired by the Sketchbook Project, this will be a collaborative sketchbook. So my first contribution was inspired by Magic Game Time, although mine has nothing to do with games. Also I’m not an artist. Wishing I had Zac Gorman skillz. I keep saying inspired, but really, I just steal cool ideas from a bunch of random people and slop them together and pretend I’m a super hipster/creative… <_<







Scanned the image in instead of just taking a picture so it’s more visible.

Lookup IP Address by User Defined Ethernet Name on Mac OS X

December 19th, 2012 by Cody

This proved to be far more tricky than I thought it would. The documentation not being clear to me and lack of example code made things convoluted. So this involved a lot of trial and error with the SCNetworkConfiguration APIs and reading the System Configuration Schema. I had found examples in our code base on how to look up the BSD name based on the user defined name by iterating through the kSCPrefNetworkServices in SCNetworkSetRef returned from SCNetworkSetCopyCurrent(). Once you have the BSD name, it’s pretty simple to lookup the IP address and subnet mask from it.

The problem I ran into is if the user has two network services configured on the same ethernet device. So say the user has two network services, Ethernet 1 and Ethernet 2, both configured on en0. I want the IP address for Ethernet 2 network service. So I fetch it’s BSD name which is en0, and then see en0 has two IP Addresses with no way to link back which IP address belongs to which network service. My first attempt was to go through SCNetworkProtocolGetConfiguration(). Which sort of worked, however quickly realized that if the ConfigMethod was set to DHCP, I couldn’t get any IPv4 info from the protocol configuration dictionary.

Finally realized the trick was to use the System Configuration Schema. The solution is to look up the NetworkServiceRef using the user defined name. Then access the dynamic store information for the network service using SCDynamicStoreKeyCreateNetworkServiceEntity() and SCDynamicStoreCopyValue(). The information is stored down in State:/Network/Service/ServiceID/IPv4.

I’m worried that I might be shaving a yak here. The whole reason I’m doing this is because I need to be able to broadcast messages on the ethernet device that the user specifies. The solution I came up with was to calculate the broadcast address based on the selected device, and use sendto() to broadcast to that destination. This will fall through the routing table and out the proper device. If I was on Linux, I could just SO_BINDTODEVICE, but Mac will be having none of that SO_BINDTODEVICE hooligans. If you know of a better solution for Mac to broadcast on a specific device or get the broadcast address, please let me know.

Enough talk, here is the code I use to fetch the IP address and broadcast address. Note that CopyIPv4NetworkProtocolWithUserDefinedName() isn’t actually used or needed for this solution. I created it when trying to solve this another way and thought it would prove useful to leave in. Enjoy!

Update (04/25/2013): Fixed an issue where the System Configuration could end up with zombie network services. So if you had named a service the same name as one of the zombie services, then there was a good chance it would attempt to pull IP info from that zombie service which has no IP info. Also fixed some memory leaks.

//
// includes
//
#import <SystemConfiguration/SystemConfiguration.h>
#import <CoreFoundation/CoreFoundation.h>
#import <arpa/inet.h>
#import <string>

//
// desc: convert IPv4 address from binary format to numbers-and-dots notation in a string
//
// params: networkAddress[in] - IPv4 address in binary format to be converted to string
//
// returns: returns networkAddress in nubers-and-dots notation
//
std::string ConvertNetworkAddressToString(int networkAddress)
{
   // convert to string and return it
   struct in_addr address_struct;
   memset(&address_struct, 0, sizeof(address_struct));
   address_struct.s_addr = htonl(networkAddress);
   return std::string(inet_ntoa(address_struct));
}

//
// desc: fetch IPv4 address for network service, aka network interface,
//       with the user defined name.
//
// params: userDefinedName[in] - user defined name of network service to get IPv4 address for.
//                               this is whats displayed under System Preferences->Network, for
//                               example, "Ethernet 1", "WiFi", etc...
//
// returns: returns IPv4 address in numbers-and-dots notation if successful
//          returns 0.0.0.0 if failed (logs errors as debug messages to VLog)
//
std::string IPAddressForEthernetInterfaceWithUserDefinedName(const char *userDefinedName)
{
   // validate params
   if (!userDefinedName) {
      return std::string("0.0.0.0");
   }
   
   // first check if they want to go to localhost
   if (strcmp(userDefinedName, "localhost") == 0) {
      return std::string("127.0.0.1"); // just return localhost as destination, and we are done
   }
   
   // get network service
   SCNetworkServiceRef networkServiceRef = CopyNetworkServiceRefWithUserDefinedName(userDefinedName);
   if (!networkServiceRef) {
      return std::string("0.0.0.0");
   }
   
   // get ip address from network service
   CFStringRef address = CopyIPAddressFromNetworkService(networkServiceRef);
   if (!address) {
      CFRelease(networkServiceRef);
      return std::string("0.0.0.0");
   }
   
   // validate string
   if (CFStringGetLength(address) <= 0) {
      CFRelease(address);
      CFRelease(networkServiceRef);
      return std::string("0.0.0.0");
   }
   
   // convert to std::string
   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   std::string returnAddress = std::string([(NSString *)address UTF8String]);
   [pool drain];
   
   // cleanup
   CFRelease(address);
   CFRelease(networkServiceRef);
   
   // return ip address
   return returnAddress;
}

//
// desc: fetch IPv4 broadcast address for network service, aka network interface, with the user
//       defined name.
//
// params: userDefinedName[in] - user defined name of network service to get IPv4 broadcast address for.
//                               this is whats displayed under System Preferences->Network, for
//                               example, "Ethernet 1", "WiFi", etc...
//
// returns: returns IPv4 broadcast address in numbers-and-dots notation if successful
//          returns 0.0.0.0 if failed (logs errors as debug messages to VLog)
//
std::string BroadcastAddressForEthernetInterfaceWithUserDefinedName(const char *userDefinedName)
{
   // validate params
   if (!userDefinedName) {
      return std::string("0.0.0.0");
   }
   
   // first check if they want to go to localhost
   if (strcmp(userDefinedName, "localhost") == 0) {
      return std::string("127.0.0.1"); // just return localhost as destination, and we are done
   }
   
   // get network service
   SCNetworkServiceRef networkServiceRef = CopyNetworkServiceRefWithUserDefinedName(userDefinedName);
   if (!networkServiceRef) {
      return std::string("0.0.0.0");
   }
   
   // get ip address from network service
   CFStringRef addressString = CopyIPAddressFromNetworkService(networkServiceRef);
   if (!addressString) {
      CFRelease(networkServiceRef);
      return std::string("0.0.0.0");
   }
   
   // get subnet mask from network service
   CFStringRef subnetMaskString = CopySubnetMaskFromNetworkService(networkServiceRef);
   if (!subnetMaskString) {
      CFRelease(addressString);
      CFRelease(networkServiceRef);
      return std::string("0.0.0.0");
   }
   
   // validate strings
   if (CFStringGetLength(addressString) <= 0 || CFStringGetLength(subnetMaskString) <= 0) {
      CFRelease(addressString);
      CFRelease(subnetMaskString);
      CFRelease(networkServiceRef);
      return std::string("0.0.0.0");
   }
   
   // generate broadcast address
   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   in_addr_t address = htonl(inet_addr([(NSString *)addressString UTF8String]));
   in_addr_t subnetMask = htonl(inet_addr([(NSString *)subnetMaskString UTF8String]));
   [pool drain];
   
   int broadcastAddresss = address | ~subnetMask;
   
   // cleanup
   CFRelease(addressString);
   CFRelease(subnetMaskString);
   CFRelease(networkServiceRef);
   
   // return ip address
   return ConvertNetworkAddressToString(broadcastAddresss);
}

//
// desc: copies the first IPv4 address associated with the network service.
//
// params: networkServiceRef[in] - network service to get IPv4 address for
//
// returns: returns first IPv4 address associated with the network service if successful
//          returns NULL if failed (logs errors as debug messages to VLog)
//
CFStringRef CopyIPAddressFromNetworkService(SCNetworkServiceRef networkServiceRef) {
   return CopyIPv4PropertyFromNetworkService(kSCPropNetIPv4Addresses, networkServiceRef);
}

//
// desc: copies the first IPv4 subnet mask associated with the network service.
//
// params: networkServiceRef[in] - network service to get IPv4 subnet mask for
//
// returns: returns first IPv4 subnet mask associated with the network service if successful
//          returns NULL if failed (logs errors as debug messages to VLog)
//
CFStringRef CopySubnetMaskFromNetworkService(SCNetworkServiceRef networkServiceRef) {
   return CopyIPv4PropertyFromNetworkService(kSCPropNetIPv4SubnetMasks, networkServiceRef);
}

//
// desc: copies requested property from network service. this assumes the property your are
//       fetching from the network service is an array of strings.  it will return the first
//       string in the array.  done for convience reasons since we generally want one ip address, subnet mask, etc...
//       for network service.  for example, kSCPropNetIPv4Addresses will return an array of
//       all addresses for this network service, but typically we want just the top one in the list.
//
// params: property[in] - property you want from the network service, kSCPropNetIPv4Addresses, kSCPropNetIPv4SubnetMasks, etc...
//         networkServiceRef[in] - network service to get property from
//
// returns: returns copy of property value if successful
//          returns NULL if failed to retrieve property (logs errors as debug messages to VLog)
//
CFStringRef CopyIPv4PropertyFromNetworkService(CFStringRef property, SCNetworkServiceRef networkServiceRef)
{
   // validate parameter
   if (!networkServiceRef) {
      return NULL;
   }
   
   CFDictionaryRef dictionary = CopyDynamicStoreDictionaryForNetworkService(networkServiceRef, kSCEntNetIPv4);
   if (!dictionary) {
      return NULL;
   }
   
   CFStringRef string = GetFirstStringInArrayWithKeyInDictionary(property, dictionary);
   if (!string) {
      CFRelease(dictionary);
      return NULL;
   }
   
   // retain the ip address cause we will blitz the dictionary in a sec
   CFStringRef stringCopy = CFStringCreateCopy(NULL, string);
   
   // cleanup
   CFRelease(dictionary);
   
   return stringCopy;
}

//
// desc: copies the property list for a specific network service
//
// params: dynamicStoreRef[in] - dynamic store used to copy value from, user should create this using SCDynamicStoreCreate()
//         networkServiceRef[in] - network service to get property list for
//         domain[in] - domain to get property list for (kSCDynamicStoreDomainState, kSCDynamicStoreDomainSetup, etc...)
//         key[in] - key to get property list for (kSCEntNetIPv4, kSCEntNetIPv6, etc...)
//
// returns: returns CFPropertListRef contain property list of requested key if successful
//                  caller must release returned CFPropertyListRef using CFRelease
//          returns NULL if failed, check logs for more info
//
CFPropertyListRef CopyDynamicStorePropertyListForNetworkService(SCDynamicStoreRef dynamicStoreRef, SCNetworkServiceRef networkServiceRef, CFStringRef domain, CFStringRef key)
{
   // create the key we want
   CFStringRef dynamicStoreKey = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, domain, SCNetworkServiceGetServiceID(networkServiceRef), key);
   if (!dynamicStoreKey) {
      return NULL;
   }
   
   // get property list for key
   CFPropertyListRef propertyList = SCDynamicStoreCopyValue(dynamicStoreRef, dynamicStoreKey);
   if (!propertyList) {
      CFRelease(dynamicStoreKey);
      return NULL;
   }
   
   CFRelease(dynamicStoreKey);
   return propertyList;
}

//
// desc: copies the dynamic store (persistent store) dictionary for the network service.
//       if you don't know what i'm talking about, (cause i sure as hell didn't when i
//       started trying to figure this stuff out) go here
//       ( https://developer.apple.com/library/mac/#documentation/Networking/Conceptual/SystemConfigFrameworks/SC_UnderstandSchema/SC_UnderstandSchema.html#//apple_ref/doc/uid/TP40001065-CH203-CHDIHDCG ).
//
// params: networkServiceRef[in] - network service to get dynamic store dictionary for
//
// returns: returns dictionary if succesfully fetchs property list for network service
//          returns NULL if failed (logs errors as debug messages to VLog)
//
CFDictionaryRef CopyDynamicStoreDictionaryForNetworkService(SCNetworkServiceRef networkServiceRef, CFStringRef key)
{
   // validate params
   if (!networkServiceRef || !key) {
      return NULL;
   }
   
   // create dynamic store
   SCDynamicStoreRef dynamicStoreRef = SCDynamicStoreCreate(NULL, CFSTR("CopyDynamicStoreDictionaryForNetworkService"), NULL, NULL);
   if (!dynamicStoreRef) {
      return NULL;
   }
   
   // try to get it from domain state first
   CFPropertyListRef propertyList = CopyDynamicStorePropertyListForNetworkService(dynamicStoreRef, networkServiceRef, kSCDynamicStoreDomainState, key);
   if (!propertyList) {
      
      // since that failed, lets try domain setup
      propertyList = CopyDynamicStorePropertyListForNetworkService(dynamicStoreRef, networkServiceRef, kSCDynamicStoreDomainSetup, key);
      if (!propertyList) {
         CFRelease(dynamicStoreRef);
         return NULL;
      }
   }
   
   // make sure this is dictionary type
   if (CFGetTypeID(propertyList) != CFDictionaryGetTypeID()) {
      CFRelease(propertyList);
      CFRelease(dynamicStoreRef);
      return NULL;
   }
   
   // cleanup
   CFRelease(dynamicStoreRef);
   
   // return dictionary
   return (CFDictionaryRef)propertyList;
}

//
// desc: copy network service with user defined name.
//
// params: userDefinedName[in] - user defined name of network service to get IPv4 broadcast address for.
//                               this is whats displayed under System Preferences->Network, for
//                               example, "Ethernet 1", "WiFi", etc...
//
// returns: returns SCNetworkServiceRef to network service with user defined name is successful
//          returns NULL if failed (logs errors as debug messages to VLog)
//
SCNetworkServiceRef CopyNetworkServiceRefWithUserDefinedName(const char *userDefinedName)
{
   // validate params
   if (!userDefinedName) {
      return NULL;
   }
   
   // create preferences
   SCPreferencesRef preferences = SCPreferencesCreate(kCFAllocatorDefault, CFSTR("PRG"), NULL);
   if (!preferences) {
      return NULL;
   }
   
   // get list of all interface devices
   CFArrayRef serviceArray = SCNetworkServiceCopyAll(preferences);
   if (!serviceArray) {
      CFRelease(preferences);
      return NULL;
   }
   
   // get array of valid service id's
   CFArrayRef validServiceIds = CreateArrayOfValidNetworkServiceIds();
   
   // look for out guy
   SCNetworkServiceRef returnNetworkServiceRef = NULL;
   for (int i=0; i < CFArrayGetCount(serviceArray); i++) {
      SCNetworkServiceRef networkServiceRef = (SCNetworkServiceRef)CFArrayGetValueAtIndex(serviceArray, i);
      
      // if not valid service id, then skip
      CFStringRef serviceId = SCNetworkServiceGetServiceID(networkServiceRef);
      if (!CFArrayContainsValue(validServiceIds, CFRangeMake(0, CFArrayGetCount(validServiceIds)), serviceId)) {
         continue;
      }
      
      // if we got a match on service name
      NSString *serviceName = (NSString *)SCNetworkServiceGetName(networkServiceRef);
      if (strcmp([serviceName UTF8String], userDefinedName) == 0) {
         returnNetworkServiceRef = networkServiceRef; // save off our interface so we can return it
         CFRetain(returnNetworkServiceRef);           // retain object so we don't lose it, up to caller to release
         break;
      }
   }
   
   CFRelease(preferences);
   CFRelease(serviceArray);
   CFRelease(validServiceIds);
   return returnNetworkServiceRef;
}

//
// desc: copy IPv4 protocol with user defined name.
//
// params: userDefinedName[in] - user defined name of network service to get IPv4 broadcast address for.
//                               this is whats displayed under System Preferences->Network, for
//                               example, "Ethernet 1", "WiFi", etc...
//
// returns: returns SCNetworkProtocolRef to network service IPv4 protocol with user defined name is successful
//          returns NULL if failed (logs errors as debug messages to VLog)
//
SCNetworkProtocolRef CopyIPv4NetworkProtocolWithUserDefinedName(const char *userDefinedName)
{
   // validate params
   if (!userDefinedName) {
      return NULL;
   }
   
   SCNetworkServiceRef networkServiceRef = CopyNetworkServiceRefWithUserDefinedName(userDefinedName);
   if (!networkServiceRef) {
      return NULL;
   }
   
   // get ipv4 protocol from service
   SCNetworkProtocolRef protocol = SCNetworkServiceCopyProtocol(networkServiceRef, kSCEntNetIPv4);
   if (!protocol) {
      CFRelease(networkServiceRef);
      return NULL;
   }
   
   // cleanup
   CFRelease(networkServiceRef);
   return protocol;
}

//
// desc: convience function for fecthing the first string object in an array of string objects
//       that is stored in a |dictionary| with |key| value.
//
// params: key[in] - dictionary key, object for key is expected to be an array of CFStringRef objects
//         dictionary[in] - dictionary to look the key up in
//
// returns: returns string of first object in the array with |key| in the |dictionary|
//          returns NULL if failed (logs errors as debug messages to VLog)
//
CFStringRef GetFirstStringInArrayWithKeyInDictionary(CFStringRef key, CFDictionaryRef dictionary)
{
   // validate params
   if (!key || !dictionary) {
      return NULL;
   }
   
   // fetch value
   CFTypeRef object = CFDictionaryGetValue(dictionary, key);
   if (!object) {
      return NULL;
   }
   
   // if this isn't an array, then bail
   if (CFGetTypeID(object) != CFArrayGetTypeID()) {
      return NULL;
   }
   
   // make sure we have items in the array
   CFArrayRef array = (CFArrayRef)object;
   if (CFArrayGetCount(array) <= 0) {
      return NULL;
   }
   
   // get first object in list
   object = CFArrayGetValueAtIndex(array, 0);
   if (!object) {
      return NULL;
   }
   
   // make sure it's a string
   if (CFGetTypeID(object) != CFStringGetTypeID()) {
      return NULL;
   }
   
   return (CFStringRef)object;
}

//
// desc: create a list of valid network service ids.  this are all the network service
//       id's listed in the Setup: domain.
//
// returns: returns array of valid network service id's if successful
//                  it is up to the caller to release this using CFRelease()
//          returns NULL if failed to create list of valid network service id's, check logs for more info
//
CFArrayRef CreateArrayOfValidNetworkServiceIds()
{
   @autoreleasepool {
      
      CFStringRef pattern = CFSTR("Setup:/Network/Service/[^/]+");
      
      // create dynamic store
      SCDynamicStoreRef dynamicStoreRef = SCDynamicStoreCreate(NULL, CFSTR("CreateListOfActiveIPv4NetworkServiceIds"), NULL, NULL);
      if (!dynamicStoreRef) {
         return NULL;
      }
      
      // get property list for key
      CFArrayRef keyList = SCDynamicStoreCopyKeyList(dynamicStoreRef, pattern);
      if (!keyList) {
         CFRelease(dynamicStoreRef);
         return NULL;
      }
      
      CFRelease(dynamicStoreRef);
      
      // strip prefix "Setup:/Network/Service/" so we have just the service id
      NSMutableArray *serviceIdList = [[NSMutableArray alloc] initWithCapacity:CFArrayGetCount(keyList)];
      if (!serviceIdList) {
         CFRelease(keyList);
         return NULL;
      }
      
      for (NSString *key in (NSArray *)keyList) {
         NSString *serviceId = [key substringFromIndex:[key length] - 36]; // service id should be at end and is 36 characters long, so get that substring from end
         [serviceIdList addObject:serviceId];
      }
      
      CFRelease(keyList);
      return (CFArrayRef)serviceIdList;
   }
}

//
// desc: return if a network service id is valid or not.  checks
//       to see if network service id exist in the Setup: domain to
//       detemine if it is valid or not.
//
// params: networkServiceId[in] - network service id, for example "91B9C4B4-BB98-445D-BFDB-2D92DADE3B6B"
//
// returns: returns true if network service id exist in the Setup: domain
//          returns false if network service id does NOT exist in the Setup: domain
//
bool IsValidNetworkServiceId(CFStringRef networkServiceId)
{
   CFArrayRef validServiceIds = CreateArrayOfValidNetworkServiceIds();
   if (!validServiceIds) {
      return false;
   }
   
   if (CFArrayContainsValue(validServiceIds, CFRangeMake(0, CFArrayGetCount(validServiceIds)), networkServiceId)) {
      CFRelease(validServiceIds);
      return true;
   }
   
   CFRelease(validServiceIds);
   return false;
}

First attempt at marketing!

October 23rd, 2012 by Cody

Finally starting to work on some marketing material. Hopefully this will end up on a postcard style flyer on the desk of Zeus Comics. First draft, so still needs some tweaking.

Realizations as an iOS hobbyist

October 6th, 2012 by Cody

Read this article, Some Depressing Thoughts On The App Store, this morning and had some thoughts I needed to get out there. I’ve always thought of the app store being more of a marketing tool for Apple, but I think someone validating the claim really makes it hit home. I know I haven’t done my part to market my game and I created S BAT 2 to complete a life goal, not to supplement my income. I was not expecting to become a millionaire or even hundred-aire based on one silly idea for a game. It would be nice to get reimbursed the fees for the dev license, but even then, I considered it the cost of a hobby. Between seeing articles like this, XCode and LLDB making my job a complete nightmare this week, and the fact that when I let my dev license expire I’ll never be able to play my game again (well I could do some jailbreaking), I honestly don’t know why any hobbyist would want to develop on iOS. Outside of Objective-C, which is amazing, Apple development is pretty painful process in my book. If I do tackle another game project, it’ll be on Windows or Linux. At least there I won’t have to pay just to be able to develop and play my own game. Also the indie game community is way more supportive in the Windows and Linux world. End rant.

S BAT 2 GXO: The OS HD Now Available!

September 22nd, 2012 by Cody

Finally got HD version for iPad released on the App Store. I apologize that the framerate isn’t as good as I had hoped for. If I find time, I will try to correct that. Also just submitted v1.1 for iPhone which contains various fixes.

S BAT 2 GXO: The OS version 1.1 fixes

  • fix, now decrypts and plays background track on a low priority background thread, this is to fix a performance hiccup that occurs when it is decrypting the track
  • fixed some typos in tutorial menus
  • added pause bar to tutorial mode so players can back out of it
  • fixed the repeater beams from popping from their old location when being reinitialized
  • fix where first wave stage would sometimes continue spawning off the play field

Now I just need to make a sad attempt at marketing this guy :D.

Getting back to it

July 20th, 2012 by Cody

After a long hiatus which included beating Bastion, VVVVV, and Botanicula, I’m finally getting back into some S BAT 2 development. Got a little bit of feedback, but not much. Which is what I expected since I’m virtually unknown to anyone and it’s just a few friends that mention it every once in a while. Goal is to try to get iPad paid version out sometime in August. I will also make some sad attempt at marketing, and if I don’t see any interest after that, then I will probably drop the project. Anywho, here is what I got done today.

- Move decryption and loading of background files to low priority background thread so it will no longer interrupt gameplay with the performance hit
- Added pause terminal to top of the tutorial mode so you can back out and adjust options when in the tutorial
- Fixed typos in tutorial terminal text

S BAT 2 GXO: The OS is now available!

June 6th, 2012 by Cody

And even better, it’s free! Download it here! Starting to get some feedback from people so starting a list of fixes and changes for the next update. I plan to take a break for the next couple of weeks and play through Bastion (I’m sooo far behind on gamming) and then I’ll start on small update for iPhone followed by working on hi res iPad version. And somewhere in there, I plan to start scripting the story mode. So after I take a short hiatus, here is the list of stuff I want to tackle. Thanks for playing and please feel free to give me feedback.

S BAT 2 GXO: The OS v2

  • Fix bug where game pauses when loading next background track, this hiccup sometimes causes the mobile towers to move outside of the play field
  • Add terminal to top of tutorial mode so user can exit tutorial mode at any point and adjust options without having to complete the tutorial
  • Track down bug when emulating on iPad where sometimes the emissions will reach their target, but disappear preventing the next wave from spawning
  • Fix typos in the tutorial menu text (I’m sure Marc is laughing it up on this one…)

Loading sound from memory into cocos2d’s SimpleAudioEngine

April 19th, 2012 by Cody

This was seriously way more work than I anticipated. In the end, it really wasn’t that complicated, but just took a while to put all the pieces together. Having to wrap the ExtAudioFileRef around an AudioFileID and then having to setup callback functions to get the AudioFileID from memory felt like a bit of overkill… Here is the code I added to CDOpenALSupport.m. I’m sure there is a better solution, but this works so I’m gonna stick with it for now ^_^.

//
// desc: for storing the decrypted audio file data in memory
//
typedef struct {
    unsigned char *data;    // pointer to audio data
    SInt64 dataLength;      // length of audio data
} AudioFileMemory;

//
// desc: callback for reading the audio data, 
//       check AudioFileOpenWithCallbacks doc for more details
//
OSStatus AudioFileReadProc(void *inClientData, SInt64 inPosition, UInt32 requestCount, void *buffer, UInt32 *actualCount)
{
    // check parameters
    if (!inClientData || !buffer || !actualCount) {
        return EINVAL;
    }
    
    AudioFileMemory *audioFileMemory = (AudioFileMemory *)inClientData;
        
    // make sure position is within bounds
    if (inPosition < 0 || inPosition >= audioFileMemory->dataLength) {
        *actualCount = 0; // don't read anything and tell them everything is just friggin fine,
                          // this is called passive aggressive error handling ^_^
        return noErr;
    }
    
    // see if we need to cap requested length
    *actualCount = requestCount;
    SInt64 endPosition = inPosition + requestCount;
    if (endPosition >= audioFileMemory->dataLength) {
        *actualCount = requestCount - (endPosition - audioFileMemory->dataLength);
    }
    
    memcpy(buffer, audioFileMemory->data + inPosition, *actualCount);
    return noErr;
}

//
// desc: callback for getting size of audio data 
//       check AudioFileOpenWithCallbacks doc for more details
//
SInt64 AudioFileGetSizeProc(void *inClientData) {
    if (!inClientData) {
        return EINVAL;
    }
    
    AudioFileMemory *audioFileMemory = (AudioFileMemory *)inClientData;
    return audioFileMemory->dataLength;
}

//
// desc: assumes this is encrypted or packaged file and that
//       the file naming convention is filename.xxx.enc or filename.xxx.tar, etc...
//       determines type based off xxx, only coded for checking for wave and mp3
//       cause that's all I care about right now
//
// params: inFileURL[in] - file url to get audio file type on
//
// returns: audio file type id for file if successful
//          returns 0 if file type is unknown
//
AudioFileTypeID GetAudioFileTypeId(CFURLRef inFileURL)
{
    CFURLRef pathWithoutEncExtension = CFURLCreateCopyDeletingPathExtension(kCFAllocatorDefault, inFileURL);
    CFStringRef extension = CFURLCopyPathExtension(pathWithoutEncExtension);
    AudioFileTypeID audioFileTypeId = 0;
    if (CFStringCompare(extension, (CFStringRef)@"wav", kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
        audioFileTypeId = kAudioFileWAVEType;
    }
    else if (CFStringCompare(extension, (CFStringRef)@"mp3", kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
        audioFileTypeId = kAudioFileMP3Type;
    }
    
    CFRelease(pathWithoutEncExtension);
    CFRelease(extension);
    return audioFileTypeId;
}

//
// desc: creates an audio file id, checks to see if file type is encrypted.
//       if it's not encrypted, then it loads it like normal and returns audioFileId.
//       if is its encrypted, it will decrypt the file in memory and load it into a AudioFileId.
//       if it decrypts, then it will return pointer to the audio data in audioFileMemory.  when
//       you close the audioFileId, be sure to free() the data pointer inside audioFileMemory.
//       I'm not really sure if we need to keep the data around, but I'm doing it to be safe.
//
// params: inFileURL[in] - url of file to load
//         audioFileId[out] - returns audio file id if file was successfully loaded
//         audioFileMemory[out] - returns pointer to audio memory if the audio file was decrpyted and loaded from mem
//                                BE SURE TO CALL free() ON THE DATA POINTER WHEN YOU ARE DONE WITH THE AUDIO FILE ID
//
// returns: returns  0 if opened file like normal (not encrypted)
//          returns  1 if dectypred file and loaded it from memory
//          returns -1 if failed to open unencrypted file
//          returns -2 if failed to open encrypted file
//          returns -3 if failed to decrypt file
//          returns -4 if failed to load audio file id with decrypted memory
//
int CreateAudioFileId(CFURLRef inFileURL, AudioFileID *audioFileId, AudioFileMemory *audioFileMemory)
{
    CFStringRef extension = CFURLCopyPathExtension(inFileURL);
    OSStatus err = noErr;
    
    // reset audioFileMemory
    memset(audioFileMemory, 0, sizeof(AudioFileMemory));
    
    // if not an encrypted file, then get audio id like always
    if (CFStringCompare(extension, (CFStringRef)@"enc", kCFCompareCaseInsensitive) != kCFCompareEqualTo) {
        CFRelease(extension);
        
        err = AudioFileOpenURL(inFileURL, kAudioFileReadPermission, 0, audioFileId);
        if (err) {
            CDLOG(@"CreateAudioFileId: AudioFileOpenURL FAILED, Error = %ld\n", err);
            return -1;
        }
        
        return 0;
    }
    
    CFRelease(extension);
    
    // else we are dealing with encrypted file, so decrypt it
    @autoreleasepool {
        NSData *encData = [NSData dataWithContentsOfURL:(NSURL *)inFileURL];
        
        if (!encData) {
            CDLOG(@"CreateAudioFileId: Failed to load %@, could not find file.", CFURLGetString(inFileURL));
            return -2;
        }
        
        audioFileMemory->dataLength = CCDecryptMemory((unsigned char *)[encData bytes], [encData length], &audioFileMemory->data);
        if (!audioFileMemory->data) {
            CDLOG(@"CreateAudioFileId: Failed to load %@, failed to decrypt.", CFURLGetString(inFileURL));
            return -3;
        }
    }
    
    err = AudioFileOpenWithCallbacks(audioFileMemory,
                                     AudioFileReadProc, NULL,
                                     AudioFileGetSizeProc, NULL,
                                     GetAudioFileTypeId(inFileURL), audioFileId);
        
    if (err) {
        char bytes[4];
        bytes[0] = (err >> 24) & 0xFF;
        bytes[1] = (err >> 16) & 0xFF;
        bytes[2] = (err >> 8) & 0xFF;
        bytes[3] = err & 0xFF;
        CDLOG(@"CreateAudioFileId: AudioFileOpenWithCallbacks FAILED, Error = %ld, %c%c%c%c\n", err, bytes[0], bytes[1], bytes[2], bytes[3]);
        return -4;
    }
    
    return 1;
}



And after adding all that, just had to change CDloadWaveAudioData() and CDloadCafAudioData to use the new CreateAudioFileId() calls.

CDloadWaveAudioData() change:

//Taken from oalTouch MyOpenALSupport 1.1
void* CDloadWaveAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei*	outSampleRate)
{
	OSStatus						err = noErr;	
	UInt64							fileDataSize = 0;
	AudioStreamBasicDescription		theFileFormat;
	UInt32							thePropertySize = sizeof(theFileFormat);
	AudioFileID						afid = 0;
	void*							theData = NULL;
    AudioFileMemory                 audioFileMemory;
	
	// Open a file with ExtAudioFileOpen()
    if (CreateAudioFileId(inFileURL, &afid, &audioFileMemory) < 0) {
        goto Exit;
    }

CDloadCafAudioData change:

//Taken from oalTouch MyOpenALSupport 1.4
void* CDloadCafAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate)
{
	OSStatus						status = noErr;
	BOOL							abort = NO;
	SInt64							theFileLengthInFrames = 0;
	AudioStreamBasicDescription		theFileFormat;
	UInt32							thePropertySize = sizeof(theFileFormat);
	ExtAudioFileRef					extRef = NULL;
	void*							theData = NULL;
	AudioStreamBasicDescription		theOutputFormat;
	UInt32							dataSize = 0;
    AudioFileID                     audioFileId = 0;
    AudioFileMemory                 audioFileMemory;
    
    // create audio file id
    if (CreateAudioFileId(inFileURL, &audioFileId, &audioFileMemory) < 0) {
        goto Exit;
    }
	
	// Open a file with ExtAudioFileOpen()
    status = ExtAudioFileWrapAudioFileID(audioFileId, false, &extRef);
	if (status != noErr)
	{
		CDLOG(@"MyGetOpenALAudioData: ExtAudioFileOpenURL FAILED, Error = %ld\n", status);
		abort = YES;
	}
	if (abort)
		goto Exit;

Momentum Lost

December 4th, 2011 by Cody

Sorry for lack of updates, I’ve lost some momentum due to Thanksgiving and Christmas stuff coming up. Starting to get back into the swing of things. Have a plan to tackle tutorial which is next major thing on the list. In the meantime, I got some artwork done. Three of the ten characters are done. Here is a shot (from left to right) of Coco, Bango Blue, and Spin Lock. Click on image to see high rez.

S BAT 2 beta 2.0 now with fancy retina display

November 15th, 2011 by Cody

Sorry for lack of updates. Lost some momentum last few weeks (mainly due to Warhammer 40k: Space Marine and Dragon Age…). I got me a fancy iPhone 4S and got the artwork up-converted for retina display. Cocos2d made it relatively easy. Had one issue with z axis, but it wasn’t too hard to track down. As far as I can remember, the only change for this is retina. So no changelog this time.

SBAT2GXOTheOS_beta_2_0.zip