TweetFollow Us on Twitter

Rhapsody In Purple

Volume Number: 14 (1998)
Issue Number: 3
Column Tag: Rhapsody

Rhapsody in Purple

by Karl Kraft

An overview of the several ways to patch applications in Rhapsody, with various levels of bravery

Overview

Rhapsody may have an OS 8 look and feel, but under the hood it is running a completely different engine. While this new engine may sweep away some of our most beloved Mac items, like loadable "system" code in the form of INITs and CDEVs, their Rhapsody equivalents are by no means non-existent, or even difficult to implement. In fact modifications to the system and to applications under Rhapsody can be done in a clean and safe manner using high level constructs.

This article seeks to explain how a Rhapsody application comes to have a particular look and feel, and ultimately how to modify the look and feel of not only your applications, but all applications on the system. The overall effect of the source in the article is to change the background color of windows to an awful shade of purple.

How Programs Launch in Rhapsody

When a user thinks of an application, he usually thinks of a single software package, such as "ClarisWorks", which is actually a collection of templates, fonts, applications, help files, and other miscellaneous items.

When most developers thinks of an application, the target is more specific, usually a single binary image that contains executable code. Under Mac OS, this executable would also have a resource fork with various resources bound to the application.

Under Rhapsody, an application consists of many more parts, each of which is either executed or interpreted by the system as a whole. The most basic parts are:

  1. The executable file, which includes loading instructions for individual processor families, and basic information such as the application's icon. Under Rhapsody, this comes in a format called "mach-o".
  2. Resources in the form of images, property-lists, string files, and others.
  3. NeXT Interface Builder or "nib" files, that define the layout and connection of objects in the interface.
  4. Various frameworks and dynamic libraries supplied by Apple Computer, such as AppKit, Foundation and System.
  5. The Window Server, a separate program that handles all the drawing and PostScript imaging.
  6. The many utility and server programs that run on the machine such as AKServer (short for AppKit Server), a program that helps handle the pasteboard.
  7. The mach kernel and underlying operating system.

Together these pieces offer you, the Macintosh hacker, dozens of places where a patch can be applied both for your projects, and applications delivered in a compiled form.

Simple Non-Code Patches

Even if you aren't a programmer, you shouldn't have much trouble figuring out how to use interface builder to modify an application. This can be useful when a sysadmin type desires to remove functionality from an application, or perform interface butchery.

Using Viewer, the Rhapsody equivalent to Finder, select an application such as Terminal.app. For this example, pretend that you have have been ordered to prevent users from changing their preferences in this application. Note: Defaults can be changed in ways besides the preferences panel, but the type of people who would try this stunt would seldom think out the total impact of their interface butchery.

Start by making a copy of Terminal.app, and storing it in a safe place. You might need it to recover from your actions.

Terminal.app is actually a folder that contains the various resources and executable pieces of the Terminal application. Through the magic of Viewer, folders with the .app extension appear as single files. You can invoke stronger magic by selecting Terminal.app, and then picking "Open As Folder" from Viewer's File menu. This should reveal a folder with three items, a file called Terminal, which is the actual executable image, a folder with a header file for communicating with Terminal, and a folder called Resources. The Resources folder contains all the resources for the application, such as images, nib files, string files, and property lists.

Descending into the Resources folder, you should find a folder titled English.lproj. The lproj extension indicates a language project, which contains all the localizable items for this application. This is often just about every resource in the application. Even images are localizable, because the concept they represent can be conveyed differently in each language. An image of a dollar sign might be localized to a Yen symbol for the Japanese language project.

Inside English.lproj you strike paydirt, a nib file that is cleverly named Terminal.nib. You can open this nib file in interface builder by double clicking on the nib file icon.

Note: Terminal.app and all the resources grouped with it are not modifiable by a regular user. You need to be either logged in as root, or working on a copy of Terminal.app that belongs to you.

Using Interface Builder, you can navigate through the Terminal menu structure until you find the Preferences menu item. Since nib files contain not only interface objects but also their connections, you can discover what method is called when this menu item is clicked. Using the Inspector, and selecting connections, you should discover that it is connected to the File's Owner object, and calls the method preferences:.

