Launch Screen Sizes for iOS 12

If you still use launch screens, these are the sizes you need.

As with mobile icons, the proliferation of new Apple mobile devices demands an ever increasing number of launch images.  This post lists the necessary artwork sizes to directly support all devices (and possible orientations) as of iOS 12.

Of course, Apple realizes that adding new launch screen sizes every time they introduce a new form factor will quickly become unsustainable.  (It is close to that now; otherwise, this post would be unnecessary. 😉 )  For that reason, they introduced launch storyboards, which allow a launch screen to be defined by a layout of controls and constraints, rather than by individually sized images.  The only problem is that, unlike normal storyboards, the launch storyboard is loaded before the application runs, so only automatic layout can occur; no code can be executed.  Therefore, customization is restricted, which prevents developers from accomplishing the same thing that is possible with launch images, namely, use of the entire screen on each individual device for displaying an image.

Apple takes the position that the launch screen should display an image reflective of the first screen of the application to give the impression that the app has launched that quickly.  This certainly is better for Apple, who is not known for missing branding opportunities, but using the launch images for branding, i.e., a splash screen, is expected in the game industry (and, arguably, for any software).  Also, games usually have custom background artwork, and if that is customized to screen sizes, there is no easy way to mimic that with a launch storyboard, at least at the moment.

To be fair, nowadays the launch screen is a somewhat rare occurrence, as it is only shown at first launch, or when the application has been unloaded, either closed by the user or removed from the background by the system to recover resources.  If you design your splash screen to be an image in a sea of solid color or a repeating texture, you can reproduce that with a launch storyboard.  The kind of splash screens that Rick Tumanis designed for Demolish! Pairs or Pretty Good Solitaire, however, do not lend themselves to this approach.  It is for that reason that we still use launch images in those games (and others).  In fact, Demolish! Pairs will reshow the splash screen if you shake the device from the menu.  (It is an ‘undo’ when done from within a game.)

So without further ado…

iPhone Launch Screen Sizes

