TweetFollow Us on Twitter

Diet For Fats
Volume Number:11
Issue Number:10
Column Tag:Powering Up

A Diet For Your Fat Applications

How to create fat applications that can strip their own unneeded code.

By Blake Ward, Ph.D., Idaho Falls, Idaho

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

With well over a million Power Macintoshes sold, and no sign of slowing sales, it has become critical to provide a PowerPC-native version of every application you write. However, with a huge installed base of 680x0-based Macintoshes, you’ll also need to ship a 680x0-native version of your application for the foreseeable future. This presents a problem for the developer - how do you give customers a convenient choice between the two versions without forcing them to live with the disk space overhead of both.

Large complex commercial applications that require multiple floppies and an installer can include separate 680x0 and PowerPC-native versions on the floppies and automatically install the proper version. This is a relatively clean solution that avoids the disk space overhead of providing both versions, but it still has it’s drawbacks. If they are installing your application on an external hard disk, this approach can lead to confusion and frustration if they move that hard disk to another Macintosh and discover that your application either runs very slowly, or worse yet will not run at all. The installer-based solution is also not always ideal for shareware or freeware applications. Since these applications are normally sold for little or nothing and are distributed over the Internet, the added complexity, overhead and cost of an installer may not be an option.

One simple solution to the problem of mismatched machines and programs is the “fat application”. A fat application is a single application file that includes both 680x0 and PowerPC-native versions of the application. When you launch a fat application, the system figures out which version of the application to execute. Fat applications are convenient for the end user since they don’t have to understand or worry about the type of Macintosh they have - the application will do the right thing. Fat applications do have one serious drawback though. Users with only one type of Macintosh and no plans to buy the other pay a permanent disk space penalty for the much larger file that essentially contains two copies of the application. There are simple utilities that will strip the unneeded code from an application, but most users aren’t likely to have them, and you certainly don’t want to ship another utility with your product. The ideal solution would really be a fat application that knows how to strip out its own unneeded code...

This article describes a simple technique for creating self-stripping fat applications. Using the supplied source code (SlimApp), in a few hours you’ll be able to modify your fat application so users may click a button and strip off the code unneeded for the Macintosh they’re running on. The solution described here works equally well whether you’re writing in C or C++ and whether you’re using no framework or a framework like MacApp, PowerPlant or Sprocket. I’ve even included a sample fat application and project files for CodeWarrior to show you how it’s done and suggest a friendly user interface for this new feature. It should also work with little or no change in other development environments.

Fat Binaries

Before jumping into an explanation of how we’ll strip a fat application, I’ll begin with a begin with a brief description of what a fat application looks like on the inside. If you’re an old pro with fat applications, just skip ahead to the next section.

Figure 1 shows the organization of a traditional Macintosh application. The application file has two or more ‘CODE’ resources containing the 680x0 instructions for the application, an assortment of ‘DLOG’, ‘MENU’, etc. resources, a ‘SIZE’ resource and no data fork. Depending on the development environment that created the application, it might also contain a ‘DATA’ resource (that holds initial values for the application’s globals). Even if your application contains only one segment, there will still be two ‘CODE’ resources since the first one (ID = 0) is actually the jump table for the application.

Figure 1. A Typical 680x0 Application

A typical PowerPC-native application has the organization shown in Figure 2. The same ‘DLOG’, ‘MENU’, etc. resources are present, but there is also a ‘cfrg’ resource and there are no ‘CODE’ resources. The actual PowerPC instructions are stored in the data fork of the file. The important thing to notice is that the executable code in each version of the application is stored in a different location, but non-code resources are identical in both PowerPC and 680x0 versions. So we can create a fat application by essentially just merging the 680x0 and PowerPC versions of the application.

Figure 2. A Typical PowerPC-Native Application

