Every now and then I like to do a write-up on something code-related. The issue I faced recently was probably one of the most convoluted programming rabbit holes I’ve ever been down, so it seemed to be a fitting subject.



The problem I faced revolved around the Save File Transfer System that I created for my game, Soda Dungeon. In particular, when I used the system to transfer a file to an iOS device, the application crashed.



First, a quick overview of the existing system: When the game saves, it writes a long list of values into a string using a Serializer. Values like gold, dungeon level, items collected, etc. That value is stored to disk, then deserialized and cast back to its native format when the game loads. The transfer system just builds on this concept. Although the serialized string is semi-obfuscated, parts of it still read as plain text. I was also concerned that it may internally contain some instances of “special characters,” and I get paranoid about packing up and transporting those around. I decided to encode the serialized string into Base64- meaning that the result would essentially only be comprised of plain, alphanumeric characters. This also meant that the resulting string would be larger, but I thought it was a fair trade-off for “safety.” In hindsight Base64 was probably complete overkill and probably not necessary because I wasn’t sending the file as a url parameter or anything, but it’s what I locked myself into.



So we have this file that crashes when loaded on an iOS device. At first glance, there could be a lot of culprits. Was the file getting corrupted? Was iOS unable to process the encoding/decoding in the same way the other platforms did? This led me through a lot of false-starts and red herrings before I found my way to the root of the problem. Soda Dungeon is programmed in HaXe, and at first I thought maybe iOS had an issue with HaXe’s dynamic typing system. So I updated the code and made sure all variables relating to the transfer were strictly-typed. No luck. I did a lot of print-outs of both the serialized string and the Base64 encoded string on different platforms, searching for a way in which the data differed. Everything seemed to match up. Another fumble occurred when I was sure that I had discovered extra whitespace in one of the files. But it turned out that my text editor’s auto-format had simply done this behind the scenes because I forgot to turn the feature off. I wasted a lot of time down that path, too.



Eventually I got the crazy idea to do something I should have tried much sooner: uploading and downloading the same save file without leaving the iOS ecosystem. Unsurprisingly, it worked. Also, as I had found previously, files transferred from iOS to any other platform still loaded fine. So here was definitive proof that the file was changing somehow, to a format that iOS could just not process. I was convinced that the issue now related to the difference in how Windows and iOS encoded their line endings- Windows using carriage return + line feed (CR+LF), while iOS must be using what OSX used, which was borrowed from UNIX: line feed only (LF). This led to more wasted hours of trimming the files for various kinds of whitespace, which is what the line ending characters were considered. Again, no luck.

Then, I realized I had glossed over a very important detail: When I compared the different file strings from each platform, I was only ever examining the original one generated on Windows. Sure, I compared it to the file that was downloaded onto iOS. But it still came from Windows. I never compared it to one that was generated on iOS. Sure enough, they differed.

When converted to their regular, serialized format, each string was almost identical, save for a few random sections. So this ruled out the line ending theory. The problem was that I had no idea what kind of data these differing chunks of text actually represented. But like I mentioned before, the serialized format was only partially obfuscated. Any time I stored a value as plain text in the game’s save file, it would read as plain text here. As it turned out, one of these mystery chunks landed right before some plain text values I could recognize: the list of enemies seen in the bestiary. I loaded up the code that was responsible for the initial serializing of the save file, and answer was very clear: right before I serialized the bestiary information, I serialized two date objects.

Knowing this, I could map the mystery chunks 1:1 with a corresponding date object. I discovered that the crux of the issue was that on Windows the dates were being encoded as a raw timestamp: “1459545643000.” On iOS, this value was encoded as a formatted string: “2016-04-01 17:20:43.” Looking at it now it seems pretty obvious that the data I was trying to inspect was a date, especially considering that “2016″ showed up. But it’s not quite as obvious when surrounded by hundreds of other characters. Also it was late at night.

So now I had to find a way to convert the timestamp to a formatted string so that the file would be accepted on iOS. Seeing that the timestamp was just a series of numbers I assumed it was a UNIX timestamp, aka the number of seconds that had passed since January 1st, 1970. This would be very ironic, considering it was Windows that had chosen to adopt the UNIX file endings, not the other way around. But after plugging that number into a UNIX timestamp converter, the resulting date was completely different from the formatted one that iOS was encoding with.



I turned to the HaXe documentation to try and figure out the nature of the timestamp. It didn’t explain anything, but as luck would have it, HaXe lets you generate a new Date object by passing in a timestamp. Surely it was the same timestamp format it was using to serialize with on Windows? Sure enough, it was. After creating the date object and printing it as a formatted string to the console, it matched the iOS date exactly. Down to the very second.

I was in the homestretch. The problem was solved, but now I needed to perform this conversion automatically whenever a file was loaded on iOS. First, I noticed that whenever a date was serialized, regardless of platform, it was always preceded with a ‘v.’ On Windows, that ‘v’ was always followed by the 13 digit timestamp. Based on the surrounding serialized values, it seemed safe to assume that a ‘v’ followed by at least 13 digits was a date. From here it was pretty straightforward: use a Regular Expression to capture all instances of the timestamp patttern, strip off the ‘v,’ cast the digits to a number, feed it to a date object, output that as a formatted string, and swap it back in for the original value that was found. As such, here is the code snippet that saved the file transfer system on iOS:

var dateMatch:EReg = new EReg(“v[0-9]{13,}”, “g”);

nativeSaveFileString = dateMatch.map(nativeSaveFileString, mapTimestampToFormattedString); function mapTimestampToFormattedString(match:EReg):String

{

var timestamp:String = match.matched(0);

timestamp = timestamp.substr(1);

var numericTimestamp = Std.parseFloat(timestamp);

var date = Date.fromTime(numericTimestamp);

return “v” + date.toString();

}

There was only one catch: nothing prevented this timestamp pattern from showing up inside of another string value. So if for some reason I named an enemy “v1234567890123,” that name would be captured and converted to a date string. With a little more work I could come up with a way to preemptively filter out strings like this, but it seemed unnecessary. If we ever decide to name an enemy in that fashion then I think we deserve it. At least iOS would still parse the file correctly.