Apple iPhones provide the bulk of the variety in device screen sizes, so you need to provide 11 (or 12) launch images for full coverage, as follows:

  • Portrait iOS 12+
    1. 1242 x 2688 (@3x) – iPhone XS Max
    2. 828 x 1792 (@2x) – iPhone XR
  • Landscape iOS 12+
    1. 2688 x 1242 (@3x) – iPhone XS Max
    2. 1792 x 828 (@2x) – iPhone XR
  • Portrait iOS 11+
    1. 1125 x 2436 (@3x) – iPhone X / iPhone XS
  • Landscape iOS 11+
    1. 2436 x 1125 (@3x) – iPhone X / iPhone XS
  • iPhone Portrait iOS 8+
    1. 1242 x 2208 (@3x) – Retina HD 5.5″ (iPhone 6 Plus / 6s Plus / 7 Plus / 8 Plus)
    2. 750 x 1334 (@2x) – Retina HD 4.7″ (iPhone 6 / 6s / 7 / 8)
  • iPhone Landscape iOS 8+
    1. 2208 x 1242 (@3x) – Retina HD 5.5″ (iPhone 6 Plus / 6s Plus / 7 Plus / 8 Plus)
  • iPhone Portrait iOS 7+
    1. 640 x 960 (@2x) – 2x (iPhone 4s)
    2. 640 x 1136 (@2x) – Retina 4 (iPhone 5 / 5s / SE)
  • iPhone Portrait iOS 5, 6 (legacy)
    1. 320 x 640 – 1x (optional – unused on iOS 8+)
    2. 640 x 960 (@2x) – 2x (same as #10)
    3. 640 x 1136 (@2x) – Retina 4 (same as #11)

Note that the lowest supported target system for Xcode 10.0 (latest version as of this writing) is iOS 8.0, and no devices at the original iPhone resolution (320 x 640) can run that version of iOS, so the final unique size (#12) is unused in modern apps.

iPad Launch Screen Sizes

There is good news and bad news when it comes to supporting iPad launch images.

Good News

The good news is that you only need 4 launch images for iPad support, as follows:

  • iPad Portrait iOS 7+
    1. 768 x 1024 – 1x (iPad 2)
    2. 1536 x 2048 (@2x) – 2x (iPad Retina / Air / Air 2 / 5th gen / 6th gen)
  • iPad Landscape iOS 7+
    1. 1024 x 768 – 1x (iPad 2)
    2. 2048 x 1536 (@2x) – 2x (iPad Retina / Air / Air 2 / 5th gen / 6th gen)

That is pretty straightforward, especially given that they are the same aspect ratio, so you can just create the double density size and directly reduce the image.  Further, if your target platform is iOS 10 or higher, the iPad 2 is no longer supported, so you only need the two larger images for all remaining iPads.

For completeness, here are the legacy sizes for iPads running iOS 5 or 6:

  • iPad Portrait Without Status Bar iOS 5, 6 (legacy)
    1. 768 x 1004 – 1x (optional – unused on iOS 8+)
    2. 536 x 2008 (@2x) – 2x (optional – unused on iOS 8+)
  • iPad Portrait iOS 5, 6 (legacy)
    1. 768 x 1024 – 1x (same as #1)
    2. 1536 x 2048 (@2x) – 2x (same as #2)
  • iPad Landscape Without Status Bar iOS 5, 6 (legacy)
    1. 1024 x 748 – 1x (optional – unused on iOS 8+)
    2. 2048 x 1496 (@2x) – 2x (optional – unused on iOS 8+)
  • iPad Landscape iOS 5, 6 (legacy)
    1. 1024 x 768 – 1x (same as #3)
    2. 2048 x 1536 (@2x) – 2x (same as #4)

The only (4) new sizes here are just the same launch screen sizes with an area for the status bar (20 or 40 pixels) removed.  These sizes are never used in modern apps.

Bad News

The bad news is that, as keen observers will have noticed, I did not list any of the iPad Pro models above; this is because an iPad Pro acts like an iPad Retina.  Without a launch storyboard, all iPad Pro devices show up as though they are only 2048 x 1536 resolution.

To be fair, the smallest iPad Pro, the 9.7″ model, is only 2048 x 1536, but the two larger models have higher resolution that is not used: 12.9″ is 2732 x 2048; 10.5″ is 2224 x 1668.

If you want to have the full resolution of iPad Pro devices, you must use a launch storyboard rather than launch images; you cannot use both, as (experimentation suggests that) the inclusion of a launch storyboard supersedes launch images.  Damn.

Conclusion

The release of iOS 12 added the need for 4 additional launch screens (to support both orientations for the iPhone XR and iPhone XS Max), so a “universal” app needs to provide a total of 15 launch images to support all iPhone and iPad devices.  However, if your game or app needs to take advantage of the full resolution of the iPad Pro, you will need to provide a launch storyboard instead, and adjust your launch screen appropriately.

All Apple needs to do to provide complete support is to provide constraints for individual form factors, allowing different images to be selected from the launch storyboard based on device model, OR use a provided launch image in preference to (or prior to) a launch storyboard.  Best of all, they could just remove the arbitrary and silly restriction that prevents access to full iPad Pro resolution unless we provide a launch storyboard, which restriction seems to be in place for the sole purpose of strong-arming developers into supporting launch storyboards (before they provide equivalent functionality).

In the meantime (which will likely be forever), it makes sense to start figuring out how to adjust your launch design and strategy to work with launch storyboards.  This is not unlike what is currently necessary for Android support, anyway.  For our next project, we are investigating a high resolution square-ish logo that can be the sole image in a field of white, scaled to largest size possible to fit the launch screen.

Abandoning this expected branding opportunity, though?  Not likely.

Modern Mobile Icon Sizes

Icon sizes you need to support iOS 12 and Android Oreo devices.

With the explosion of mobile devices, it keeps getting more difficult to keep track of the myriad icon sizes required for mobile apps.  This post simply lists the required sizes and uses for iOS and Android devices (as of now).

When creating an application icon, it is best to start with an image that is (at least) the size of the largest icon necessary, which is currently 1024 x 1024, and then reduce that image to the necessary sizes, reducing level of detail as/if necessary for smaller icons.

iOS App Icons

As of Xcode 10 (iOS 12), iOSApple iOS devices have application icons for the app on three different device types (iPhone, iPad, and iPad Pro), the store, spotlight (search), settings, and notifications, and these icons may need to be provided in single (unadorned), double (“@2x”), or triple (“@3x”) scale factors.

For a “universal” app, you need to provide icons in 13 resolutions (for 15/18/19 different uses, depending on how you count).  iPhone only apps need 8 resolutions; iPad only apps need 9 resolutions.

Here is a list of the iOS icon sizes, along with a color-coded list of the uses:

  • 1024 x 1024
    • App Store – all applications  (<title>.png)
  • 180 x 180
    • iPhone application @3x  (<title>_iphone@3x.png)
  • 167 x 167
    • iPad Pro application @2x  (<title>_pro@2x.png)
  • 152 x 152
    • iPad application @2x  (<title>_ipad@2x.png)
  • 120 x 120
    • iPhone application @2x  (<title>_iphone@2x.png)
    • iPhone spotlight @3x  (<title>_search@3x.png)
  • 87 x 87
    • iPhone settings @3x  (<title>_settings@3x.png)
  • 80 x 80
    • iPhone spotlight @2x  (<title>_search@2x.png)
    • iPad spotlight @2x  (<title>_search@2x.png)
  • 76 x 76
    • iPad application  (<title>_ipad.png)
  • 60 x 60
    • iPhone notification @3x  (<title>_notify@3x.png)
  • 58 x 58
    • iPhone settings @2x  (<title>_settings@2x.png)
    • iPad settings @2x  (<title>_settings@2x.png)
  • 40×40
    • iPad spotlight  (<title>_search.png)
    • iPhone notification @2x  (<title>_notify@2x.png)
    • iPad notification @2x  (<title>_notify@2x.png)
  • 29 x 29
    • iPad settings  (<title>_settings.png)
  • 20 x 20
    • iPad notification  (<title>_notify.png)

Android App Icons

AndroidAndroid icon requirements had remained fairly consistent, but as of Android 8.0 (Oreo, API 26), those application icons are now classified as “legacy”, though still required for support on earlier devices (i.e., 85.4% of the market); the latest devices can use “adaptive” icons.  All applications should have a store icon.

For most Android apps, you should provide application icons in 6-8 resolutions (none of which overlap with the iOS resolutions).

Store Icon

Android apps, if they are to be published, require a store icon:

  • 512 x 512 – Google Play

Legacy Icons

To support devices running Android 7.1 (Nougat, API 25) and earlier, which you absolutely should, provide legacy icons in resolutions supporting 5 (or 6) different screen densities:

  • 192 x 192 – xxxhdpi
  • 144 x 144 – xxhdpi
  • 96 x 96 – xhdpi
  • 72 x 72 – hdpi
  • 48 x 48 – mdpi
  • 36 x 36 – ldpi (optional)

Note that some legacy icons can (technically) be omitted, but those sizes will be generated by the Android system from other sizes, and not necessarily the best resolution (i.e., a larger icon may be generated from a lower resolution icon, which looks poor).  Therefore, only the low density (ldpi) icon is considered optional; no modern devices are low density, and if one was it would necessarily generate the icon from a larger source.

Adaptive Icons

Adaptive icons were introduced in Android Oreo to allow the system to perform visual effects with the shape and/or contents of an application icon.  In order for this to be supported, adaptive icons need to be separated into foreground and background layers; the foreground contains the important content of the icon, toward the center, and the background is an image or color that may provide branding but could be clipped.

These are the components:

  • foreground – 108 x 108 image with transparency, icon content in center 72 x 72
  • background – color or 108 x 108 image to be composited with foreground

The foreground and background may be moved or sized independently before being composited together, and the resulting image will likely be cropped into an arbitrary shape; Android reserves an 18 pixel border for these kind of visual effects.

For more information, see this page about supporting adaptive icons.

Conclusion

For a mobile app to support all recent iOS and Android phones and tablets, you will need to create about 20 icons, in the resolutions above, including separate foreground and background layers for adaptive icon support.  If possible, start by creating an App Store icon (1024 x 1024) from a separate foreground and background.  Use that as a source icon to generate the smaller Google Play, iOS, and Android legacy icons, adjusting detail as necessary.  Finally, resize the foreground and background layers appropriately to generate the components for the adaptive icons.  (Then, take a deep breath.)

Now you are ready to implement your application. 😉  Of course, if you want to support Android TV, Apple TV, Android Wear, Apple Watch, Android Auto, and/or Apple CarPlay…

UI Lessons from a Terrible Interface

Or, 40 reasons why DirectTV sucks.

Let me preface this by pointing out that I have many, many years of experience with DVR recordings, originally (and still) using various TiVo boxes with Comcast/Xfinity cable, and more recently, almost three years with DirecTV (AT&T) and their Genie DVR.

Despite the claims in their commercials, DirecTV is objectively worse than cable television with a TiVo box.  The quality of recordings is noticeably lower in any viewing circumstance, and where there is motion involved, DirectTV just falls over.  Anybody who thinks otherwise must either not be discerning, have poor eyesight, or have never seen decent cable.

A few years back, TiVo redesigned its user interface to focus more directly on providing an interface for both cable recording and streaming services; I was not thrilled because it added some complication for me, who (at the time) did not use any streaming services.  However, the interface had a clear goal and it achieved that goal with few major glitches.

Earlier this year, DirecTV, presumably as an initiative of AT&T which acquired them just before we signed up (coincidentally), launched a massive interface redesign that completely changed the way customers used the DVR, and not for the better.  Unlike the TiVo redesign, this one was very poorly designed, and also poorly executed, resulting in the worst user interface in recent memory.

In this article, I critique many of the failures, large and small, with commentary about the UI principles violated and how we, as developers, can avoid making the same mistakes.

Quality Issues

The fundamental concern about any software is that it performs its function correctly and consistently.  It should be properly tested and prove robust before being presented to (or inflicted upon) the general public.  Generally, all issues are issues with quality, but there are some that are specific problems with quality assurance:

  1. The new interface was rolled out to customers before proper testing had been completed.  The sheer number of issues listed here is evidence of a massive failure of quality assurance.  Lesson: Always test your software completely before release.
  2. The interface locks up regularly, simply failing to respond in any way, requiring one to turn the DVR off and back to regain control.  This is what is known as a “showstopper” bug, one where a user cannot continue.  Lesson: Never, ever, ship a piece of software with a known showstopper.

Functionality Issues

The whole purpose of any software is to perform a function.  If software fails to perform that function correctly or completely (or at all), then the design and user experience are irrelevant.  Here are some of the issues with the DVR simply doing its basic job:

  1. Recordings arbitrarily start late or stop early.  The most fundamental purpose of a DVR is to record a program as requested, not only a portion thereof.  This happened with recordings both done in my absence and with live recordings I was actively watching (terminating the recording after only a minute or two).  Lesson: Be certain that your software actually performs its basic function before anything else.
  2. The DVR would get confused and schedule (and perform) simultaneous recordings of the same show, on the same channel, at exactly the same time.  Lesson: Do not release software that behaves illogically; it will annoy and confuse customers.
  3. Before the redesign, one could either record a series on any channel, or on a specific channel; this important functionality was removed, even though the interface still assumes that this is possible.  Now, every new series is scheduled for ‘All Channels’, which is especially problematic for heavily repeated or syndicated shows.  Lesson: Do not remove useful functionality in software upgrades.
  4. Arranging a list into priority order is just completely broken.  Changing the order of scheduled series causes the addition of duplicate entries into the list.  This is fundamentally terrible programming, the inability to handle a list properly.  Lesson: Do not hire programmers who cannot sort a list without making it grow.
  5. Using the back button to return to a previous page does not always work; instead, it often gets “stuck”, but one can do something like opening the list view, then go back multiple times to go back even further.  The full undo stack is there, just not working correctly and ending prematurely.  Lesson: Be sure to completely test new features.
  6. The ‘on demand’ functionality provides different (lesser) access on the DVR than from the mobile app.  The DVR will report that a show is not available, but then it can be immediately watched on an iPad without difficulty.  Lesson: Be consistent with content on multiple platforms; secondary platforms should not have better access.
  7. The “upgrade” did not fix the playback and compression issues; the recordings still get very blocky and almost unwatchable when there is motion on screen.  Lesson: Fix major problems with software before adding new features or other changes.
  8. The “upgrade” also did not fix the myriad audio issues, where sometimes a recording will start playing without any audio, or live recordings have an audio stutter, or a paused recording will make random pops and clicks.  Lesson: Test all aspects of a software interface, not only visuals.
  9. Now, however, the video also blanks out entirely for a second or two when playback speed is changed (for example, fast forward is started).  Lesson: Design a test plan that incorporates current and past bugs to prevent regressions and buggy releases.

Design Issues

Good software begins with good design, which is responsible for the entire user experience.  The user experience (UX) incorporates the aesthetics, flow, and interface, and generally works towards making the software easy to understand and use.  These are some design issues where the expressed intent works against the user:

  1. First and foremost, a design should be purposeful, providing a benefit to the end user.  This design change adds no value, yet makes the user learn a new way of doing things (at least, the things that can still be done).  Lesson: Never change a software interface just for the sake of being different; change must have a purpose.
  2. In 2018, the DVR still does not have any way to recover accidentally deleted recordings; if you accidentally delete a recording, it is gone forever.  (TiVo has had this functionality for more than a decade; Apple has used the concept for 35 years!)  Lesson: Always incorporate expected features first.
  3. In the play list, when multiple recordings of a program are put into a folder, the description chosen is that of the latest recording, which description is most likely to contain spoilers.  If one is getting ready to binge a season, the last thing one wants to see is something like, “In the aftermath of the death of <major character>…”  Lesson: Consider how the user is going to actually use your software in practice.
  4. Recordings of marked episodes show the latest (highest episode number) on top, but unmarked episodes (say, with only an episode name) are sorted to show the earliest recording on top, essentially the reverse order.  Lesson: Be consistent in presentation, even where the data may not be complete or consistent in format.
  5. Entries in the ‘to do’ list no longer show the number of upcoming recordings, so to get this useful information that used to be available at a glance, the user now needs to enter (then exit) the information screen for every entry.  Lesson: Provide important information immediately (at a glance) rather than requiring additional actions.
  6. Changing priorities in the ‘Series Manager’ now no longer has a move (drag) selector where the up or down arrows move the entry up or down in the list.  There are now separate up and down buttons which need to be pressed with the ‘select’ button.  This requires more effort and is far less intuitive.  Lesson: Never change an intuitive interface to require extra actions to perform identical functions.
  7. Because of the change in the move interface, one can no longer move an entry up or down by a page using the ‘page up’ and ‘page down’ buttons.  If you add a 100th entry, and you want the priority to be near the middle, you could have to press the select button 50 or more times to get it where you want it to be.  Lesson: Always test with a large data set simulating the real world, especially on interfaces that must scale.  Bonus lesson: Always eat your own dog food.
  8. The ‘manage’ option is no longer on the menu anymore; now it appears in the sidebar of the play list.  This is an illogical arrangement.  Every other selection on the sidebar has a (usually filtered) play list, so ‘manage’ does not belong.  Lesson: Be consistent when providing functionality at the same level (e.g., menu).
  9. The ‘to do’ list (and ‘series manager’) is buried under ‘manage’, rather than somewhere easier to access.  It is at the same level as ‘recording history’ and ‘purchases’, which are so rarely used as to border on pointless.  Lesson: Frequently accessed features should be more easily accessed than rarely used features.
  10. The DVR has a completely different behavior than the mobile app, which functions much better.  The platform dictates some differences between DVR and mobile app, but there is no design consistency between the two.  Lesson: All supported platforms for a software product should have consistent design and functionality.
  11. If a recording of a show appears directly in the play list, not in a folder, the episode name and number are not shown.  This is annoying for a scripted program with a description, but ridiculous when the episode name is the only relevant information.  Lesson: Provide the most important information, that which a user will most want to see, at a glance, and only require additional actions to access less important data.
  12. The ‘to do’ list, likewise, does not show the name or episode number for a scheduled recording, so you have to go to the information page just to see whether this is the desired episode.  Lesson: Consider the purpose for which a customer would be using a particular view to determine which information is important in that context.

Usability Issues

Even with a good user interface design, there can be implementation issues that adversely impact the usability of the software and ruin the user experience.  Here are some issues where the implementation detracts from the impression of the product:

  1. The interface has poor performance in general, always feeling sluggish and slow.  Lesson: The first rule of user experience is to make the software responsive.
  2. There is a delay when playing a recording before switching to display the video full screen, so if a program begins immediately, one needs to back it up.  Lesson: Where performance is poor or a wait is required, handle it gracefully; either indicate or mask or the situation with animation, sound, and/or other feedback.
  3. The selected recording on a play list shows an oversized listing (which looks like an ad banner) but removes the normal listing.  Lesson: Do not remove the fundamental item view when expanding to provide additional details.
  4. Pressing the ‘select’ button performs different functions depending on which list the user is viewing at the time, or which level of that list.  Lesson: Be consistent with the functionality of a button; do not use it for different purposes depending on context.
  5. The ‘play’ button on the remote does not play the selected recording at all levels.  Lesson: Do not disable logical (consistent) functionality on certain views.
  6. The ‘record’ button has different behaviors on different lists and different levels.  Lesson: Provide consistent behaviors for global buttons, regardless of view.
  7. Pressing the ‘record’ button in the information view for a recording in the ‘to do’ list removes the entire series, rather than only the episode.  Lesson 1: Do not have a button do more than the logical intent.  Lesson 2: Never allow a destructive behavior to be performed, without confirmation, on the press of a single button.
  8. Selecting the ‘move to top’ function in the series manager causes the selected program to move to the top of the list, but the selection then changes to the next item in the list.  Lesson: Do not change focus from a selected item unless necessary (i.e., the item was deleted); users expect the same item to remain highlighted.
  9. When duplicates exist in the series manager (a bug unto itself), moving a program up causes the selection to change.  Lesson: Again, do not change the item focus.
  10. The play list undo stack is not balanced; a user must press the ‘back’ (or left arrow) button twice after pressing the ‘list’ button to return to the original view.  Lesson: One action (forward) should require only one ‘back’ press to reverse.
  11. Pressing ‘back’ to return to a view where a (now) deleted recording was highlighted causes a completely incorrect recording to be highlighted, often within a folder (i.e., one level down), which is very confusing for the user.  Lesson: When a highlighted item is no longer available on reversing, select a logical default (e.g., first item).
  12. The current viewing position of a recording is lost, seemingly randomly.  Lesson: Do not lose, misplace, or corrupt user information; it frustrates customers.
  13. The choice of font size is too small for some users (as reported frequently online).  Lesson: Test font readability extensively and provide options for users with impaired eyesight, including colorblind users, if necessary.

Support Issues

Any significant software product is likely to have some bugs or usability issues.  Some issues are problems with the customer support provided by the company, such as these:

  1. This major “upgrade” was rolled out to the entire customer base very quickly.  Lesson: When making major user interface changes to a product, test properly with a smaller subset of users to avoid providing a substandard product to everybody.
  2. The many issues with the new version of the software were seemingly ignored.  Lesson: Always listen to customers and address issues as soon as possible.
  3. There is no way to refuse the upgrade, nor to revert to the older software.  Lesson: For a substantial product change, allow users control over when to upgrade and provide a method of reverting (as a failsafe).
  4. The customer support area is loaded with hundreds, probably thousands, of complaints about many of the above issues, but DirecTV/AT&T rarely ever address any of them, and never with anything positive, personalized, nor useful.  Lesson: Always, always, let your customers know that you have heard their complaints, that you appreciate their feedback, and how the issue can or will be addressed.

Conclusion

Frankly, we only signed up with DirecTV because it was the only choice at our location in Los Angeles, and after our experiences, we intend to never use them again.  (We have now cancelled the service, with some difficulty and annoyance.)

My message for AT&T is: If you want to actually improve your service, stop spending so much money on advertisements lying about DirecTV being better than cable and spend some on actually making it competitive.  Fire the DVR team and use your mobile app team instead.  Quit trying to make people bundle your mobile service with your television service, and give them a reasonable monthly price, like the one you offered us only after I was leaving the service.  (Offering a 50% discount to stay just pissed me off a lot more.)

And for the sake of everyone, dump that crappy contractor, Consolidated Smart Systems, who is making your company look even worse.  They do not have a one star rating on Google, Yelp, and with the Better Business Bureau for nothing. 🙁

Windows 8.1 Repaired

Microsoft fixes its touch interface bug reported previously.

Microsoft Windows 8.1About a month ago, I posted about a bug deep in the Windows 8.1 touch interface code, a problem which triggered an exception in our products.  I detailed the debugging process and verification that it was an error in Microsoft’s latest operating system.

Last week, Microsoft indirectly confirmed the bug by issuing an update, KB2919355, which fixes the reported problem.  With that update installed via Windows Update, our test system now works correctly with all of our unrevised games that had previously responded to a hardware (floating point) exception.  Of course, KB2919355 is a “cumulative update” comprised of more than 100 different fixes so, without unwarranted experimentation, I cannot be certain which one addresses our issue, though KB2927066: Ichitaro crashes when you use a touch screen to enter text in Windows 8.1 seems to be the most likely candidate.  (The description is very similar.)

Unfortunately, we still need to update all of our Windows products, since we cannot rely on customers applying the update (and, ironically, it actually fails to install on one of our development systems).  The fix comes too late, after Windows 8.1 was distributed to the public at large and, also, after I spent many hours debugging the problem.  Still, it is better that it was acknowledged and fixed (than denied and ignored).

A series of updates for Goodsol products will commence shortly.

Debugging Windows 8.1

There is bug deep in the Windows 8.1 touch interface code.

Over the past couple of weeks, I spent quite a lot of time trying to debug an issue that was causing crashes in our programs under Windows 8.1 and which, ultimately, turned out to be a bug in the touch interface code of the operating system.  The problem was not with our code; in fact, our tight programming practices actually outed Microsoft’s failure.

To see how we got to that point, read on…

Symptoms

Late last year, we got a single report of a crash in Pretty Good MahJongg under Windows, occurring only when the touch screen was being used; playing the game with mouse and keyboard worked fine.  Crashes in Pretty Good MahJongg are extremely rare, and this had all the earmarks of a device driver problem, so it was given fairly low priority.  Additionally, we did not have the necessary hardware (at that time) to reproduce the error.  Then we got a second report, and we knew something was amiss.

The first two bug reports were nearly identical, and the obvious commonality (and difference from our systems) was that both machines were running Windows 8.1 and, obviously, had touchscreens.  We had tested our products under Windows 8 when it was released, but did not check Windows 8.1 nor using a touch interface (which, in an ideal world, should not crash programs in any event).  Something appeared to be happening with either Windows 8.1 or the touchscreen interface, or both.

Specifically, the error being reported, in all cases, was “Floating point underflow“, though that actual text for the error comes directly from our exception handler, not the system.

Diagnosis

Our products have a very good crash logging system, so when I got the crash dumps, I discovered that the crashes did not appear to happen in program code at all and, in fact, some of them showed only system routines in the stack dump, while others were essentially the same, but with our message loop (but no other program code) in the stack.

My immediate thought was that the problem could be a message collision, knowing that the touch interface added some new Windows messages.  This could mean that either a touch message was triggering program (e.g., animation) code at an unexpected time, perhaps prior to initialization, or vice versa, with a program message causing driver code to be executed improperly.  This could potentially explain both stack conditions, although I would expect to see our program code elsewhere, but that was never the case.

The bigger problem, at first, was that all of the PGMJ message processing code was identical to that in the Goodsol Solitaire Engine, which drives Goodsol Solitaire 101, Most Popular Solitaire, and FreeCell Plus, as well as the code in Action Solitaire, and that most of the message loop is actually contained in a common library shared by all of these products.  After initial confirmation that all reports were for PGMJ, this concern was finally resolved when crash reports began to escalate, and they expanded to include the whole range of products.  At least the new reports fulfilled my expectation.

Of course, to get to the bottom of the problem, I needed to be able to reproduce the error myself, so I ordered a Windows 8.1 tablet (Dell Venue 8 Pro) for testing.  Fortunately, this tablet displayed the error, and I was able to determine a little bit more about the issue.  The crash happened immediately upon the very first touch within the program, whether clicking a button or simply selecting an edit box, though navigating the program with the virtual keyboard worked…  that is, right up until the first (virtual mouse) touch. 🙁

I built a version of PGMJ that moved custom messages elsewhere in the numbering space, but that made no difference at all.  I tried a couple of other brute force experiments, but nothing altered the crash behavior one bit, so I set up remote debugging on the device and began to debug the program properly.  Unfortunately, the debugger saw the stack in exactly the same way as our exception handler, so every crash was deep in system code and if any program routine was in the stack, it was only the message loop.  Still, our program was definitely and consistently crashing, which meant something was different.  The one major advantage of proper debugging, though, is that I got full symbols, so I was able to determine that the actual crash was happening in ‘ninput.dll‘.  But why?

Here you may imagine days of various attempts at debugging the root cause of the crashes, including “handling” certain messages rather than calling DefWindowProc(), doing the opposite and not processing any messages, and setting breakpoints all over the place and, mostly, being disappointed at how few triggered.  I finally narrowed down the issue to happening from a DialogProc() function within the common library when the (new) WM_GESTURENOTIFY message was posted.  That message is the result of the default processing of a WM_GESTURE message, so presumably handling that in some way would prevent the crash.  No dice.  There is a strange documentation conflict when WM_GESTURENOTIFY is sent to a dialog box, since, “This message should always be bubbled up using the DefWindowProc function.”  However, regarding DialogProc, “Although the dialog box procedure is similar to a window procedure, it must not call the DefWindowProc function to process unwanted messages.”  This gave me a bit of a combinatorial problem, too, but nothing seemed to have any effect on the crash.

Finally, frustrated, I regressed to pure shotgunning of the problem.  I knew that not all programs crashed when touched under Windows 8.1, but (all of) ours consistently did, albeit not in our code.  I added an early message box to demonstrate crashing before any of the dialog boxes or other interface features were shown, and then I began removing pieces of the initialization code.  Voila!  The issue revealed itself!

After removing some of the very first initialization code, executed prior to almost anything else being done, and seemingly entirely unrelated to interface code, the crashes disappeared (though, of course, the program no longer worked).  Methodically reducing the amount of code removed, I was able to determine that the crashes were triggered (but not caused) by three simple lines of code in the exception handler initialization.

Problem

Ultimately, the crash problem was a result of the following C++ code:

    unsigned flags = _controlfp ( 0, 0 );
    flags &= ~( _EM_INVALID | _EM_DENORMAL | _EM_ZERODIVIDE | _EM_OVERFLOW | _EM_UNDERFLOW );
    (void)_controlfp ( flags, MCW_EM );

This, very simply, enables floating point exceptions within the program, including the (now problematic) _EM_UNDERFLOW exception.  The purpose was to provide maximum checking for errors in our code, which is usually so clean that it squeaks.  We never imagined that it would catch errors in a released operating system.  For reference, the above code has been shipping for more than 9 years, to many thousands of customers (and potential customers), and never had any problem before Windows 8.1 arrived.

To be perfectly clear, the actual bug is in Windows 8.1, specifically within ‘ninput.dll’.  There is an error in that module, creating a floating point underflow exception, compounded by reliance on a particular floating point state, namely that the hardware underflow exception is (and remains) disabled.  This is a flaw in the operating system, even though the default floating point state and, therefore, most programs do not display symptoms.

Solution

The actual solution, of course, is to remove the above code, which is a workaround to avoid triggering the crashes.  The tradeoff is that our programs will no longer be quite as robust in detecting floating point errors, but as stated above, this checking has been in place for almost a decade without finding any problems in our code, so it should be fairly safe to remove at this point.

Note that removing these three lines of code is actually more than needs to be done to resolve the immediate problem (i.e., the underflow error exception), but enabling the other exceptions still provides additional places for the operating system to fail, perhaps even further along in the same processing path.  The fundamental problem is that Microsoft counted on the default floating point state (and never tested otherwise) for its latest touch interface code, so it is safest for us to simply revert to using the default state as well.

Verification

It is not enough to simply come up with a solution; that solution must be verified.  We approached this issue in two different ways.

First, I built a new beta version of Pretty Good MahJongg with the above solution applied, and that version was provided to as many of the PGMJ customers who reported a problem as feasible.  Every single one (who reported back) confirmed that the crashes were gone.

Second, we bought a brand new Ultrabook laptop with a touchscreen for testing on a different device.  The laptop shipped with Windows 8 (not 8.1), so it was perfect for conducting our verification tests.

I installed the shipping version of PGMJ (Pretty Good MahJongg 2.41) using nothing but the touch interface, and everything worked fine.  We tested several games and had no problems at all (n.b., under Windows 8).  Then, I upgraded the laptop to Windows 8.1 and confirmed that the crash detailed above happened in exactly the same manner and place when using the touchscreen, but the game was perfectly playable with the mouse and keyboard (until one forgot and touched the screen 🙂 ).  Finally, I installed the beta version of PGMJ with the workaround, and everything worked again; in fact, this is a great way to play the game, especially for a title designed without touchscreens in mind.

Given that we verified the solution using two different and separate processes, we are confident that the issue is resolved.  Indeed, Pretty Good MahJongg 2.5 will be released on March 25, so look for it, still the very best tile matching games available for Windows.

For those who know some of my background, the score now stands as follows:
Gregg Seelhoff 3 – Microsoft 0

MAS Preparation, Part 6

App Sandboxing implementation

In the previous installment of Preparing for Mac App Store Submission, I discussed the reasons and methods for implementating Mac App Store receipt validation within your project.  At this point in the series, you may have already completed all of the currently necessary and recommended changes for converting your Mac OS X game, available for direct download, into an application suitable for the Mac App Store (MAS); you may have even submitted it already.

This final part is a discussion of the latest imposition by Apple, a technology known as application sandboxing, which was introduced with Mac OS X 10.7 Lion.  The general concept of sandboxing is that an application simply runs in its is own “sandbox”, unable to do anything outside its own little piece of the file system, except in specific cases, for which it must have entitlements.  The idea is that a compromised program (via malware or bad programming) is limited in the amount of system damage that can be done.  This is the way that all iOS applications work, and this is, in fact, just a transference of that approach to OS X.  The problem, of course, is that desktop systems are (and should be) different than tablets and phones, so certain types of programs are left out, including those which interact with other programs, such as those which provide ease of access to people who need assistance using computers.  (This is a very poorly considered decision on Apple’s part.)

Frankly, the main reason that you need to understand application sandboxing is that, as of June 1, 2012, all new applications and significant updates submitted to the Mac App Store are required to be sandboxed.  (This deadline was recently pushed back from the original date of March 1 in light of a considerable number of issues that Apple is still having with their implementation.)  If you already have a MAS application (or submit one before the deadline), you will be allowed to apply bug fixes without adding sandboxing, but otherwise, you will need to have sandboxing implemented.  (At least, that is what they are saying right now.)

0. Understand sandboxing

The first step is to understand application sandboxing and the implications of using it for your game.  The definitive documentation for Apple’s implementation of this technology is App Sandbox Design Guide, which is recommended reading.

Essentially, by default, your application is contained entirely within its own (read-only) bundle and a directory named by your bundle identifier (e.g., ‘com.goodsol.com/PrettyGoodSolitaire’) under the ‘~/Library/Containers’ folder.  An internal technology called Powerbox regulates (reads: mostly disallows) any access outside these two folders.  Most access to hardware features and user information is also disallowed without a specific entitlement.

If your game is entirely self-contained, then sandboxing may not present much of a problem.  If you use certain technologies, such as submitting high scores to a web site via the Internet, then you will have to enable appropriate entitlements.  If you have more involved interactions, especially involving other applications (or helpers), you may be forced to redesign/eliminate those features, or else give up on sandboxing (and, hence, the Mac App Store).  Whatever the situation, you will need to determine how this requirement will affect your application.

In our case, we lost the capability to load custom card sets, since the default operation (in the downloadable version) is to read installed files from the ‘Application Support’ folder, though that was not a huge blow since MAS already killed the idea of downloading improvements to the game.  The other feature that is gone is the ability to explicitly load and save games (for now) because the current version of our games are still built with Carbon, which cannot navigate the sandbox/Powerbox.  (Our primary save game feature, AutoSave, still works perfectly fine.)

1. Identify possible sandboxing issues

The next step is to identify possible issues with sandboxing your application, so let’s look at three different categories of features…

What you CAN do:
  • You may read files from your application bundle, as usual.  (Bundles should always be considered read-only, so your code should never attempt to write here.)
  • You may read and write files within your sandbox folders; this process is transparent provided you are using system calls to obtain the correct user folders (and you should never hard-code folder paths, anyway).
  • You may update your application preferences (.plist) using a standard API.
  • You may provide a simple link to a web page; this is allowed by default entitlements (i.e., not considered an outbound connection).
What you CANNOT do:
  • You may not read from nor write to most locations outside your sandbox, including the user desktop and document folders, without the user explicitly selecting the location (and the appropriate entitlement set).
  • You may not use any Carbon navigation dialogs, nor any Cocoa navigation dialogs other than NSOpenPanel and NSSavePanel, and you may not simulate user input to those panels.
  • You may not use “incompatible” functions, including those included in the Authorization Services and Accessibility APIs.
  • You may not send Apple events to arbitrary applications, nor broadcast user-info dictionaries.
  • You may not load any kernel extensions.
  • You may not alter preferences of other applications, nor terminate other applications, including helper applications.
  • You may not sign future updates with a different certificate than the original; doing so will cause Mac OS X to deliberately crash the program.
What requires an entitlement:
  • You need an entitlement to allow outbound network sockets (i.e., for sending information to the internet, operating as a client).
  • You need an entitlement to allow inbound network connections (i.e., for acting as a server).
  • You need an entitlement to allow read or write access to folders or files selected explicitly by the user (via NSOpenPanel/NSSavePanel).
  • You need entitlements to access the user’s ‘Movies’, ‘Music’, and ‘Pictures’ folders.
  • You need entitlements to access camera, microphone, or Bluetooth devices.
  • You need entitlements to access serial, USB, or FireWire ports.
  • You need an entitlement to allow printing.
  • You need entitlements to access the user’s address book or calendars.
  • You need an entitlement to allow the use of Core Location for determining geographical location.

 

After perusing the notes above, write down a list of features that may (or should) fall afoul of application sandboxing and be sure to test each of these features to confirm that they are working prior to enabling sandboxing entitlements.

2. Enable default entitlements

The next step is to enable the default entitlements for sandboxing; essentially, this disables all restricted access types, which allows us to verify which features of the game really need entitlements, and which work without modification.

To do this in Xcode 4.2.1 (under Lion) is fairly simple: Just open the ‘Summary’ tab for your target settings, scroll down to the ‘Entitlements’ section, and check the ‘Enable Entitlements’ (top) checkbox, and then confirm that the ‘Enable App Sandboxing’ checkbox is also selected.  If you are not already using iCloud in your project, then you should make sure that the ‘iCloud Key-Value Store’ is unchecked and cleared, and that that ‘iCloud Containers’ table is empty.

What the first checkbox actually does is create an entitlements file, a property list with the root name from ‘Entitlements File’ (defaults to the project name) and ‘.entitlements’ as the extension (e.g., ‘Pretty Good Solitaire.entitlements’), and it adds that file to your project.  The other (app sandboxing) checkbox automatically adds the ‘com.apple.security.app-sandbox‘ key to the entitlements file, which sets the default/maximum restrictions.

In order to build with application sandboxing enabled, you must be code signing the project.  Clearly this is already the case for projects being submitted to the Mac App Store.  However, if you are only testing and/or do not have an appropriate certificate (such as in our case where, as a contractor, I do not have access to the distribution certificate), you can follow the instructions in the ‘Create a Code Signing Certificate for Testing‘ section of the App Sandbox Quick Start[Editor: Sorry for no direct link, but section links on that page are fuzzled.]  The next section, ‘Specify the Code Signing Identity’, explains how to make the new certificate work with your project.

3. Verify access failures

Your next step is to verify access failures with application sandboxing enabled in the project.  To do this, start by launching the Console utility (from ‘Utilities’) and clearing its display.  This will show log entries to confirm a service denied by sandboxing.

Now, work through the list of suspect features that you created earlier and retest every one of those features with the new signed/sandboxed build.  Failures are, of course, expected, and for every failure there should be a log entry in Console from “sandboxd” and usually containing the text “deny” and an indication of the restricted service.

Note each confirmed failure, and determine which (if any) of the entitlements listed on the target ‘Summary’ page is likely to resolve the issue.  Do not make any changes to the entitlements until all of the suspect features are tested and notated.  A complete list of available entitlements can be found on the Enabling App Sandbox page of the Entitlement Key Reference.  (Note that some entitlements may not be listed on the ‘Summary’ page, requiring direct editing of the entitlements property file.)

If there are any features that are confirmed to fail only under sandboxing and for which there is not an appropriate entitlement, you may want to check the App Sandbox Temporary Exception Entitlements page for a solution that, as the name suggests, may work for the time being, but will probably not be available in the future.  If none of the entitlements nor temporary exceptions will resolve the problem, then you may need to redesign or remove that feature and/or lobby Apple to add an entitlement for the particular action you are attempting.

4. Add necessary entitlements

The next step is adding the necessary entitlements to the project, which should be done as an iterative process.  For each entitlement that you anticipate requiring, enable that entitlement, rebuild the project, then test every feature that is confirmed to have failed with sandboxing previously, noting each one that now functions correctly.

The general idea is that one should minimize the number of entitlements included (requested?) in a sandboxed application.  If you add an entitlement that does not actually help any of your features function, then you should immediately remove that entitlement.  Using excessive entitlements, in addition to reducing the (dubious) security, is a potential cause for rejection from the Mac App Store.  (In other words, simply checking all of the boxes may work in testing, but not for MAS submission.)

When you have completed this process, you should have a reasonably small number of entitlement keys (perhaps even zero) enabled, and all of the features of your program should be functioning properly.  It is always a good idea to double-check all of the identified features, as well as do some additional testing, once the “final” list of entitlements is enabled.  If you still have features that do not work under sandboxing (only), and have no appropriate entitlements, then you may have a decision to make.

In our case, we only had to add the ‘com.apple.security.network.client’ entitlement key, in order to perform submissions to our online high score server, but then we had to make the hard decision to lose the ‘Open…’ and ‘Save…’ features from our store version (for reasons stated previously).

5. Create file migration rules

The penultimate step in the process is creating file migration rules which is simply a property information file that is used only once (on a system) to indicate which existing files should be moved (not copied) into the sandbox.  If you have a new project, without existing users, then you have the luxury of skipping the final two steps.

The rest of us need to create a new property list, named (exactly) “container-migration.plist“, and add it to the project.  This file contains a dictionary key, ‘Move‘, which contains string keys indicating the names of folders and/or individual files to be moved into the equivalent location within the sandbox (or array pairs of strings if you wanted to both move files into the sandbox and relocate them within the relative folder hierarchy, though I cannot think of a good reason why you would).

The specific documentation for the (simple) format of this migration file is in Migrating an App to a Sandbox (which read).  The relevant portion of our migration file is as follows:

<dict>
    <key>Move</key>
    <array>
        <string>${Library}/Pretty Good Solitaire</string>
    </array>
</dict>

This is all it takes to move the entire (unsandboxed) ‘~/Library/Pretty Good Solitaire’ folder into the equivalent sandboxed folder (the unwieldy ‘~/Library/Containers/com.goodsol.PrettyGoodSolitaire/Data/Library/Pretty Good Solitaire’).

Important: Note that the files are moved to the sandbox; they are not copied (and there is no documented process to only copy them).  The ramifications of this are that existing users switching to a sandboxed version will automatically have their files and settings moved for them, but those files and settings will no longer be available to any unsandboxed versions, nor to any other application that may have been accessing these files.

6. Test file migration

The final step is to test the file migration process to assure that your migration file works correctly.

On a system on which you have unsandboxed data for the application in question, manually duplicate the folder and/or files (in ‘Finder’), giving you a backup for additional testing (especially important should the migration fail).  Confirm that the sandbox container folder (i.e., ‘~/Library/Containers/<bundle_id>’) does not exist (and delete it and empty the trash if it does).

Build the application with sandboxing enabled and with the ‘container-migration.plist’ file included in the project.  Run the application.  A container folder should have been created and the specified files/folders moved into appropriate locations within the container’s ‘Data’ folder.

If you need to retest (or if you decide against sandboxing entirely), you can completely delete the container folder for your application (‘<bundle_id>’ and below, not the ‘Containers’ folder) and copy the duplicated data back to its original location.

Conclusion

Following the above process to enable application sandboxing in your game, after having updated your project based on the previous five parts, you should be completely ready to submit to the Mac App Store.  Go for it!  Just keep in mind that the process changes, reviewers vary, and if your project is accepted on its first submission, you are very lucky.  However, if you use the available tools to check your code and your project build, and follow the guidelines in this series of posts, your path should be smoother.

I sincerely hope that you have enjoyed, or at least benefited from, all of these posts.  Comments are definitely appreciated, as are links to this blog, good word of mouth, and US currency in both large and small denominations.

MAS Preparation, Part 5

Mac App Store receipt validation

In the last installment of Preparing for Mac App Store Submission, I discussed the (general) source code modifications that you may need to make in order to convert your downloadable Mac OS X project into one acceptable for the Mac App Store (MAS).

Now, in this fifth part, I will talk about a more specific addition to your source code, functionality for checking the presence and validity of a MAS receipt file.  While this change is not required, and would not cause your submission to be rejected, it is strongly recommend for reasons detailed below.  In fact, we will start there…

0. Consider the reason for receipt validation

Before implementation, it is good to understand, in general terms, the purpose of receipt validation.

When somebody “purchases” an application from the Mac App Store, a receipt file is added to the downloaded bundle authenticating it; this includes free products as well.  However, the level of checking provided by the system itself is minimal.  Soon after the MAS launch, it was discovered that there was a fairly simply method [details deliberately omitted] by which even a simple-minded software thief could circumvent the very basic protections and run stolen MAS applications.

The solution, of course, is to verify the validity of the software receipt for your application, which helps prevent “script kiddies” from easily stealing and distributing your products.  Of course, it is never quite as simple as that, especially since code signatures and encryption are inherently complex, and the program needs to validate a receipt file that has not been created yet (making this code rather tricky to test).

Keep in mind that this validation process is optional, so you can choose not to do it all, to fully validate the receipt file, or to implement only partial checks; the choice is up to you.

The official Apple documentation for this process is in Validating Mac App Store Receipts.

1. Obtain a sample receipt for testing

As mentioned above, your first challenge is that the receipt checking code needs to be able to validate a receipt file from a purchase that is to be made in the future.  To do this, you may want to get a sample receipt for testing.

This used to be fairly easy, as Apple provided a sample receipt, along with the associated validation data, to Mac developers.  Unfortunately, recently (as in, since this series began), the certification signature on the original sample receipt expired and, instead of providing a newer one, Apple has decided to remove the sample receipt entirely in favor of connecting to the iTunes servers to obtain one directly.  Whatever.

Now, the process involves getting the application to initially quit with an exit code of 173, which indicates a failed verification, which is supposed to trigger iTunes to prompt you for your (test) account credentials and then download a valid receipt and insert it into the application bundle, as described in Test During the Development Process.  In practice, this definitively does not work all of the time during development, and the prerequisites are not clear.

What is clear is that the application needs to be signed before Mac OS X (version 10.6.6 or higher) will request iTunes credentials, and that they need to be for a test account.  However, any old test account will apparently not do, and a failure to produce and/or download a receipt displays no error message (just no receipt and only a vague “returnCode:1” in the console).  It appears to be that the application needs to already be in iTunes Connect and that the test account must be associated with that application via the master company account.  Unfortunately, for the moment, this remains as an exercise for the reader.

Another (unproven) approach could be simply borrowing a receipt from any purchased application from the Mac App Store.  You will need to know the exact bundle identifier (fairly easy), version number (also easy), and computer GUID (not quite as easy), but you could use such a receipt exactly like the sample receipt previously provided by Apple.

Important: Do NOT include any receipt files in your project.  Receipts are specific to the application, version, and system, and they are generated and added by the Mac App Store/iTunes; including a receipt in your submission bundle will result in summary rejection of the application.

2. Locate the receipt file

The first step to take in your source code is to locate the receipt file for checking.  In the release version, intended for distribution, the location of the receipt is always within your application bundle at ‘Contents/_MASReceipt/receipt‘.

If you decide to use a sample receipt for testing, you will probably want to define its (different) location, the parameters expected from the sample receipt, and a definition to use for conditional compilation.  In our case, the debug versions define USE_SAMPLE_RECEIPT and the other values (from the old sample receipt) like so:

#ifdef APPSTORE
    #define RECEIPT         "Contents/_MASReceipt/receipt"

    #ifdef _DEBUG
        #define USE_SAMPLE_RECEIPT
    #endif

    #ifdef USE_SAMPLE_RECEIPT
        #define SAMPLE_RECEIPT  "/Users/Gregg/Desktop/receipt"
        #define SAMPLE_BUNDLE   "com.example.SampleApp"
        #define SAMPLE_VERSION  "1.0.2"
        #define SAMPLE_GUID     { 0x00, 0x17, 0xF2, 0xC4, 0xBC, 0xC0 }
    #endif
#endif

Note that we explicitly define different path variables (RECEIPT versus SAMPLE_RECEIPT) and only define the sample parameters when USE_SAMPLE_RECEIPT is defined.  This is additional protection against accidentally using a property intended only for testing in release code.  For the same reasons, we also include this check in the code before the main loop:

#ifndef _DEBUG
#ifdef USE_SAMPLE_RECEIPT
    ErrorMessage ( "Warning!  Sample receipt is being checked in release version." );
#endif
#endif

So, our first verification function is GetReceipt(), which returns the path of the (untouched) receipt file.  The routine obtains the path to the bundle and then generates the path directly to the receipt for later verification, adjusted appropriately if USE_SAMPLE_RECEIPT is defined.  Additionally, just for another sanity check, it also verifies that the actual bundle identifier and short version string match definitions within the code (so we can safely use the definitions later).

3. Check the signature

The next step in validating a receipt is to check the signature to verify that it is properly signed by Apple.  Sample code for doing this (as well as the next two steps) can be found in the Implementation Tips section of the Validating Mac App Store Receipts document, as well as via a nice web search.

Our second verification function is CheckSignature(), which returns a PKCS7* data pointer (and is essentially a fleshed out version of Listing 1-4 in the section linked above).  It opens the receipt file, validates the data types, adds the “Apple Root CA” certificate, and verifies the receipt signature (via PKCS7_verify), then cleans up everything except the receipt pointer, which is returned for use in the next function.

At this point, you know that the receipt file exists, it contains data, and its signature is valid.

4. Verify the receipt contents

The next step is validating a receipt is to verify the receipt contents and confirm that the receipt is intended for your application and the current version number.  The document referenced above gives more details about implementation, but the process is essentially stepping through the objects in the receipt and processing each one appropriately.

There are four types of objects within each receipt that are relevant to the validation process: bundle identifier, bundle version, opaque value, and hash.  In this step, you want to verify that the bundle identifier and bundle version match the hard-coded definitions for your project.  Note that matching against values retrieved from the bundle is not as safe, since the ‘Info.plist’ file could potentially be modified to match an invalid receipt.  Also, you will want to store the bundle identifier, opaque value, and hash object values for checking during the next step.

Our third verification function is VerifyData(), which accepts the PKCS7* data pointer returned by the previous function.  It loops through the objects in the receipt and processes them appropriately, failing if either bundle identifier or bundle version do not match our hard-coded values (IDENTIFIER and VERSION, or SAMPLE_BUNDLE/SAMPLE_VERSION), or if either of these objects is missing from the receipt.  Additionally, it saves the objects necessary for the next step.

At this point, you know that the receipt is intended specifically for the current version of your application.

5. Verify the system hash

The next (and, perhaps, final) step in validating a receipt is to verify the system hash to confirm that the receipt is intended for the particular system on which it is being launched.  This step prevents the outright copying of entire application bundles from one system to another.  Of course, on failure, users are presented with the opportunity to enter their iTunes account information to obtain a valid receipt automatically, so legitimate customers are protected.

The general process for this step involves two parts: obtaining the computer’s GUID and then computing the hash of the GUID, which is then compared to the hash value stored in the last step.  The sample code in the Apple documentation referenced above describes both steps sufficiently (in listings 1-3 and 1-7, respectively).

Our fourth verification function is VerifySystem(), which calls a CopyMacAddress() function to perform the first part of the process, then uses that information to check the system hash.  More specifically, the GUID returned is added to a digest, along with the opaque value and the bundle identifier, and an expected hash is calculated.  The routine verifies that the computed hash length is the same as that of hash and then checks that the contents are identical.

At this point in the process, you now know (to a reasonable degree) that the receipt file exists and is completely valid for that system running the current version of your application.

6. Take extra validation steps

If you are particularly concerned (or paranoid), you can take extra validation steps, such as verifying the certification authorities, double-checking the bundle signature, or (apparently) even sending a receipt back to the App Store for verification.  You can also take steps to obfuscate your code and make it harder for crackers to find and modify the application to circumvent your checks, and Apple provides some suggestions.

However, just taking the general steps documented here will probably eliminate 99% of the problem, and with robust coding, error checking, and testing, theft of the Mac App Store version of your game should be minimal.  (In fact, you will likely lose significantly more money to downward pricing pressures than to piracy, but that is another topic altogether…)

Conclusion

With the addition of receipt validation checking to your project source code, your application should not be subject to simple receipt spoofing, once accepted into the Mac App Store.  In the next (final) installment, Part 6: App Sandboxing implementation, I will discuss the process of preparing for and implementing application sandboxing, as required for the Mac App Store by June 1, 2012 (recently pushed back from March 1st).

MAS Preparation, Part 4

Source code modifications

In the previous installment of Preparing for Mac App Store Submission, I provided some guidelines for application data and resources that should be followed (where applicable) as you transform an existing Mac OS X game project into a project that can be successfully submitted to the Mac App Store (MAS).

In this fourth part, I will describe a number of modifications to the source code of your project that you may want to make to avoid unnecessary MAS rejections and, perhaps, improve your customer support and project maintenance.  These recommendations build on the theme of earlier posts, as most of these changes are due to Apple restrictions against anything having to do with downloading or external sales, including the use of registration codes.

As before, these suggestions are based solely on our experience with our products, so it could help to understand the basics of our sales system.  We have two separate downloadable builds for each Mac OS X product: one is a trial version, with limitations, and the other is a full version, which is unrestricted but requires a customer-specific registration code (as well as the hidden download location).  In order to better understand our customers, each web link in the program adds a very basic tracking code, so we know which version is being used; when online high scores are submitted, we include a hash of the registration code so the scores can be applied to the correct player account.

For this process, we have created a new store version, which is unrestricted (like the full version), includes available extra data (avoiding downloads), and cannot require a registration code.  (Instead, the Mac App Store automatically includes a “receipt” which should be checked, as discussed in the next installment.)  Here are the steps we took…

1. Store game data within user library folder

For the Mac App Store, game data that is not directly created by the user must be stored within the user library (not documents) folder.  Originally, we stored our saved games and statistics in ‘~/Documents/<project name>/<player>’, which made them easily accessible for our customers, but MAS guidelines (and an explicit rejection) required us to relocate such files to ‘~/Library/<project name>/<player>’, where they are less discoverable by users, especially since ‘~/Library’ is now hidden in Finder.  In our case, we actually relocated the files for all SKUs, including a function in the downloadable versions to automatically copy this game data to the new location, to make future customer support more manageable.

Note that, with application sandboxing (as discussed in the final installment), the actual location of the (sandboxed) user library is a significantly longer path name, and the main user documents folder is not accessible by default.  Obviously, the paths given above are illustrative only; one should never hard-code a path in project source code.

2. Remove all registration code via conditionals

Since any sort of registration code is forbidden, you should remove all registration code functionality via conditional compilation.  As part of our Project modifications earlier, we added an APPSTORE preprocessor variable to the store version target (only), which now allows us to remove sections of code with “#ifndef APPSTORE”/”#endif” sections.

In our case, every place in the code where the trial and full versions were different were already denoted with the preprocessor variable ‘DEMO’, so we only needed to look at each such section and decide if it should be like the full version (i.e., no limitations), which was the case most of the time, or like the trial version (i.e., no registration codes).

3. Add fixed internal MAS registration code

Because we used registration codes to differentiate between customers, we needed to add a fixed internal MAS registration code for all store version customers.  In order to replicate the previous functionality for online high score submissions, we needed a way to distinguish among different users without accessing any individually identifiable user information.  Alas, in the end, we had to provide a hash of some system information that was reasonably unique, and simply accept the possibility of a certain number of collisions.  (Thus far, MAS sales have not been high enough to get to levels where collisions are likely, though.)

Honestly, there is a possibility that information from the included MAS receipt could have provided a better (unique) customer identifier, but our implementation (above) was completed before receipt checking was considered.  In this case, that is left as an exercise for the reader (but comments on implementation are encouraged).

4. Update version tracking codes

For the store version, you should update any version tracking codes in your project, using conditional compilation (APPSTORE) where necessary.  This will allow any usage statistics to differentiate between downloadable and MAS customers, as well as from trial version users (i.e., potential customers).

In our case, we simply add a character to the end of link URLs (i.e., as part of a single GET variable) that makes this distinction, so we just added another legal value for MAS purchases.  Additionally, our shortened version numbers are slightly different so (savvy) players can identify the product played on the online high score pages (for example, on this FreeCell scores page, see the ‘Product’ column).

5. Remove or modify marketing screens

Consider whether you should modify or remove any marketing screens in your product.  If you have dialog boxes which have the occasional link to ordering pages or downloadable content, you will need to remove those links (and any referencing text); if you have dialogs that are explicitly for the purpose of marketing, it may make sense to remove them entirely instead.

In the case of our products, we include a fairly self-explanatory ‘Help Center‘ screen which not only contains links to the help files for the product, but also serves as a portal to all manner of support services, including sales (verboten), registration code entry (not allowed), customer support email (probably OK), and suggestions for other games (who knows?).  We found that removing the disallowed and questionable content from these pages made them almost pointless, so we instead replaced the entire dialog with a direct link to the help file contents page (within the bundle).

6. Remove links to any downloadable content

You must remove any links to downloadable content or risk the rejection of your product.  Note that these links (and, in truth, a number of other “offenses”) may not always be obvious on initial review, so your product can certainly get accepted once and then later rejected for something that was there in the first version; it has happened to us more than once.  (All Apple reviewers are not created equal.)

In the case of Pretty Good Solitaire, we have card sets which customers can freely download, plus a feature to check for and download updates, both of which are forbidden in the Mac App Store.  The following subsections describe the issues that we had to address to prevent suggesting that there was anything outside the MAS playground.

6a. Remove download links from main menus

We had to remove our ‘Download Additional Card Sets‘ menu option from both the main and game menus.

6b. Remove version checking from main menus

We had to remove our ‘Download Latest Version‘ menu option, which includes an implicit version check, from the both the main and game menus.

6c. Remove download links from submenus

We had to also remove a ‘Download Additional Card Sets‘ menu option from our submenu that players use to select the desired card set (a logical and convenient place to find it).

6d. Remove download links from buttons

Finally, we had a ‘Download Additional Card Sets‘ button within the program preferences, which also had to be removed (actually, hidden).

Note:  In all of these cases, rather than modify our user interface (.nib) files, and thus have to manage different sets of files, we added conditional code that simply removed the offending menu option or hid the button, keeping the source code (including .nib files) identical between downloadable and store versions (distinguished solely by the presence/absence of APPSTORE).

7. Review any use of the Application Support folder

Before submitting your application to MAS, carefully review your use of the Application Support folder (if any).  Specifically, you are allowed to create and (appropriately) use a folder at ‘~/Library/Application Support/<app-identifier>’, where <app-identifier> “can be your application’s bundle identifier, its name, or your company’s name.”

However, there is a twist:  “These strings must match what you provided through iTunes Connect for this application.”  This particular requirement is somewhat buried in the guidelines, and it is not always checked.

In our case, we inadvertently created but did not use (in the store version) a folder, ‘~/Library/Application Support/Goodsol’, which seems to fit the criteria.  However, it was supposed to end in ‘Goodsol Development’ to meet the letter of this requirement.  This “violation” was not discovered until our third or fourth submission, and our downloadable versions already used the folder extensively as named.  Fortunately, the store version did not actually use it (by virtue of including all downloadable content), so we just removed the spurious creation of an empty folder.  Problem solved (albeit with yet another rejection and delay).

Conclusion

With the above modifications to source code, plus perhaps a little detective work finding similar issues with any particular application, your project should be tentatively ready for submission to the Mac App Store.  In the next installment, Part 5: Mac App Store receipt validation, I will discuss the reasons that you should probably validate receipts in your shipping product and provide a few links to useful code and resources.

MAS Preparation, Part 3

Data and Resource guidelines

In the previous installment of Preparing for Mac App Store Submission, I detailed changes to the information property list that are necessary (or recommended) for a Mac OS X game project being converted to one suitable for MAS submission.

This third part deals with practical implications of guidelines for project data and resources, in particular, artwork and help files.  These points are necessarily somewhat specific to the manner in which we implemented our products, but they should give you some idea of items to look out for, and as always, comments on your experiences are encouraged.

Now, please review all of the artwork and other (non-code) resources in your project with these points in mind…

1. Include a 512×512 image in the application icon

The Mac App Store requires a 512×512 image in the application icon file.  Although this is generally overkill for an application, it is necessary for MAS submissions, where that image size is (presumably) used for presenting your product to users.

We actually produce our Macintosh (.icns) icons under Windows using Axialis IconWorkshop, which handles the required sizes without problem; we recommend this tool for processing icons for multiple platforms.  (We have no affiliation with Axialis, except as a satisfied customer.)

2. Update branding artwork

Although it seems (and truly is) silly, you need to review your branding artwork and update anything that even hints that there is another way to obtain software than through the Mac App Store.

Herein lies a theme: Apple hates downloads; Apple hates external sales.  Any suggestion that a customer may download or purchase anything outside of MAS will result in the rejection of your submission.  (Trust me, we had this thrown in our face, via rejection, more than once.)

In our case, we had a static bitmap used as a splash screen.  It included the (innocuous) line, “Download the latest version of Pretty Good Solitaire from http://goodsol.com“; it was hardly noticeable, and the web address was not even a hot link.  Rejection.  We had to remove that text before resubmitting (and we also removed the line with our support email address, just in case).

3. Add any extra product data desired

Continuing with the theme, you will need to add any extra product data that you may want customers to have, especially any data previously available via download.  In our case, we have (20) extra card sets that are available for Pretty Good Solitaire customers to download (and, likewise, extra tile sets and custom layouts for Pretty Good MahJongg), so these needed to be added to the main bundle to avoid Apple’s allergy to downloading.

The one minor advantage of this, of course, is that Apple has to bear the full download bandwidth burden.  (Card set artwork is not small.)

4. Remove trial version resources from the project

If appropriate, remove any resources used only in trial versions of your product.  In our case, we have an exit screen for trial users, as well as a separate interface (nib) file with all dialogs displayed (only) in the trial version, so we removed these.  (There is no sense including unused files, especially if they could give Apple more places to find reasons to reject.)

Similarly, if you have an install bitmap in your bundle, as I recommended in Making Mac Disk Images Pretty, you may as well remove that, too (since there is no disk image packaging to be done).

5. Remove any ‘How to Order’ section from help files

Review your help files and remove any pages that discuss verboten topics, such as downloading, registering, or (especially) purchasing the product.  We have an entire section called “How to Order”, dealing with all of those topics, that is just removed wholesale for the MAS submission (but remains in our downloadable versions); it was the cause for a rejection.

Note that Apple does not reject for simple web links back to a product site, which itself may have loads of sales and download information, but you will want to be careful about the surrounding text.

6. Remove text referring to downloading or sales

Building from the previous guideline, after removing entire pages about problematic topics, check other help topics and remove any text referring to downloads or sales, even indirectly.

In our case, we have rule pages for our bonus games, which are provided in the full (purchased) version, and we removed the line, “Note: This game is only available in the full version of Pretty Good Solitaire.”  Elsewhere, we also replaced “the full version” with “this version”.

Conclusion

Using the above guidelines, you should be able to make sure that your project does not include any content that Apple may find “objectionable”, giving them cause to reject the submission.  In the next installment, Part 4: Source code modifications, I will discuss the numerous (small) changes that may be necessary in your project source code.

MAS Preparation, Part 2

Property List (Info.plist) changes

In the last installment of Preparing for Mac App Store Submission, I discussed the project modifications that are necessary (or recommended) for converting an existing Mac OS X project to one suitable for MAS submission.

This second part describes the changes to the information property list for your application that you should make for successful submission and to eschew rejections for simple issues.  As before, comments about any other issues or different experiences are certainly welcome.

Open the application information property list, usually named ‘Info.plist‘, in your project and follow these steps…

1. Update the bundle version format

First, update the format of the ‘Bundle version’ entry (CFBundleVersion) to contain exactly 3 period-separated integers representing the version number (e.g., “1.01.1“).  It cannot contain alphabetic characters (and although some documentation suggests that it may contain more or fewer integers, we did not take that chance).

For non-MAS applications, the format of this field was not enforced and, in fact, the default ‘About’ box encouraged the use of this field as a standard version description (alphanumeric string) by directly displaying it underneath the application name.  We used a format like, “1.01 (January 2012)”, which is more useful and aesthetically pleasing, but this was cause for rejection.

2. Add an application category

If you do not have one already, you will need to add an ‘Application Category’ entry (LSApplicationCategoryType).  The easiest way to set this value (in Xcode 4) is to select the ‘Summary’ tab for your target and select the appropriate setting in the ‘Application Category’ box.  In the case of Pretty Good Solitaire, we chose ‘Games – Card Games’ (public.app-category.card-games).

Note that Xcode 3 did not recognize this key, so it was necessary to explicitly add it, along with the appropriate category value, as found in the Information Property List Key Reference; fortunately, this is no longer necessary.

3. Set the minimum system version

Next, set the ‘Minimum system version’ entry (LSMinimumSystemVersion) to “10.6.6”, or higher if appropriate.

The Mac App Store does not work on versions of Mac OS X prior to 10.6.6 anyway, and leaving this at a lower setting (even if your downloadable versions support Leopard, Tiger, or even an earlier OS) may be cause for a rejection.

4. Review the supported document types

Finally, review the supported ‘Document types’ entry (CFBundleDocumentTypes), if any.  Remove any document types that will not be supported in your store version.

In our case, we supported a document type for saved games, which is still useful, but also a document type for automatic installation of extra card sets (which can be downloaded), or in the case of Pretty Good MahJongg, types for both extra tile sets and tile matching layouts.  Since downloading or installing any improvement to your application (outside of the Mac App Store) is verboten, we needed to remove this support.

Note that this initially passed muster with our first product, but it was cause for a rejection later on a different product using essentially identical functionality.  There is clearly some subjectivity to the application reviews, so a more thorough (or nitpicky) reviewer may find something previously allowed.  When a rejection can set your release schedule back a couple of weeks or more, it is not worth the risk.

Conclusion

With just these few changes to your information property list, your project should have the necessary application information for submission.  In the next installment, Part 3: Data and Resource guidelines, I will describe the issues you may encounter with your application data.