When your application launches, the System can take advantage of the fact that the two code types are stored in separate locations. If your application is launched on a 680x0-based Macintosh that knows nothing at all about fat applications, it works the same as it always did - the data fork and extra resources are simply ignored. When your application is launched on a Power Macintosh, the new Process Manager on these systems first looks for a ‘cfrg’ resource. If one is present, it is used to find and load the PowerPC instructions from the data fork, and the old ‘CODE’ resources are simply ignored. If no ‘cfrg’ resource is present, then the Process Manager just falls back on the old way of doing things, looks for the necessary ‘CODE’ resources and runs the 680x0 code in them using the Power Macintosh’s built-in 68LC040 emulator. The ‘DATA’ resource (if present) is only used by the 680x0 version of your application, the PowerPC-native version uses the Code Fragment Manager which stores each code fragment’s globals within the fragment.

Stripping Unneeded Code

Given the fat application organization just described, the process of stripping away unnecessary code to reduce an application’s file size is fairly obvious:

• If the application will be used on a 680x0-based Macintosh, we can safely eliminate the data fork of the application file since the old Process Manager doesn’t even expect it to be there. The ‘cfrg’ resource is also no longer needed. In fact, since the stripped application could be run on a Power Macintosh some time in the future, we have to get rid of the ‘cfrg’ resource or the new Process Manager will see it and assume that there’s some PowerPC code in the empty data fork.

• If the application will be used on a Power Macintosh, the ‘CODE’ resources and the ‘DATA’ resource are going to be ignored, so we can safely eliminate them. Unfortunately, after removing the ‘CODE’ resources, we end up with an application that will only run on a Power Macintosh. If it is ever moved to 680x0-base Macintosh and launched, the Finder will report a resource not found error! Since this isn’t very user friendly, we will replace the application’s ‘CODE’ resources with a tiny stub application that will warn the user that they have the wrong version and quit gracefully.

From this description, it’s obvious how a utility to strip unnecessary code from an application would be written. It’s a little less obvious how we write an application that can strip out its own unneeded code. However, with one simple trick, conditional compilation, we can actually implement internal code stripping without worrying about yanking running code out from under ourselves, and even without having to explicitly figure out which processor we’re running on:

// Note that for the sake of brevity all of the error checking and some of the
// setup code and comments have been removed from the listings in this
// article.  See the file “SlimApp.c” for all the details...

OSErr StripFatApplication(void)
{
 OSErr err;
   short int currResFork, applicationResourceFork;

    // Save away the current resource fork, make the application’s
    // resource fork current 
 currResFork = CurResFile();
 applicationResourceFork = GetApplicationResourceFork();
 UseResFile(applicationResourceFork);

    // Get the application’s file name
    // Removed for brevity...

    // Strip away the unneeded code
 err = StripUnneededCode(applicationResourceFork,              
 appFileVRefNum, appFileDirID, appFileName);

    // If we successfully stripped the unneeded code, we also want to try to
    // change the application’s name and it’s long version string so that
    // the user can tell months from now which version he/she has.
 if (err == noErr)
 RenameSlimApplication(appFileVRefNum, appFileDirID,
  appFileName);

 UseResFile(currResFork);

 return err;
}


#ifdef powerc

// This version of the function will only be compiled into the PowerPC version
// of the application.  Therefore if this PowerPC code is running we can safely
// remove the 680x0 code since it can’t possibly be in use.

