iPhone Life magazine

iOS programming: making backup copies of system files and restoring them (in the iPhone 3GS / 4 Video Camera Enhancer tools)

(Beware: highly technical article! If you aren't a programmer and/or don't want to see how my backup / restore algorithm works, step right to the last two chapters, starting with "Important note for 3G S users"!)

You may have already checked out the iPhone 3G S version of my video camera enhancer tool (last, dedicated post HERE). It was some days ago that I added some basic (non-advanced) restoration capabilities to it. Now, it's time to hugely enhance backup/restore support. This isn't solely for my Video Camera Enhancer tools; you can reuse the code in numerous similar backup / restore situations in both Cydia and AppStore apps (my sources and algorithms support both in the same code).

While reading the dedicated forums, I've noticed the approach of all previous attempts, all utilising simple, “dumb” pre-made property list-based file overwriting, have resulted in a lot of problems – namely, losing all the possible, for the platform, non-standard hacks and enhancements like the HDR hack for the 3G S. You can easily lose your HDR capability if you just overwrite the /System/Library/CoreServices/SpringBoard.app/N88AP.plist file with your own. (It's in this file that you need to add a boolean key, “hdr-image-capture”, in the “capabilities” section, to enable HDR; mini-tutorial HERE, HERE and HERE) Many previous 3G S video hacks have also overwritten this N88AP.plist file. You can easily guess the result: after overwriting with a pre-generated file lacking the manually added “hdr-image-capture” boolean key, the HDR capability has simply disappeared. Some other, even worse bugs have also appeared: missing “Phone” and “SMS” icons, freezing (on the 3G S, manually hacked) FaceTime and the like (see e.g. THIS, THIS and THIS).