Simply picking disconnect from the connections inspector will render this item impotent, leaving a boring menu item that does nothing. Perhaps a dash of purple will help spice up things . Using Interface Builder, you can add a panel to the application, with some nice purple text, that says "This item is disabled". You can see a sample of my panel in Figure 1.

Figure 1.

Hold down the control key, and drag a line from the Preferences menu item to the title bar of the newly created panel, and the connection inspector will appear. Set the connection to makeKeyAndOrderFront:.

Now save all your mischief and give it a try. If you did it right, picking Preferences should bring up your new preferences panel, and frighten the user from every picking an un-authorized menu item again.

You can edit just about any resource in this manner. Images, property lists, and other resources can be modified by simply using the appropriate editor. You can also use this technique to localize an application that has not been localized to a desired language by the developer. Simply copy English.lproj to Esperanto.lproj, and edit away. The changes you make in Esperanto.lproj will be seen by any user whose default language choice is set to Esperanto before English.

Color Me Purple All Over

What if you desire a more drastic change? What if you want all the windows in your application to have a purple background? One option would be to carefully check every time a window is created, and set its background color to purple.

In a small application that might work. A few windows means a few lines of code. However, I know of several OPENSTEP applications that have on the order of several hundred nib files, and hundreds of windows that can be created on the fly. Such a manual technique would be troublesome, because you would eventually miss a window, and its gray background would just look atrocious compared to all the glorious purple windows spread across the screen.

There is a better way, and in this example, you will convert TextEdit to produce purple panels and windows. While you read this next section, grab a copy of the source to TextEdit from /NextDeveloper/Examples/AppKit/ from the Rhapsody Developer Release, and start the project building.

One of the great advantages of Objective-C is the ability to completely override a class by a technique known as posing. In posing, a class (ie: PurpleWindow) assumes the identity of its superclass, such as NSWindow. In Figure 2 a simple class tree shows where NSPanel and PurpleWindow are both subclasses of NSWindow. After PurpleWindow begins posing as NSWindow, the class tree is changed to that of Figure 3.

Figure 2.

Figure 3.

By using this technique, you can not only subclass an existing class, but make the implementation of your class the default implementation for the super class and all it's subclasses. This saves you from the trouble and difficulty of having to write a separate subclass for NSWindow, NSPanel, and other windows that you may not even know exist.

To see this in action, use ProjectBuilder to create a new class called PurpleWindow. ProjectBuilder will create two files for you, an .m file (methods) and an .h file (headers). A quick look at the header shows that ProjectBuilder thinks PurpleWindow is a subclass of NSObject rather than NSWindow. Edit the header file to read:

#import <AppKit/AppKit.h>
implementation PurpleWindow:NSWindow
@end

Now the question is, how do you get PurpleWindows to have a purple background? If you think that the compiler should just figure it out from the prefix of "Purple", you are going to be sorely disappointed. You need to pick some method of NSWindow, override it, and then call a method to set the purple color. For starters, write the method that sets the background color to purple.

-(void)becomePurple;
{
  [self setBackgroundColor:[NSColor purpleColor]];
}

Now all that is left is to call becomePurple sometime early in the creation of a window, before it is displayed. A particularly tempting target is makeKeyAndOrderFront:. This method is called to put a window on screen, and make it the key window (the window which accepts keyboard events). So you should dutifully write:

-(void) makeKeyAndOrderFront:sender;
{
  [self becomePurple];
  [super makeKeyAndOrderFront:sender];
}

Now whenever a PurpleWindow receives the makeKeyAndOrderFront: message it should change its background color to purple, and then have NSWindow actually put the window on screen.

Note: makeKeyAndOrderFront: probably isn't the best place to actually make this patch. I picked it as an example because it is well known, quick and easy for developers to understand.

If you typed everything correctly, you should be able to compile your new modified TextEdit, and give it a test. The change should have absolutely no effect at this point. Simply creating a subclass of NSWindow does not make it automatically pose as NSWindow, nor does it cause all windows to become PurpleWindows. At this point, the only way you will get a PurpleWindow, is if you create one in InterfaceBuilder, or programmitcally.

To start the actual posing takes a single line:

[PurpleWindow poseAsClass:[NSWindow class]];

Your decision at this point is where to insert this line in your project. After you start the posing, all calls to NSWindow methods will instead be sent to PurpleWindow. Because of this, the ideal circumstance is to perform the posing before any NSWindow objects are created. This will guarantee that all NSWindow objects in your application are actually PurpleWindow objects.

The earliest point in the execution of your application would be the function main(), found in the file Edit_main.m. By making this line the first line in main(), it will take place before any objects are created. Inserting this early in main() produces:

#import <AppKit/NSApplication.h>
#import "PurpleWindow.h"

int main(int argc, const char *argv[]) {
  [PurpleWindow poseAsClass:[NSWindow class]];
 return NSApplicationMain(argc, argv);
}

Build the project again, run and test. You should find that document windows have no noticeable change. At first this may disappoint you, but the reason is that the white background of the document window comes from the NSText object that completely covers the background. You will need to look at windows with some background visible, such as the Info panel, the Preferences panel, and the Find panel. These should look like Figure 4.

Figure 4.

Posing can be a valuable tool, and a dangerous weapon. Careless and unnecessary use can make programs difficult to debug and create strange effects. If all you need is some purple windows for your application, you would probably be better off with just using a subclass. Reserve the power of posing for patching class that you lack source for, such as the Appkit, and always keep the posing down to a minimum.

Posing in a Friendly Executable

If you can pose in your application, can you pose in an application where the source is not available? To pose, you need:

  1. Your class to be linked into the target application.
  2. A call relatively early on, where you can engage the actual posing action.

Some applications will make this task easy for you. These "friendly" applications support the loading and linking of dynamic code in the form of bundles. Examples include ProjectBuilder, InterfaceBuilder, Viewer, and Mail. All of these applications load bundles in one form or another.

For the next example, the goal is to make windows in the ProjectBuilder application use the same PurpleWindow as we used in TextEdit, but this time I'll assume you don't have the complete source to ProjectBuilder or Yellow Box.

The application ProjectBuilder, supports the ability to load Bundles of code and resources than can receive a notification when a project is opened, closed, an other various useful things.

A bundle is much like an INIT in that it offers a section of executable code and resources like images, all packaged as a single unit. The biggest difference between these two is that an INIT contains code to patch the Macintosh System on startup, and a bundle contains loadable code for a single application. Another difference is that the loading of INITs happens automatically, provided that they are in the proper place. Bundles are usually only loaded by applications that explicitly look for them.

Bundles are created using the same tools, and a process very similar to building applications. You start by creating a new Project in Project Builder, but pick the type Bundle instead of Application. You can then add and edit classes to your bundle, build, and test.

For the building of ProjectBuilder bundles, the task is made a little easier by a preference template that can be found at http://www.ensuing.com/~karl/RhapHack/RhapBundle.html. This template is a ProjectBuilder bundle ready for use in ProjectBuilder, with a default interface and class that only needs a small portion of work to be useable. To start, copy the template project to some place convenient, like your home directory, and then open it in ProjectBuilder. This type of project does not have nay interface, so all the work is done in ProjectBuilder.

You now need to edit the basic class. As you can see there are several methods already created for the template that perform the basic task of grabbing information from the ProjectBuilder application and setting up the notification center to call methods on certain events. One of these methods is called setup:. According to the documentation, this method is called when the PBBundleHost first loads the bundle. This looks like a good place to start your PurpleWindow posing as NSWindow. Insert this simple code snippet in the method builderStarted:

+(void)builderStarted;
{
 [PurpleWindow poseAsClass:[NSWindow class]];
}

You will also need to add PurpleWindow.h to the list of headers imported by the PBTemplate class. Add this line at the top of the file PBTemplate.m.

#import "PurpleWindow.h"

Don't forget to add PurpleWindow to the list of classes to be compiled into this Bundle. You can add it to the project by selecting the classes suitcase in the current project, and then dragging PurpleWindow.m from the Workspace, or the PupleWindow application, to the classes suitcase.

If everything was done correctly, you should be able to build the application with no warnings or errors. If it worked, use the options button in the build panel to change the build type from "bundle" to "install". This will copy the bundle to ~/Library/ProjectBuilder.