OSErr StripUnneededCode(short int appResFork,
 short int /*appVRefNum*/, short int /*appDirID*/,
    StringPtr /*appFileName*/)
{
 OSErr err;
 short int n;
 Handle resourceHandle;

    // Remove all of the ‘CODE’ resources from the application
 n = Count1Resources('CODE');
 SetResLoad(false);
 for (; n > 0; n--) {
 resourceHandle = Get1IndResource('CODE', 1);
    // Code resources start out protected, so we have to clear the
    // protected flag before they can be removed
 SetResAttrs(resourceHandle,
 GetResAttrs(resourceHandle) & ~resProtected);
 RemoveResource(resourceHandle);
 DisposeHandle(resourceHandle);
 }

    // Do the same for the DATA resource if it exists
 resourceHandle = Get1Resource('DATA', 0);
 if ((err = ResError()) == noErr && resourceHandle) {
 SetResAttrs(resourceHandle,
 GetResAttrs(resourceHandle) & ~resProtected);
 RemoveResource(resourceHandle);
 DisposeHandle(resourceHandle);
 }
 SetResLoad(true);
 
    // OK, now we want to move our tiny 68K stub into place so that this
    // application will still run long enough to warn the user if ever moved to
    // a 68K machine.
    // It consists of two code resources and a new DATA resource that we stored
    // using different resource types in the SlimApp.rsrc file.
 resourceHandle = Get1Resource(kStubCODEType, kStubCodeID);
 SetResAttrs(resourceHandle,
 GetResAttrs(resourceHandle) & ~resProtected);
 RemoveResource(resourceHandle);
 AddResource(resourceHandle,'CODE',0,"\p");
 WriteResource(resourceHandle);
 ReleaseResource(resourceHandle);

 resourceHandle = Get1Resource(kStubCODEType, kStubCodeID + 1);
 SetResAttrs(resourceHandle,
 GetResAttrs(resourceHandle) & ~resProtected);
 RemoveResource(resourceHandle);
 AddResource(resourceHandle,'CODE',1,"\p");
 WriteResource(resourceHandle);
 ReleaseResource(resourceHandle);

    // Move our DATA resource that goes with the code resources we just moved
 resourceHandle = Get1Resource(kStubDATAType, kStubDataID);
 SetResAttrs(resourceHandle,
 GetResAttrs(resourceHandle) & ~resProtected);
 RemoveResource(resourceHandle);
 AddResource(resourceHandle,'DATA',0,"\p");
 WriteResource(resourceHandle);
 ReleaseResource(resourceHandle);

    // Write all of the changes
 UpdateResFile(appResFork);

 return noErr;

}

#else

// This version of the function will only be compiled into the 680x0 version
// of the application.  Therefore if this 680x0 code is running we can safely
// remove the data fork and ‘cfrg’ resources since they can’t possibly be in use.

OSErr StripUnneededCode(short int appResFork,
 short int appVRefNum, short int appDirID,
 StringPtr appFileName)
{

 OSErr err;
 short int n, refNum;
 Handle resourceHandle;

    // First, remove any ‘cfrg’ resources in the application resource fork
    // If we don’t get rid of these and someone runs the application on a
    // PowerPC, the finder will think there’s native PowerPC code available
    // and won’t emulate the 68K version.  There should be only one, but
    // let’s be general.
 n = Count1Resources('cfrg');
 SetResLoad(false);
 for (; n > 0; n--) {
 resourceHandle = Get1IndResource('cfrg', n);
 RemoveResource(resourceHandle);
 DisposeHandle(resourceHandle);
 }

    // Since we’ve just stripped the PowerPC version of the application, we know
    // that they’ll never be able to strip the 68K version, so there’s no need to keep
    // around the stub code.  Therefore, we’ll make the app a little smaller by
    // removing it too.
 resourceHandle = Get1Resource(kStubCODEType, kStubCodeID);
 SetResAttrs(resourceHandle,
 GetResAttrs(resourceHandle) & ~resProtected);
 RemoveResource(resourceHandle);
 DisposeHandle(resourceHandle);

 resourceHandle = Get1Resource(kStubCODEType, kStubCodeID + 1);
 SetResAttrs(resourceHandle,
 GetResAttrs(resourceHandle) & ~resProtected);
 RemoveResource(resourceHandle);
 DisposeHandle(resourceHandle);

 resourceHandle = Get1Resource(kStubDATAType, kStubDataID);
 SetResAttrs(resourceHandle,
 GetResAttrs(resourceHandle) & ~resProtected);
 RemoveResource(resourceHandle);
 DisposeHandle(resourceHandle);

 SetResLoad(true);

    // Write the changes
 UpdateResFile(appResFork);

    // Now we have to remove the actual PowerPC code.
    // Open the data fork (which contains all of the PPC code)
 err = HOpen(appVRefNum, appDirID, appFileName,
 fsRdWrPerm, &refNum);

    // And eliminate the whole data fork
 err = SetEOF(refNum, 0);

 err = FSClose(refNum); 

 return noErr;

}