Now, in my apps, nothing similar can happen upon installing or using (but not, as you'll see, using the button on the Simple view tab to restore the original configuration.) The reasons are simple: I don't use pre-saved plist files exactly in order to avoid bugs and accidental overwrites of other, independent hacks and enhancements, but only change the values strictly needed for video mode fine-tuning by always reading in the entire property list upon the startup of the app, make the necessary changes and, finally, serialising the plist back to the file system. (In addition, when it comes to the N88AP.plist file, I don't modify it at all. It's not needed, no matter what some other posts state from other people. Nevertheless, there may be fixes / hacks in other plist files I, otherwise, edit.)

So far, I've just used the following approach to restoration: I've added the (stock) plist files to my bundle and, when the user tapped the “Restore” button, after deleting the target system files, I've just overwritten them with the ones in the bundle. The implementation of all this is really straightforward:

NSString* srcPlistFilePath = nil;
NSString* targetFile = nil;
NSError* err= nil;

if (systemVersionHigherThan43)
{
srcPlistFilePath = [[NSBundle mainBundle] pathForResource:@"AVCaptureSession" ofType:@"plist"];
targetFile = @"/System/Library/Frameworks/AVFoundation.framework/N88/AVCaptureSession.plist";
}
[[NSFileManager defaultManager] removeItemAtPath:targetFile error:&err];
[[NSFileManager defaultManager] copyItemAtPath:srcPlistFilePath toPath:targetFile error:&err];

(Here, only showing the deletion and copying of AVCaptureSession.plist for 4.3.x).

How this should be enhanced so that we restore the file originally (upon first [re]install) encountered plist files and not standard stock ones, which may not be what the user wants? It's easy: don't store anything in the bundle, but just copy the system plist files into our Documents directory (independent of whether we're running as a Cydia or as a normal app – see my remarks on the differences of these on different OS versions HERE, in the “Some programming: Cydia and the Documents directory (storing persistent settings)” chapter).

Before we start coding, something should also be decided: we will also need to remove the "/var/mobile/Library/<YOUR APP'S NAME>" directory in its entirety upon the user's tapping the Restore button. This means the next restart of the app will populate the directory with the then-current(!) plist files.

Why do you want to do so? Say you download my tool and start using it. However, later, you also download other tools directly overwriting AVCaptureSession.plist / AVCapture.plist and the other, by my app, modified plist files. What will happen to the changes made by these third-party apps when you tap the “Restore” button in my app? They will get entirely lost because I restore a file that has been backed up well before you installed the additional apps modifying these files. What should, then, be done? Just give the user a way to create a snapshot of the system files any time he wants. By removing the backup files and forcing my app, upon the next start, to back them up again. If you make sure you start the third-party app to modify the system files after restoring them from my app and before starting my app again (because it's then that the backup files are locally saved for further restoration), you can easily fix this problem.

Also, this approach allows for completely cleaning up after yourself, including even deleting our custom documents directory (with iPhone 4-specific app, it's /var/mobile/Library/ip4-VideoCameraPlus). Unlike in AppStore apps (or apps deployed from Xcode), your Documents directory doesn't get cleaned up when you remove the app via Cydia, using Manage / Packages / <appname> / Modify / Remove. This is simply because there's no dedicated Documents directory inside the bundle, unlike with the “official” case, which could also be automatically deleted upon removing the app. (Actually, you're in charge of finding a unique name when storing your stuff under /var/mobile/Library – that is, way outside your bundle. No wonder Cydia doesn't know which file / directory belongs to you and can't clean up these files itself.)

Backing up the original system configuration files

Therefore, I've added the following code to do the initial backup to the app delegate, also moving the document directory path setting from the second tab to here (accessed via an extern variable to keep the code tidy) so that it surely executes right after starting the app and doing any change by the user. (Of course, instead of the app delegate, I could have added the code to the first tab's VC's viewDidLoad too, because it would also execute right before the user overwrites these values with anything. Keeping this in the original location's, the second tab's viewDidLoad, it would probably never execute; hence the relocation to a class that does execute right after start.)

The new code in the app delegate for the 3G S:

if (![documentsDir hasPrefix:@"/var/mobile/Applications/"])
documentsDir = @"/var/mobile/Library/ip3gs-VideoCameraPlus"; // we're under Cydia
if (![[NSFileManager defaultManager] fileExistsAtPath:[documentsDir stringByAppendingString:@"/CameraRollValidator.plist"]]) // check for a backup file inside - not for the existence of the docs dir, as it'll already be present with Xcode / AppStore apps!
{
NSError* err= nil;
if (![documentsDir hasPrefix:@"/var/mobile/Applications/"]) [[NSFileManager defaultManager] createDirectoryAtPath:documentsDir withIntermediateDirectories:NO attributes:nil error:&err]; // create docs dir if it doesn't exist (Cydia only!)

NSString* srcPlistFilePath = @"/System/Library/Frameworks/AVFoundation.framework/N88/AVCaptureSession.plist";
NSString* targetFile = [documentsDir stringByAppendingString:@"/AVCaptureSession.plist"];
if (!systemVersionHigherThan43)
{
srcPlistFilePath = @"/System/Library/PrivateFrameworks/Celestial.framework/N88/AVCapture.plist";
targetFile = [documentsDir stringByAppendingString:@"/AVCapture.plist"];
}
[[NSFileManager defaultManager] copyItemAtPath:srcPlistFilePath toPath:targetFile error:&err];

srcPlistFilePath = @"/System/Library/PrivateFrameworks/Celestial.framework/N88/CameraRollValidator.plist";
targetFile = [documentsDir stringByAppendingString:@"/CameraRollValidator.plist"];
[[NSFileManager defaultManager] copyItemAtPath:srcPlistFilePath toPath:targetFile error:&err];
srcPlistFilePath = @"/System/Library/PrivateFrameworks/Celestial.framework/N88/MediaValidator.plist";
targetFile = [documentsDir stringByAppendingString:@"/MediaValidator.plist"];
[[NSFileManager defaultManager] copyItemAtPath:srcPlistFilePath toPath:targetFile error:&err];
}

and for the iPhone 4:

if (![documentsDir hasPrefix:@"/var/mobile/Applications/"])
documentsDir = @"/var/mobile/Library/ip4-VideoCameraPlus"; // we're under Cydia

if (![[NSFileManager defaultManager] fileExistsAtPath:[documentsDir stringByAppendingString:@"/AVCaptureSession.plist"]]
&&
![[NSFileManager defaultManager] fileExistsAtPath:[documentsDir stringByAppendingString:@"/AVCapture.plist"]]) // check for a backup file inside - not for the existence of the docs dir, as it'll already be present with Xcode / AppStore apps!
{
NSError* err= nil;
if (![documentsDir hasPrefix:@"/var/mobile/Applications/"]) [[NSFileManager defaultManager] createDirectoryAtPath:documentsDir withIntermediateDirectories:NO attributes:nil error:&err]; // create docs dir if it doesn't exist (Cydia only!)
{
NSString* srcPlistFilePath = @"/System/Library/Frameworks/AVFoundation.framework/N90/AVCaptureSession.plist";
NSString* targetFile = [documentsDir stringByAppendingString:@"/AVCaptureSession.plist"];
if (!systemVersionHigherThan43)
{
srcPlistFilePath = @"/System/Library/PrivateFrameworks/Celestial.framework/N90/AVCapture.plist";
targetFile = [documentsDir stringByAppendingString:@"/AVCapture.plist"];
}
[[NSFileManager defaultManager] copyItemAtPath:srcPlistFilePath toPath:targetFile error:&err];
}
}

As you may have guessed, the iPhone 4 version is shorter as we don't need to back up CameraRollValidator and MediaValidator (we don't modify them). Nevertheless, the second if() is a bit longer because we don't use a separate flag file to denote we've already backed up and the only file we back up has different names under different iOS versions; therefore, we need to check for occurrences of any of the two possible versions. It's only when there isn't any of the both that we go on creating a backup.

Restoring the original system configuration files

If you understand what I've explained above on the code supporting both Xcode (traditional “Documents” subdirectory) and Cydia deployment and how the back up routine worked, this will be easy:

iPhone 3G S:

-(IBAction) restroreOrigConfig
{
if (![[NSFileManager defaultManager] fileExistsAtPath:[documentsDir stringByAppendingString:@"/CameraRollValidator.plist"]]) return; // no backup file exists (e.g., the user tapped the Restore button twice) - restoring would result in a big mess because the target files would also be deleted
NSError* err= nil;

NSString* srcPlistFilePath = [documentsDir stringByAppendingString:@"/AVCaptureSession.plist"];
NSString* targetFile = @"/System/Library/Frameworks/AVFoundation.framework/N88/AVCaptureSession.plist";
if (!systemVersionHigherThan43)
{
srcPlistFilePath = [documentsDir stringByAppendingString:@"/AVCapture.plist"];
targetFile = @"/System/Library/PrivateFrameworks/Celestial.framework/N88/AVCapture.plist";
}
[[NSFileManager defaultManager] removeItemAtPath:targetFile error:&err];
[[NSFileManager defaultManager] copyItemAtPath:srcPlistFilePath toPath:targetFile error:&err];
[[NSFileManager defaultManager] removeItemAtPath:srcPlistFilePath error:&err]; // get rid of the source file too

srcPlistFilePath = [documentsDir stringByAppendingString:@"/CameraRollValidator.plist"];
targetFile = @"/System/Library/PrivateFrameworks/Celestial.framework/N88/CameraRollValidator.plist";
[[NSFileManager defaultManager] removeItemAtPath:targetFile error:&err];
[[NSFileManager defaultManager] copyItemAtPath:srcPlistFilePath toPath:targetFile error:&err];
[[NSFileManager defaultManager] removeItemAtPath:srcPlistFilePath error:&err]; // get rid of the source file too

srcPlistFilePath = [documentsDir stringByAppendingString:@"/MediaValidator.plist"];
targetFile = @"/System/Library/PrivateFrameworks/Celestial.framework/N88/MediaValidator.plist";
[[NSFileManager defaultManager] removeItemAtPath:targetFile error:&err];
[[NSFileManager defaultManager] copyItemAtPath:srcPlistFilePath toPath:targetFile error:&err];
[[NSFileManager defaultManager] removeItemAtPath:srcPlistFilePath error:&err]; // get rid of the source file too

[[NSFileManager defaultManager] removeItemAtPath:[documentsDir stringByAppendingString:@"/backup.plist"] error:&err]; // also delete the Advanced view config plist file so that we can delete the entire dir

if (![documentsDir hasPrefix:@"/var/mobile/Applications/"]) [[NSFileManager defaultManager] removeItemAtPath:documentsDir error:&err]; // Cydia app: we also delete the docs dir
}


iPhone 4:

-(IBAction) restroreOrigConfig
{
if (![[NSFileManager defaultManager] fileExistsAtPath:[documentsDir stringByAppendingString:@"/AVCaptureSession.plist"]]
&&
![[NSFileManager defaultManager] fileExistsAtPath:[documentsDir stringByAppendingString:@"/AVCapture.plist"]]) return; // no backup file exists (e.g., the user tapped the Restore button twice) - restoring would result in a big mess because the target files would also be deleted
NSError* err= nil;

NSString* srcPlistFilePath = [documentsDir stringByAppendingString:@"/AVCaptureSession.plist"];
NSString* targetFile = @"/System/Library/Frameworks/AVFoundation.framework/N90/AVCaptureSession.plist";
if (!systemVersionHigherThan43)
{
srcPlistFilePath = [documentsDir stringByAppendingString:@"/AVCapture.plist"];
targetFile = @"/System/Library/PrivateFrameworks/Celestial.framework/N90/AVCapture.plist";
}
[[NSFileManager defaultManager] removeItemAtPath:targetFile error:&err];
[[NSFileManager defaultManager] copyItemAtPath:srcPlistFilePath toPath:targetFile error:&err];
[[NSFileManager defaultManager] removeItemAtPath:srcPlistFilePath error:&err]; // get rid of the source file too

[[NSFileManager defaultManager] removeItemAtPath:[documentsDir stringByAppendingString:@"/backup.plist"] error:&err]; // also delete the Advanced view config plist file so that we can delete the entire dir

if (![documentsDir hasPrefix:@"/var/mobile/Applications/"]) [[NSFileManager defaultManager] removeItemAtPath:documentsDir error:&err]; // Cydia app: we also delete the docs dir
}

Important note for 3G S users

I've continued fine-tuning and heavily testing my hacks. On the iPhone 3G S running the (for “Full sensor” recordings, recommended) 4.3.x firmware, if you do record using the “Full sensor” mode, make absolutely sure you do it using the highest possible data rate, 1.75 Mbps! I've encountered much more frequent clicking in several lower data rates (namely, 1 Mbps and the default 475 kbps). Don't be afraid of it not being played back on the phone smoothly (actually, video playback must be resumed manually quite often) – it will on a desktop. And it won't click / freeze / lag much. For example, in a 6-minute test, I've only encountered a huge click at 1:00 [in another test, I've noticed a similarly huge click at exactly 1:00 too], and two small at 1:35 and 2:45. In another test, in addition to the already-mentioned huge click at 1:00, the next one was a small one at 3:00. At the same time, I also ran the same test (1.75 Mbps) on a 4.2.1 3GS. It clicked far more frequently. At first, in the first two minutes, I only heard 3-4 clicks; after that, however, their frequency has heavily soared and I encountered clicks every about 15-20 seconds.

The new versions of my app

They're already in Cydia (1.6 for the 3G S and 1.3 for the iPhone 4). As usual, I provide you with the full source code; it's HERE (iPhone 4) and HERE (3GS). Feel free to experiment with the source code!

Want to master your iPhone and iPad? Sign up here to get our tip of the day delivered right to your inbox.
Email icon
Want more? Get our weekly newsletter:

Werner Ruotsalainen is an iOS and Java programming lecturer who is well-versed in programming, hacking, operating systems, and programming languages. Werner tries to generate unique articles on subjects not widely discussed. Some of his articles are highly technical and are intended for other programmers and coders.

Werner also is interested in photography and videography. He is a frequent contributor to not only mobile and computing publications, but also photo and video forums. He loves swimming, skiing, going to the gym, and using his iPads. English is one of several languages he speaks.