Now that the bundle has been installed you can add it to the list of bundles that PBBundleHost will load by selecting Bundles in the Preferences panel of Project Builder. It won't be loaded right away though, you need to quit ProjectBuilder and relaunch in order for the bundle to be loaded.

When you launch ProjectBuilder, you should be able to bring up the Find panel and it should have a purple window as shown in Figure 5. If not, something is wrong. Check to make sure that the project was built and installed correctly, and that all the source code changes have been made to the bundle.

Figure 5.

If you play with this new creation for a while, you will notice a few important things related to the goals stated in the very beginning of this journey:

The first is that it works. It is possible to write code that significantly modifies an application's framework even if you do not have source for the framework, or the application. We were able to patch at the level of the high-level framework, rather than at a low-level machine trap.

It is a clean patch. The code doesn't have to patch a half-dozen points to constantly detect windows and convert the background to purple. And the patch only applied to the application that loaded it. It is easy to see that if the bundle was bogus, it would only affect the single application that loaded it.

Application or Bundle, the code was identical. PurpleWindow wasn't modified in this example at all. You merely copied it from a previous project. This is part of what made it such a clean patch. You can design new classes to augment the AppKit in an application, and then apply these classes to other applications.

The patch fell short in two very important goals:

The patch only applies after the bundle is loaded. In the case of ProjectBuilder, this was before any windows were created. However an application like Preferences does not load the executable code from the bundle until it is actually needed. A system wide PurpleWindow patch is not going to do much good if the user has to push a button in every app to get it going.

The patch only applies to ProjectBuilder, which is a "cooperative" application. You still don't have a way to get the executable code of the bundle linked into just any running application. Even the actual API for how a bundle interacts with an application is different for each application, so the bundle you have created works only in ProjectBuilder.

Both of these problems can be easily solved if programmers would band together and create a common API, and agree to have a common directory where bundles could be located. Similarly, world peace could be easily achieved if you just get the leaders of the world to agree on a few things. There are more developers than world leaders.

An Exploitable Link

This article started with a list of what makes up an application, and indicated that each of these was a target point for modification. So far you have modified the resources, interface files, and the executable itself in source code form. While each of these points has advantages and can help in the overall goal, they don't deliver the ideal hoped for: a patch to all applications that run on the system. If there is a way to make patch every application, it must lay in the other items, such as the frameworks, window server, server programs, or the kernel.

The kernel has facilities to allow loadable code and drivers, similar to the way that Preferences allows you to load bundles, and since every program links to the kernel, it seems like the ideal place to find the abilities required for this venture. However, developing source code that dynamically loads into the kernel takes a great deal of discipline, and debugging takes a great deal of patience and usually two machines. Also since code running in the kernel space can do anything, a much greater chance exists to trash your operating system beyond repair.

Server programs like AKServer primarily serve in interprocess communication between applications and in providing system resources. They have no facility for loadable code, which results in a more difficult patch. They are not going to do much to bring us closer to the goal.

With a little work, functions can be sent to the WindowServer, however they must be written in PostScript, and they tend to be limited to affecting how the drawing or windows are presented, such as changing the shape or color of the cursor.

This leaves frameworks, which also have no provision for loading code. However, there is a weak point in how frameworks are loaded that can be exploited.

How Frameworks Work

In the final step of compiling any Rhapsody program, the linker is passed a list of frameworks that the program requires for execution. This list is then embedded within a portion of the executable file. When a file is passed to the operating system for execution, it finds the needed frameworks, dynamically links them to the executable, and then actually executes the program. If a common framework was replaced with a framework that loaded bundles, it should be possible to make any application behave as a cooperative application like ProjectBuilder.

To find out what Frameworks are common, you can examine various applications using the AppEdit application, or the command line tool "otool". Casual examination shows that three frameworks are used by every application. These three frameworks are called System, Foundation, and Appkit, and are all located in /NextLibrary/Frameworks.

The System framework encompasses all basic operating system calls, such as read() and write(). If this framework was modified, it would affect not only applications but every executable file including command line tools and startup procedures. Patching this framework is possible, but an error could be difficult to recover from. Without a valid System.framework, your computer can not even start up.