#endif

When you want to strip unneeded code from your application, just call StripFatApplication(). It gets references to the application’s resource fork and the application file and then calls StripUnneededCode() with those values. The source code actually contains two versions of StripUnneededCode(), one is conditionally compiled into the 680x0 version of your application, the other is conditionally compiled into the PowerPC version. By using conditional compilation to select what we strip from the application, we don’t have to try to figure out which processor we’re running on. Each version of StripUnneededCode() just uses standard Resource Manager calls to remove the code that would be used by the other version of the application.

In addition to removing all of the ‘CODE’ and ‘DATA’ resources from the application, the PowerPC version of StripUnneededCode() also moves three small resources to take their place. These resources are provided with the source for this article, but you can also create them yourself. You can create resources for a startup stub by simply building a separate minimal “application” that does nothing but initialize the Toolbox, put up a warning alert and then quit:

void main(void)
{

    // Initialize Toolbox Managers so we can get the alert up
 InitGraf(&qd.thePort);
 InitFonts();
 InitWindows();
 InitMenus();
 TEInit();
 InitDialogs(nil);
 InitCursor();
 
    // Warn the user that this version of the application only runs on a
    // Power Macintosh.  You can customize this alert to list a phone number
    // for your company so that the user can inquire about getting a replacement
    // unstripped copy of the application.
 StopAlert(kNo68KCodeErrorDialog, 0L);

}

When the StartupStub project is built, its application file will contain two ‘CODE’ resources (the jump table and the main segment) and one ‘DATA’ resource that total just over 1K. These resources have already been moved into SlimApp.rsrc (which you need to include in your application project). Their types and IDs were changed (to ‘CoDe’ and ‘DaTa’) so that they wouldn’t conflict with the real resources of your application but would be available when the real 680x0 version of your application is stripped away.

The stripping process described above should be general enough to work with just about any application you might have. However, it doesn’t deal with stripping “fat resources” (for instance fat versions of custom WDEF’s, etc.) since they’re probably small enough that stripping won’t be worth the effort. If your application uses fat resources and you also want to strip them, you’ll have to add the appropriate functionality to StripUnneededCode(). The routine listed above also assumes that all code fragments are PowerPC code and it simply eliminates all ‘cfrg’ resources and the entire data fork. If you have an unusual application that uses the Code Fragment Manager for other types of code, you’ll have to make StripUnneededCode() a little more selective about what it deletes.

The User Interface

Since the concept of a “fat application” is a programmer notion that the average user will neither care about nor understand, it’s especially important that we put a friendly, easy-to-use interface.

The first step is to decide how to give the user access to this functionality. The sample application provided with the source code uses a button in it’s About Box. This seems more appropriate than a menu command or a preferences dialog since stripping away unneeded code is an unusual, one time action that can’t be undone. If we don’t have a fat version of the application, the button can be hidden. If visible, we can make it totally clear what the button will do, by setting its name depending on the machine we’re currently running on:

Figure 3. Sample User Interface

To pick a label for the button, just use same conditional compilation trick we used above to figure out which version to strip. You may also want to display a short message indicating which version of the application is running. The message will make everything clearer to your users and make it possible for your customer support people to ask users which version they’re running. The function Has68KPowerPCCode() checks to see if there are any ‘cfrg’ resources (in which case the application has PowerPC code) and if the StartupStub resources haven’t been moved (in which case it must still include a 68K version):