The next possible framework is Foundation. While not used by as many command line programs, it is used by some, and therefore has many of the same problems as System.framework.

That leaves AppKit.framework, which is a good choice overall. There are no boot programs depending on it, and if you totally destroy the framework, it is possible to repair the damage by booting into single-user mode. Appkit.framework also make sense, as most patches would be targeted towards applications' look and feel. It also has another benefit missing from Foundation and System. To patch applications. you need to load bundles at some point early in the application's life. One of the very first objects instantiated in an application is the NSApplication class, and this class lives in Appkit.framework. Of course, it is possible to make the patches in the System or Foundation frameworks, just with a greater risk.

It would be possible to patch the actual binary code of the Appkit framework, however, such a low level patch is not needed. Instead you can move the Appkit framework to a new location, and then create a new "patch" framework. If the "patch" framework is placed where the operating system looks for Appkit.framework, then launched applications will bind to the "patch" framework, if the patch Framework is compiled to depend on the new Appkit framework in its new location, both the patch framework, and the Appkit framework would be loaded whenever a new application is launched.

The Adventure Begins

This procedure is not for the weak, timid, tired, forgetful or easily worried. Don't try this on a system that has important data. You would be well advised to read this entire section from start to finish and understand it before placing your fingers on the keyboard. Also, between the time this was written and the time you read it, the layout of files may have changed drastically, check the Ensuing web site for any errata. Above all else, don't come complaining to me when you find your system no longer boots.

In this process you will end up with three frameworks:

  • OldKit. This is the original AppKit as supplied by Apple, modified to run as either AppKit or OldKit.
  • NewKit. This will be the new version of AppKit that you make using Project Builder.
  • AppKit. This is what the system thinks is the one true AppKit and can be toggled back and forth between OldKit and NewKit.

Moving the Appkit framework ( or any important framework) is a quick way to disable a system, but it is necessary to get things rolling. You will need to either login as root, or become root using the superuser command. As root, and using the command line, execute the following commands:

cd /NextLibrary/Frameworks

This will get your current working directory to be the place for the rest of these commands.

mkdir OldKit.framework

This creates a new folder called OldKit that will hold the original Appkit.

 (cd AppKit.framework; gnutar -cf - . ) | (cd OldKit.framework; gnutar -xf - ) 

This whopper of a command copies the contents of AppKit to OldKit, and preserves as much of the directory structure as possible. Frameworks tend to have a great deal of links, and using the copy command will use more than twice the disc space. This command may take several minutes depending on how speedy your hard drives are.

cd OldKit.framework

Change into the OldKit framework for the rest of the commands

ln -s Versions/B/OldKit OldKit

This creates an alias called OldKit in the current directory that links to where the actual copy of OldKit will reside.

cd Versions/B
cp AppKit OldKit

This copies the file AppKit to OldKit. This is done so that you can have a pristine copy of AppKit when AppKit.framework is linked to OldKit.framework.

At this point OldKit.framework would seem to be ready to go, however there is a small bit of binary editing that needs to be done first. When applications are compiled to use OldKit, the actual OldKit file you just created will be examined, and inside is a string that identifies the path to this framework, which is still set to AppKit. To see this string you can type the command

otool -L OldKit

You must change this string so that NewKit can successfully link to OldKit. To change the string use a hex editor like HexEdit or you can download a program called App2Old that specifically patches AppKit to OldKit at ftp://ftp.ensuing.com/xxxxxx. After you edit the file, use otool to make sure the change was made.

Now that this has been done, you have a new framework called OldKit that is functionally identical to AppKit, just with a different name. In the mean time, the original AppKit framework still exists, and applications launched at this point still use the AppKit.framework.

You now need to create a new AppKit framework that accomplishes two tasks.

  1. 1. Augment the Applications class to activate the posing.
  2. 2. Link to OldKit, so that original AppKit classes are available at runtime.

The framework is created in much the same way as an Application or bundle. From ProjectBuilder, create a new project, and select the type of Framework. You then need to edit several variables and configuration files to prepare the project for compiling. Add the OldKit framework by adding it to the frameworks suitcase. Edit the file Makefile.postamble and change the following lines, so that the versions of this kit match the original AppKit.

DEPLOY_WITH_VERSION_NAME = B
COMPATIBILITY_PROJECT_VERSION = 45
CURRENT_PROJECT_VERSION = 45

You now need to find a convenient place to start the posing. A framework does not have a main() function, so you need some Objective-C method called early in the life of the Application. The ideal choice is to make an initialize method for your posing category of Application.

While not the very first method called in the life of an application, it does happen very early, and usually before any nibs are loaded. Create a new class file called Application_Patch.m, and modify the header to be a category of Application. The add an implementation of the method initialize to the m file.

***Application_Patch.h***

@interface Application(Patch)
@end

***Application_Patch.m***
@implementation Application(Patch)
+(void)initialize;
{
  [PurpleWindow poseAsClass:[NSWindow clas s]];
}
@end

The final source for this project, is the familiar PurpleWindow class, which you can copy from the previous example used in ProjectBuilder. If you compile and install, you should find a new framework in ~/Library/Frameworks called AppKit.framework, this is the patched AppKit.framework. Using AppEdit or otool, you should be able to see that this framework relies on the OldKit framework.

You are now ready to install this new framework as the default AppKit framework. By running these commands you can put your machine in to a very unstable state. Since you will be removing the AppKit to a new location, the system will be unable to find any AppKit resources that it may need, such as an open, save or font panels. If you try to use any system resources in AppKit.framework that are not already loaded, you may find that you program hangs.

From the command line type the following as root:

cd /NextLibrary/Frameworks
cp -r ~/AppKit/AppKit.framework NewKit

This copies NewKit from your personal library to the system library.

mv AppKit.framework safety
This moves AppKit.framework to a folder named safety

ln -s NewKit AppKit.framework

This creates an alias for NewKit called AppKit.framework,

At this point you should be able to launch new applications and find that familiar, yet somewhat sickening shade of purple. If Applications won't launch any more, relink AppKit to OldKit, and then proceed to debug by checking the console for messages.

Don't play around too much. The system at this point is still very unstable. When the current running applications were launched, (like Viewer, ProjectBuilder, Terminal), they started using AppKit.framework to load system resources. Those resources are now in OldKit, and if you try to print, open or save files, or anything else that needs a system resource, you may be lucky to get an error message, more likely you will wedge things pretty bad.

It would seem therefore that if you logged out and logged in, or rebooted, your entire system would be patched and you would be done. However, some of the early login process requires resources in Appkit.framework, and they are not forgiving about the fact that the AppKit is not there. If you reboot at this point you will not be able to login.

That said, you can fix this minor problem by changing the Resources directory of NewKit to point to the Resources of OldKit.

cd /NextLibrary/Frameworks/NewKit
rm -rf Resources
ln -s ../OldKit/Resources Resources

After rebooting, everything should return to "normal", and you should have a very purple operating system to play with. Congratulations.

Closing

When I originally wrote this hack during MacHack '97, it actually worked a bit differently. Instead of the patches being part of the patched AppKit, they were loadable bundles in much the same way as ProjectBuilders loadable bundres. A front end called HackManager allowed the user to pick which patches were applied to which applications. While the overall design was more flexible, and enabled the user to turn off the patch (something missing from this example), it suffered a great deal from slow launching applications. Each time an application was loaded, several dozen bundles would have to be read into memory as well, and then applied to the running application.

Hopefully, Apple will develop some plan for dealing with system wide patches like the example given. However, their most recent FAQ on Rhapsody suggests that the entire idea of modifying the system as a whole is going to be avoided.


Karl Kraft, Karl_Kraft@ensuing.com, has written several applications for OPENSTEP, the operating system on which Rhapsody is based, and is currently working on The CodeBook, an interactive book for learning the Yellow Box framework.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »
Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links below... | Read more »
Marvel Future Fight celebrates nine year...
Announced alongside an advertising image I can only assume was aimed squarely at myself with the prominent Deadpool and Odin featured on it, Netmarble has revealed their celebrations for the 9th anniversary of Marvel Future Fight. The Countdown... | Read more »
HoYoFair 2024 prepares to showcase over...
To say Genshin Impact took the world by storm when it was released would be an understatement. However, I think the most surprising part of the launch was just how much further it went than gaming. There have been concerts, art shows, massive... | Read more »