OSErr Has68KPowerPCCode(void)
{
 OSErr err;
 short int currResFork, applicationResourceFork;
 Boolean is68KApp, isPowerPCApp;
 
    // Keep track of the current resource fork so that we can restore everything
    // to its previous state when we’re done
 currResFork = CurResFile();
 applicationResourceFork = GetApplicationResourceFork();
 UseResFile(applicationResourceFork);
 
    // First, see if our replacement 68K stub code resources are still stored
    // under a different resource type.  .
 if (Count1Resources(kStubCODEType) == 2)
 is68KApp = true;
 else is68KApp = false;

    // Also see if there are any ‘cfrg’ resources in the application
 if (Count1Resources('cfrg') > 0)
 isPowerPCApp = true;
 else isPowerPCApp = false;

 UseResFile(currResFork);

 if (is68KApp && isPowerPCApp)
 return kFatBinaryApplication;
 else if (isPowerPCApp)
 return kPowerPCApplication;
  else return k68KApplication;

}

There are also situations in which we either won’t be able to strip out the unneeded code, or it wouldn’t be a such a good idea to strip out the code. For instance, if the application is currently on a locked volume, we won’t be successful. If the application is being run from a server, we might be able to change it depending on the user’s access permission, but we probably don’t want to since other users with different machine types might be planning to use the same copy of the application. You can call the function SafeToStrip() and only enable the button if it returns true:

Boolean SafeToStrip(void)
{
 OSErr err;

 FCBPBRec fcbParams;
 Str63 appFileName;

 HParamBlockRec params;
 CInfoPBRec pb;
 GetVolParmsInfoBuffer volParms;

    // Build a parameter block for an FCB info request.
 fcbParams.ioCompletion = nil;
 fcbParams.ioNamePtr = appFileName;
 fcbParams.ioFCBIndx = 0;
 fcbParams.ioRefNum = GetApplicationResourceFork();
 
    // First, check to see if the volume that contains the application is
    // currently locked.  If so, we won’t be able to change the application.
    // We get the volume’s vRefNum from the values returned by the FCB call.
 params.volumeParam.ioCompletion = nil;
 params.volumeParam.ioVRefNum = fcbParams.ioFCBVRefNum;
 params.volumeParam.ioVolIndex = 0;
 params.volumeParam.ioNamePtr = nil;
 err = PBHGetVInfo(&params, false);

    // Check the volume locked bits
 if (err != noErr || (params.volumeParam.ioVAtrb & 0x0080) != 0)
 return false;   // volume locked by hardware
 else if ((params.volumeParam.ioVAtrb & 0x8000) != 0)
 return false;   // volume locked by software

    // Is the file itself locked?
 pb.hFileInfo.ioNamePtr = appFileName;
 pb.hFileInfo.ioVRefNum = fcbParams.ioFCBVRefNum;
 pb.hFileInfo.ioDirID = fcbParams.ioFCBParID;
 pb.hFileInfo.ioFDirIndex = 0;
 err = PBGetCatInfoSync(&pb);
 if (err != noErr || (pb.hFileInfo.ioFlAttrib & 0x01) != 0)
 return false;

    // Get some general volume information to help us figure out whether we’re
    // running from a local volume or from a server.
 params.ioParam.ioCompletion = nil;
 params.ioParam.ioVRefNum = fcbParams.ioFCBVRefNum;
 params.ioParam.ioNamePtr = nil;
 params.ioParam.ioBuffer = (Ptr)&volParms;
 params.ioParam.ioReqCount = sizeof(GetVolParmsInfoBuffer);
 err = PBHGetVolParms(&params, false);
 if (err != noErr)
 return false;

    // If it’s a local volume, then there won’t be any server address
 if (volParms.vMServerAdr == 0)
 return true;

 return false;
 
}