Price Scanner via MacPrices.net

Apple Watch Ultra 2 now available at Apple fo...
Apple has, for the first time, begun offering Certified Refurbished Apple Watch Ultra 2 models in their online store for $679, or $120 off MSRP. Each Watch includes Apple’s standard one-year warranty... Read more
AT&T has the iPhone 14 on sale for only $...
AT&T has the 128GB Apple iPhone 14 available for only $5.99 per month for new and existing customers when you activate unlimited service and use AT&T’s 36 month installment plan. The fine... Read more
Amazon is offering a $100 discount on every M...
Amazon is offering a $100 instant discount on each configuration of Apple’s new 13″ M3 MacBook Air, in Midnight, this weekend. These are the lowest prices currently available for new 13″ M3 MacBook... Read more
You can save $300-$480 on a 14-inch M3 Pro/Ma...
Apple has 14″ M3 Pro and M3 Max MacBook Pros in stock today and available, Certified Refurbished, starting at $1699 and ranging up to $480 off MSRP. Each model features a new outer case, shipping is... Read more
24-inch M1 iMacs available at Apple starting...
Apple has clearance M1 iMacs available in their Certified Refurbished store starting at $1049 and ranging up to $300 off original MSRP. Each iMac is in like-new condition and comes with Apple’s... Read more
Walmart continues to offer $699 13-inch M1 Ma...
Walmart continues to offer new Apple 13″ M1 MacBook Airs (8GB RAM, 256GB SSD) online for $699, $300 off original MSRP, in Space Gray, Silver, and Gold colors. These are new MacBook for sale by... Read more
B&H has 13-inch M2 MacBook Airs with 16GB...
B&H Photo has 13″ MacBook Airs with M2 CPUs, 16GB of memory, and 256GB of storage in stock and on sale for $1099, $100 off Apple’s MSRP for this configuration. Free 1-2 day delivery is available... Read more
14-inch M3 MacBook Pro with 16GB of RAM avail...
Apple has the 14″ M3 MacBook Pro with 16GB of RAM and 1TB of storage, Certified Refurbished, available for $300 off MSRP. Each MacBook Pro features a new outer case, shipping is free, and an Apple 1-... Read more
Apple M2 Mac minis on sale for up to $150 off...
Amazon has Apple’s M2-powered Mac minis in stock and on sale for $100-$150 off MSRP, each including free delivery: – Mac mini M2/256GB SSD: $499, save $100 – Mac mini M2/512GB SSD: $699, save $100 –... Read more
Amazon is offering a $200 discount on 14-inch...
Amazon has 14-inch M3 MacBook Pros in stock and on sale for $200 off MSRP. Shipping is free. Note that Amazon’s stock tends to come and go: – 14″ M3 MacBook Pro (8GB RAM/512GB SSD): $1399.99, $200... Read more

Jobs Board

*Apple* Systems Administrator - JAMF - Syste...
Title: Apple Systems Administrator - JAMF ALTA is supporting a direct hire opportunity. This position is 100% Onsite for initial 3-6 months and then remote 1-2 Read more
Relationship Banker - *Apple* Valley Financ...
Relationship Banker - Apple Valley Financial Center APPLE VALLEY, Minnesota **Job Description:** At Bank of America, we are guided by a common purpose to help Read more
IN6728 Optometrist- *Apple* Valley, CA- Tar...
Date: Apr 9, 2024 Brand: Target Optical Location: Apple Valley, CA, US, 92308 **Requisition ID:** 824398 At Target Optical, we help people see and look great - and Read more
Medical Assistant - Orthopedics *Apple* Hil...
Medical Assistant - Orthopedics Apple Hill York Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Now Read more
*Apple* Systems Administrator - JAMF - Activ...
…**Public Trust/Other Required:** None **Job Family:** Systems Administration **Skills:** Apple Platforms,Computer Servers,Jamf Pro **Experience:** 3 + years of Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.