The final step in providing a clear user interface is leaving behind some indication that the application has been stripped. It’s easy to install an application when you have only a 680x0 Macintosh, strip away the PowerPC version and then months later after buying a new Power Macintosh start to wonder whether the application was stripped or not. The SampleApp About Box helps by including a brief message indicating which version you have, but this requires that the user actually launch your application to find out. The StripFatApplication() function also calls RenameSlimApplication() to also provide feedback in two other optional ways. If the name of your shipped application file ends in “(Fat)”, it will remove the this suffix and if the long version string for your application (the one shown by the Finder’s Get Info command) contains the string “Fat Application”, it will be replaced by “Power Mac ONLY” or “680x0 Application” as appropriate. Of course, all of these strings (along with every other string used by SlimApp) are defined in resources in SlimApp.rsrc to make localization and customization easy. In particular, several of these strings contain the placeholder “<the application>“ that you’ll want to replace with the name of your application.

Putting Everything Together

Now that you’ve seen how the actual stripping takes place, all that remains is the easiest part - actually incorporating it into your fat application. To help illustrate this process, I’ve included a complete running sample fat application along with project files for CodeWarrior. If you’re building your fat application with CodeWarrior, you’ll likely have two projects. The first builds a 68K version of your application and probably looks something like the following:

Figure 4. Project Window for the 680x0 Version of SampleApp

To add self-stripping, we’ve added two files to the basic application: SlimApp.c and SlimApp.rsrc. After adding these files to your project, the only other thing required is to provide a user interface for the stripping feature. Just include SlimApp.h and call one or more of the following SlimApp functions from that code:

Has68KPowerPCCode()

Returns kFatBinaryApplication, kPowerPCApplication or k68KApplication.

SafeToStrip()

Returns true if the application file isn’t locked, or on a locked volume or server.

StripFatApplication()

Returns noErr if it was successful in stripping the unneeded code from the application.

The function DoAboutSampleApp() in the sample application illustrates the use of these functions and the issues mentioned in the User Interface section. Feel free to use any or all of it in your application.

When you’re building a fat application, be careful to make sure that none of your segments are preloaded. If they are, your 680x0 code will load at launch time and occupy valuable memory even when you’re running native on a Power Macintosh. (The stripping code also assumes that none of the resources are already in memory when it removes them.) If you’re using CodeWarrior, just double-click the segment name to bring up a dialog box and make sure that Preloaded isn’t set:

Figure 5. Setting the Segment Attributes

Once you’ve modified and built the 68K version of your fat application, you can move on to the fat version of your application. If you’re using CodeWarrior, you’ll have a PPC project that includes all of the source files for your application. However, instead of including your application’s resource files, it just includes the whole finished 680x0 version of the application:

Figure 6. Project Window for the Fat Version of SampleApp

This time you’ll only need to include the SlimApp source file - the SlimApp resources are already in 680x0 application file that you included. Build the PPC project and you’re finished. You’ve got a fat application that can strip its own unneeded code to become a “slim application”.

Conclusion

SlimApp was used in ToDo List (a slick to do list manager available in the archives on the Internet) where it received nothing but praise from users. New users who had one type of Macintosh, and worried about disk space, had no problem using it to reduce the size of their copy of the application. Perhaps more importantly, users who didn’t know or care about fat applications were free to ignore the whole issue - their copy of the application works fine on any Macintosh. Take a few hours and put your fat applications on a diet, your users will thank you.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Macs Fan Control 1.5.14 - Monitor and co...
Macs Fan Control allows you to monitor and control almost any aspect of your computer's fans, with support for controlling fan speed, temperature sensors pane, menu-bar icon, and autostart with... Read more
VueScan 9.7.96 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more
FileMaker Pro 19.6.1 - Quickly build cus...
FileMaker Pro is the tool you use to create a custom app. You also use FileMaker Pro to access your app on a computer. Start by importing data from a spreadsheet or using a built-in Starter app to... Read more
Duet 3.1.0.0 - Use your iPad as an exter...
Duet is the first app that allows you to use your iDevice as an extra display for your Mac using the Lightning or 30-pin cable. Note: This app requires a iOS companion app. Release notes were... Read more
Firefox 107.0.1 - Fast, safe Web browser...
Firefox offers a fast, safe Web browsing experience. Browse quickly, securely, and effortlessly. With its industry-leading features, Firefox is the choice of Web development professionals and casual... Read more
War Thunder 2.21.1.91 - Multiplayer war...
In War Thunder, aircraft, attack helicopters, ground forces and naval ships collaborate in realistic competitive battles. You can choose from over 1,500 vehicles and an extensive variety of combat... Read more
Numbers 12.2.1 - Apple's spreadshee...
With Apple Numbers, sophisticated spreadsheets are just the start. The whole sheet is your canvas. Just add dramatic interactive charts, tables, and images that paint a revealing picture of your data... Read more
DEVONthink Pro 3.8.7 - Knowledge base, i...
DEVONthink is DEVONtechnologies' document and information management solution. It supports a large variety of file formats and stores them in a database enhanced by artificial intelligence (AI). Many... Read more
Drive Genius 6.2.3 - $79.00
Drive Genius features a comprehensive Malware Scan. Automate your malware protection. Protect your investment from any threat. The Malware Scan is part of the automated DrivePulse utility. DrivePulse... Read more
VLC Media Player 3.0.18 - Popular multim...
VLC Media Player is a highly portable multimedia player for various audio and video formats (MPEG-1, MPEG-2, MPEG-4, DivX, MP3, OGG, ...) as well as DVDs, VCDs, and various streaming protocols. It... Read more

Latest Forum Discussions

See All

‘Genshin Impact’ Version 3.3 Pre-Install...
Following the reveal of the release date and more for Genshin Impact (Free) version 3.3 ‘All Senses Clear, All Existence Void’, HoYoverse showcased the Genius Invokation TCG that arrives this week in the update. | Read more »
TouchArcade Game of the Week: ‘Sling Min...
The world of PC games has always blown my mind because there’s just SO MUCH stuff out there that it’s not uncommon at all for there to be a game that’s well-liked and well-reviewed, and seemingly quite popular with a solid fanbase, and have it be... | Read more »
SwitchArcade Round-Up: Reviews Featuring...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for December 2nd, 2022. So, today turned out a little quieter than the usual Friday. It was so quiet, in fact, that I decided to pen a few reviews. The Knight Witch, Railbound, and Donut... | Read more »
Blue Archive reveals its latest event st...
Nexon has announced the new update for Blue Archive, under the name of An Unconcealed Heart. Featuring a battle between two academies, the story will follow a group struggling to gain recognition, and will bring three new students to recruit. [... | Read more »
Dead Cells+ Is Out Now on Apple Arcade a...
Following the major update for Dead Cells on iOS and Android a few days ago, Playdigious has brought Dead Cells+ () to Apple Arcade. As an App Store Great, Dead Cells+ includes all prior paid DLC and content updates. It also has exclusive mobile... | Read more »
SwitchArcade Round-Up: ‘Romancing SaGa’,...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for December 1st, 2022. Wow, December. We’re already at the last month of the year? Phew. I have a lot of work to finish in the next few weeks. As for today, we’ve got a little news, a... | Read more »
‘Railbound’ Update Now Available Adding...
One of our favorite puzzlers released this year is Railbound from Afterburn Games, which hit in early September and earned our Game of the Week recognition for being an absolutely ace logic puzzler. The goal is to place rail pieces down in order to... | Read more »
The Seven Deadly Sins: Grand Cross celeb...
Netmarble Corporation has pulled out all the stops to celebrate the 3 and a half year anniversary of The Seven Deadly Sins: Grand Cross. The Grand Cross 3.5th Year Anniversary the Ultimate One, a rather wordy title, brings with it a brand new... | Read more »
‘Skullgirls Mobile’ Major Update 5.2 Out...
Developer Hidden Variable pushed out a major update for Skullgirls Mobile (Free) a few hours ago. The version 5.2 update brings in Black Dahlia (before the console and PC game), Retakes, XP Treats, free gifts, and more. Since launch, Skullgirls... | Read more »
Out Now: ‘Disgaea 4’, ‘Romancing SaGa: M...
Each and every day new mobile games are hitting the App Store, and so each week we put together a big old list of all the best new releases of the past seven days. Back in the day the App Store would showcase the same games for a week, and then... | Read more »

Price Scanner via MacPrices.net

Holiday Sale: Apple AirPods Pro for only $199...
Amazon has new 2022 AirPods Pro in stock and on sale for $199.99 shipped as part of their Holiday sale. Their price is $50 off Apple’s MSRP, equaling their Black Friday price, and it’s the lowest... Read more
New Holiday Sale: Apple retailers are offerin...
Several Apple retailers lowered prices on 10.9″ iPad Airs overnight to lows of $100 off MSRP starting at $499. Their prices are the lowest available for iPad Airs anywhere this Holiday season right... Read more
New Holiday sale at Amazon: Take $50 off Appl...
Amazon has Apple’s new 10th-generation iPads in stock and on sale, for the first time, for $50 off MSRP starting at only $399. Their discount applies to all models and all colors. With the discount,... Read more
Holiday Sale: Get an 8.3″ Apple iPad mini for...
Sams Club has 10.9″ 64GB iPad minis on Holiday sale for $80-$100 off MSRP through December 7, 2022. With their discount, prices start at $399 — the cheapest price for a new iPad mini from any of the... Read more
Sams Club Holiday December Event sale: Apple...
Apple AirPods Max headphones are on sale at Sams Club for $110 off MSRP ($439) as part of their December Event sale, ending on December 7, 2022, valid for all colors. Sale price for online orders... Read more
Apple’s 10.2″ 64GB 9th-generation iPads are o...
Sams Club has 9th-generation 64GB iPads on Holiday sale for $60 off MSRP through December 7, 2022. With their discount, prices start at $259 — the cheapest price for a new iPad from any of the Apple... Read more
11″ 128GB WiFi M2 iPad Pro on sale for $749,...
B&H Photo has the new 11″ 128GB WiFi M2-powered iPad Pro (in Space Gray or Silver) on Holiday sale for $749 including free 1-2 day shipping to most US addresses. Their price is $50 off MSRP and... Read more
Find the best Holiday sale price on an iPad u...
We’ve updated our iPad Price Trackers with the latest information on the new 10th-generation iPads, M2-powered iPad Pros, M1 iPad Airs, iPad minis, and 9th generation iPads from Apple’s authorized... Read more
Apple retailers are offering $100-$150 Holida...
Apple retailers have posted their most-recent Holiday sale prices on 13″ MacBook Airs. Take up to $150 off MSRP on M2-powered Airs with these sales with prices starting at only $1099. Free shipping... Read more
Holiday Sale: Apple’s 14″ MacBook Pros with M...
B&H Photo is offering $200-$300 discounts on Apple’s 14″ MacBook Pros with M1 Pro CPUs as part of their Holiday 2022 sale, with prices starting at $1799. Free 1-2 day shipping is available to... Read more

Jobs Board

Support Technician II - *Apple* Support - O...
…problems and acting as a liaison between customers and resolving groups. As an Apple Technical Specialist, you will be supporting many of our popular Apple Read more
*Apple* Electronic Repair Technician - PlanI...
…a highly motivated individual to join our Production Department as an Apple Electronic Repair Technician. The computer repair technician will diagnose, assemble, Read more
Lead Developer - *Apple* tvOS - Rumble (Uni...
…earnings, and positive sentiment About the role: We are looking for a Lead Apple tvOS Developer to join our application engineering team to expand our video centric Read more
Tier 1 Endpoint Engineer - *Apple* - Red Ri...
…Desk on site, at our Client's location, with a focus on support to Apple products. This position will handle technical support requests directly from customers and Read more
Product Manager II - *Apple* - DISH (United...
…you will be doing We seek an ambitious, data-driven thinker to assist the Apple Product Development team as our new Retail Wireless division continues to grow and Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.