// // main.m // SpinRiteRecoveryDisc // // Created by Mr. Gecko on 2/2/13. // Copyright (c) 2013 Mr. Gecko's Media (James Coleman). All rights reserved. http://mrgeckosmedia.com/ // // Permission to use, copy, modify, and/or distribute this software for any purpose // with or without fee is hereby granted, provided that the above copyright notice // and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, // OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, // DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS // ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // #import NSString * const MGMDiskUtilPath = @"/usr/sbin/diskutil"; NSString * const MGMHDIUtillPath = @"/usr/bin/hdiutil"; NSString * const MGMInstallerPath = @"/usr/sbin/installer"; NSString * const MGMBlessPath = @"/usr/sbin/bless"; NSString * const MGMRMPath = @"/bin/rm"; NSString * const MGMLNPath = @"/bin/ln"; NSString * const MGMCHMODPath = @"/bin/chmod"; NSString * const MGMCHOWNPath = @"/bin/chown"; NSData *runCommand(NSString *command, NSArray *arguments) { NSTask *task = [NSTask new]; [task setLaunchPath:command]; [task setArguments:arguments]; NSPipe *outPipe = [NSPipe new]; [task setStandardError:outPipe]; [task setStandardOutput:outPipe]; [task setEnvironment:[NSDictionary dictionaryWithObject:@"CM_BUILD" forKey:@"CM_BUILD"]]; [task launch]; [task waitUntilExit]; NSData *returnData = [[outPipe fileHandleForReading] readDataToEndOfFile]; [task release]; [outPipe release]; return returnData; } void MGMPrintV(NSString *format, va_list args) { NSAutoreleasePool *pool = [NSAutoreleasePool new]; if (![format hasSuffix:@"\n"]) format = [format stringByAppendingString:@"\n"]; NSString *message = [[[NSString alloc] initWithFormat:format arguments:args] autorelease]; fwrite([message UTF8String], 1, [message length], stdout); [pool drain]; } void MGMPrint(NSString *format, ...) { va_list ap; va_start(ap, format); MGMPrintV(format, ap); va_end(ap); } int main(int argc, const char * argv[]) { NSAutoreleasePool *pool = [NSAutoreleasePool new]; if (![NSUserName() isEqual:@"root"]) { MGMPrint(@"This program requires that you run it as root. To do so, run this command with sudo infront of it. It will request your password and then continue."); [pool drain]; exit(1); } /*MGMPrint(@"Welcome to Mr. Gecko's SpinRite Disk Maker."); MGMPrint(@"This utility will create a new DMG with the contents the Recovery Drive modified to include VirtualBox and SpinRite. VirtualBox will run SpinRite on your Hard Disk."); MGMPrint(@"Press enter to continue. To exit, push control-c."); char *ignore = malloc(1); scanf("%c", ignore); free(ignore);*/ NSFileManager *manager = [NSFileManager defaultManager]; NSData *data = runCommand(MGMDiskUtilPath, [NSArray arrayWithObjects:@"list", @"-plist", nil]); NSString *error = nil; NSDictionary *diskList = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&error]; if (error!=nil) { MGMPrint(@"Encountered error parsing disk list: %@", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]); [pool drain]; exit(1); } NSString *recoveryPartition = nil; NSString *mountPoint = nil; NSArray *disksAndPartitions = [diskList objectForKey:@"AllDisksAndPartitions"]; for (unsigned int i=0; i<[disksAndPartitions count]; i++) { NSArray *partitions = [[disksAndPartitions objectAtIndex:i] objectForKey:@"Partitions"]; if (partitions!=nil) { for (unsigned int p=0; p<[partitions count]; p++) { if ([[[partitions objectAtIndex:p] objectForKey:@"VolumeName"] isEqual:@"Recovery HD"]) { recoveryPartition = [NSString stringWithFormat:@"/dev/%@", [[partitions objectAtIndex:p] objectForKey:@"DeviceIdentifier"]]; mountPoint = [[partitions objectAtIndex:p] objectForKey:@"MountPoint"]; break; } } if (recoveryPartition!=nil) break; } } if (recoveryPartition==nil) { MGMPrint(@"Your system does not appear to have a Recovery Partition."); [pool drain]; exit(1); } MGMPrint(@"Found Recovery Partition at %@.", recoveryPartition); if (mountPoint==nil) { runCommand(MGMDiskUtilPath, [NSArray arrayWithObjects:@"mount", recoveryPartition, nil]); NSData *data = runCommand(MGMDiskUtilPath, [NSArray arrayWithObjects:@"list", @"-plist", nil]); NSString *error = nil; NSDictionary *diskList = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&error]; if (error!=nil) { MGMPrint(@"Encountered error parsing disk list: %@", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]); [pool drain]; exit(1); } NSArray *disksAndPartitions = [diskList objectForKey:@"AllDisksAndPartitions"]; for (unsigned int i=0; i<[disksAndPartitions count]; i++) { NSArray *partitions = [[disksAndPartitions objectAtIndex:i] objectForKey:@"Partitions"]; if (partitions!=nil) { for (unsigned int p=0; p<[partitions count]; p++) { if ([[[partitions objectAtIndex:p] objectForKey:@"VolumeName"] isEqual:@"Recovery HD"]) { mountPoint = [[partitions objectAtIndex:p] objectForKey:@"MountPoint"]; break; } } if (recoveryPartition!=nil) break; } } } if (mountPoint==nil) { MGMPrint(@"Failed to mount Recovery Partition."); [pool drain]; exit(1); } NSString *spinRiteMountPoint = nil; if (![manager fileExistsAtPath:@"/tmp/SpinRite.dmg"]) { NSData *data = runCommand(MGMHDIUtillPath, [NSArray arrayWithObjects:@"create", @"-srcfolder", mountPoint, @"-format", @"UDRW", @"-size", @"680m", @"-fs", @"HFS+", @"-layout", @"SPUD", @"-volname", @"SpinRite", @"-attach", @"-plist", @"/tmp/SpinRite.dmg", nil]); NSString *error = nil; NSDictionary *info = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&error]; if (error!=nil) { MGMPrint(@"Encountered error parsing image info: %@", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]); [manager removeItemAtPath:@"/tmp/SpinRite.dmg" error:nil]; runCommand(MGMDiskUtilPath, [NSArray arrayWithObjects:@"unmount", recoveryPartition, nil]); [pool drain]; exit(1); } NSArray *systemEntities = [info objectForKey:@"system-entities"]; for (unsigned int i=0; i<[systemEntities count]; i++) { spinRiteMountPoint = [[systemEntities objectAtIndex:i] objectForKey:@"mount-point"]; if (spinRiteMountPoint!=nil) { break; } } } else { NSData *data = runCommand(MGMHDIUtillPath, [NSArray arrayWithObjects:@"attach", @"-plist", @"/tmp/SpinRite.dmg", nil]); NSString *error = nil; NSDictionary *info = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&error]; if (error!=nil) { MGMPrint(@"Encountered error parsing image info: %@", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]); [manager removeItemAtPath:@"/tmp/SpinRite.dmg" error:nil]; runCommand(MGMDiskUtilPath, [NSArray arrayWithObjects:@"unmount", recoveryPartition, nil]); [pool drain]; exit(1); } NSArray *systemEntities = [info objectForKey:@"system-entities"]; for (unsigned int i=0; i<[systemEntities count]; i++) { spinRiteMountPoint = [[systemEntities objectAtIndex:i] objectForKey:@"mount-point"]; if (spinRiteMountPoint!=nil) { break; } } } runCommand(MGMDiskUtilPath, [NSArray arrayWithObjects:@"unmount", recoveryPartition, nil]); if (spinRiteMountPoint==nil) { MGMPrint(@"Failed to make a disk image of the Recovery Partition."); [manager removeItemAtPath:@"/tmp/SpinRite.dmg" error:nil]; [pool drain]; exit(1); } [manager removeItemAtPath:[spinRiteMountPoint stringByAppendingPathComponent:@"com.apple.boot.S"] error:nil]; runCommand(MGMBlessPath, [NSArray arrayWithObjects:[NSString stringWithFormat:@"--folder=%@", [spinRiteMountPoint stringByAppendingPathComponent:@"com.apple.recovery.boot"]], [NSString stringWithFormat:@"--file=%@", [[spinRiteMountPoint stringByAppendingPathComponent:@"com.apple.recovery.boot"] stringByAppendingPathComponent:@"boot.efi"]], nil]); // May God bless this folder so that this disk will boot amazingly. NSString *baseSystemMountPoint = nil; if (![manager fileExistsAtPath:@"/tmp/BaseSystem.dmg"]) { NSString *originalBaseSystemMountPoint = nil; NSData *data = runCommand(MGMHDIUtillPath, [NSArray arrayWithObjects:@"attach", @"-plist", [[spinRiteMountPoint stringByAppendingPathComponent:@"com.apple.recovery.boot"] stringByAppendingPathComponent:@"BaseSystem.dmg"], nil]); NSString *error = nil; NSDictionary *info = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&error]; if (error!=nil) { MGMPrint(@"Encountered error parsing image info: %@", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]); runCommand(MGMDiskUtilPath, [NSArray arrayWithObjects:@"unmount", recoveryPartition, nil]); [pool drain]; exit(1); } NSArray *systemEntities = [info objectForKey:@"system-entities"]; for (unsigned int i=0; i<[systemEntities count]; i++) { originalBaseSystemMountPoint = [[systemEntities objectAtIndex:i] objectForKey:@"mount-point"]; if (originalBaseSystemMountPoint!=nil) { break; } } if (originalBaseSystemMountPoint==nil) { MGMPrint(@"Unable to mount Base System from Recovery Image."); [manager removeItemAtPath:@"/tmp/BaseSystem.dmg" error:nil]; [pool drain]; exit(1); } data = runCommand(MGMHDIUtillPath, [NSArray arrayWithObjects:@"create", @"-srcfolder", originalBaseSystemMountPoint, @"-format", @"UDRW", @"-size", @"2g", @"-fs", @"HFS+", @"-layout", @"SPUD", @"-volname", @"BaseSystem", @"-attach", @"-plist", @"/tmp/BaseSystem.dmg", nil]); error = nil; info = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&error]; if (error!=nil) { MGMPrint(@"Encountered error parsing image info: %@", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]); [manager removeItemAtPath:@"/tmp/BaseSystem.dmg" error:nil]; runCommand(MGMHDIUtillPath, [NSArray arrayWithObjects:@"detach", originalBaseSystemMountPoint, nil]); [pool drain]; exit(1); } systemEntities = [info objectForKey:@"system-entities"]; for (unsigned int i=0; i<[systemEntities count]; i++) { baseSystemMountPoint = [[systemEntities objectAtIndex:i] objectForKey:@"mount-point"]; if (baseSystemMountPoint!=nil) { break; } } runCommand(MGMHDIUtillPath, [NSArray arrayWithObjects:@"detach", originalBaseSystemMountPoint, nil]); } else { NSData *data = runCommand(MGMHDIUtillPath, [NSArray arrayWithObjects:@"attach", @"-plist", @"/tmp/BaseSystem.dmg", nil]); NSString *error = nil; NSDictionary *info = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&error]; if (error!=nil) { MGMPrint(@"Encountered error parsing image info: %@", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]); [manager removeItemAtPath:@"/tmp/BaseSystem.dmg" error:nil]; [pool drain]; exit(1); } NSArray *systemEntities = [info objectForKey:@"system-entities"]; for (unsigned int i=0; i<[systemEntities count]; i++) { baseSystemMountPoint = [[systemEntities objectAtIndex:i] objectForKey:@"mount-point"]; if (baseSystemMountPoint!=nil) { break; } } } if (baseSystemMountPoint==nil) { MGMPrint(@"Unable to mount Base System."); [manager removeItemAtPath:@"/tmp/BaseSystem.dmg" error:nil]; [pool drain]; exit(1); } runCommand(MGMBlessPath, [NSArray arrayWithObjects:[NSString stringWithFormat:@"--folder=%@", [baseSystemMountPoint stringByAppendingString:@"/System/Library/CoreServices"]], [NSString stringWithFormat:@"--file=%@", [baseSystemMountPoint stringByAppendingString:@"/System/Library/CoreServices/boot.efi"]], nil]); // May God bless this folder so that this disk will boot amazingly. //[manager removeItemAtPath:[baseSystemMountPoint stringByAppendingPathComponent:@"Install OS X Mountain Lion.app"] error:nil]; //[manager removeItemAtPath:[[baseSystemMountPoint stringByAppendingPathComponent:@"System"] stringByAppendingPathComponent:@"Installation"] error:nil]; MGMPrint(@"Please download VirtualBox, drag the DMG file here, and push enter."); char *virtualBoxString = malloc(512); scanf("%s", virtualBoxString); NSString *virtualBoxPath = [[NSString stringWithUTF8String:virtualBoxString] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; free(virtualBoxString); NSString *virtualBoxMountPoint = nil; data = runCommand(MGMHDIUtillPath, [NSArray arrayWithObjects:@"attach", @"-plist", virtualBoxPath, nil]); error = nil; NSDictionary *info = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&error]; if (error!=nil) { MGMPrint(@"Encountered error parsing image info: %@", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]); [pool drain]; exit(1); } NSArray *systemEntities = [info objectForKey:@"system-entities"]; for (unsigned int i=0; i<[systemEntities count]; i++) { virtualBoxMountPoint = [[systemEntities objectAtIndex:i] objectForKey:@"mount-point"]; if (virtualBoxMountPoint!=nil) { break; } } if (virtualBoxMountPoint==nil || ![manager fileExistsAtPath:[virtualBoxMountPoint stringByAppendingPathComponent:@"VirtualBox.pkg"]]) { MGMPrint(@"Unable to mount VirtualBox."); [pool drain]; exit(1); } runCommand(MGMInstallerPath, [NSArray arrayWithObjects:@"-pkg", [virtualBoxMountPoint stringByAppendingPathComponent:@"VirtualBox.pkg"], @"-target", baseSystemMountPoint, nil]); /*NSString *extensionsPath = [[baseSystemMountPoint stringByAppendingPathComponent:@"Library"] stringByAppendingPathComponent:@"Extensions"]; [manager removeItemAtPath:[extensionsPath stringByAppendingPathComponent:@"VBoxDrv.kext"] error:nil]; [manager removeItemAtPath:[extensionsPath stringByAppendingPathComponent:@"VBoxNetAdp.kext"] error:nil]; [manager removeItemAtPath:[extensionsPath stringByAppendingPathComponent:@"VBoxNetFlt.kext"] error:nil]; [manager removeItemAtPath:[extensionsPath stringByAppendingPathComponent:@"VBoxUSB.kext"] error:nil];*/ //VirtualBox shell scripts fail on my computer. I think it's related to me customizing the ls command. So here is a custom version of those shell scripts. Because the recovery partition is 64bit only, I'm removing x86 and only linking x86_64. NSString *virtualBoxBinaryPath = [baseSystemMountPoint stringByAppendingString:@"/Applications/VirtualBox.app/Contents/MacOS/"]; NSArray *files = [manager contentsOfDirectoryAtPath:virtualBoxBinaryPath error:nil]; for (unsigned int i=0; i<[files count]; i++) { NSString *file = [virtualBoxBinaryPath stringByAppendingString:[files objectAtIndex:i]]; if ([file hasSuffix:@"-amd64"]) { NSString *newFile = [file stringByReplacingOccurrencesOfString:@"-amd64" withString:@""]; runCommand(MGMRMPath, [NSArray arrayWithObjects:@"-f", newFile, nil]); runCommand(MGMLNPath, [NSArray arrayWithObjects:@"-vh", file, newFile, nil]); } else if ([file hasSuffix:@"-x86"]) { runCommand(MGMRMPath, [NSArray arrayWithObjects:@"-f", file, nil]); } } runCommand(MGMCHMODPath, [NSArray arrayWithObjects:@"6755", @"/Applications/VirtualBox.app/Contents/MacOS/VirtualBox", nil]); runCommand(MGMHDIUtillPath, [NSArray arrayWithObjects:@"detach", virtualBoxMountPoint, nil]); NSFileHandle *shProfile = [NSFileHandle fileHandleForWritingAtPath:[baseSystemMountPoint stringByAppendingString:@"/etc/profile"]]; [shProfile writeData:[@"echo \"Available commands are virtualbox, safari, diskutility, and networkutility.\"\n" dataUsingEncoding:NSUTF8StringEncoding]]; [shProfile closeFile]; runCommand(MGMCHMODPath, [NSArray arrayWithObjects:@"6755", [baseSystemMountPoint stringByAppendingString:@"/usr/bin/VirtualBox"], nil]); [manager createFileAtPath:[baseSystemMountPoint stringByAppendingString:@"/usr/bin/safari"] contents:[@"#!/bin/bash\n/Applications/Safari.app/Contents/MacOS/Safar\n" dataUsingEncoding:NSUTF8StringEncoding] attributes:nil]; runCommand(MGMCHMODPath, [NSArray arrayWithObjects:@"755", [baseSystemMountPoint stringByAppendingString:@"/usr/bin/safari"], nil]); [manager createFileAtPath:[baseSystemMountPoint stringByAppendingString:@"/usr/bin/diskutility"] contents:[@"#!/bin/bash\n/Applications/Disk\\ Utility.app/Contents/MacOS/Disk\\ Utility\n" dataUsingEncoding:NSUTF8StringEncoding] attributes:nil]; runCommand(MGMCHMODPath, [NSArray arrayWithObjects:@"755", [baseSystemMountPoint stringByAppendingString:@"/usr/bin/diskutility"], nil]); [manager createFileAtPath:[baseSystemMountPoint stringByAppendingString:@"/usr/bin/networkutility"] contents:[@"#!/bin/bash\n/Applications/Network\\ Utility.app/Contents/MacOS/Network\\ Utility\n" dataUsingEncoding:NSUTF8StringEncoding] attributes:nil]; runCommand(MGMCHMODPath, [NSArray arrayWithObjects:@"755", [baseSystemMountPoint stringByAppendingString:@"/usr/bin/networkutility"], nil]); [manager copyItemAtPath:@"/System/Library/Frameworks/AGL.framework" toPath:[baseSystemMountPoint stringByAppendingString:@"/System/Library/Frameworks/AGL.framework"] error:nil]; runCommand(MGMHDIUtillPath, [NSArray arrayWithObjects:@"detach", baseSystemMountPoint, nil]); data = runCommand(MGMHDIUtillPath, [NSArray arrayWithObjects:@"convert", @"/tmp/BaseSystem.dmg", @"-format", @"UDZO", @"-o", @"/tmp/BaseSystem-out.dmg", @"-plist", nil]); error = nil; info = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&error]; if (error!=nil) { MGMPrint(@"Encountered error parsing image info: %@", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]); [manager removeItemAtPath:@"/tmp/BaseSystem-out.dmg" error:nil]; [pool drain]; exit(1); } [manager removeItemAtPath:@"/tmp/BaseSystem.dmg" error:nil]; [manager removeItemAtPath:[[spinRiteMountPoint stringByAppendingPathComponent:@"com.apple.recovery.boot"] stringByAppendingPathComponent:@"BaseSystem.dmg"] error:nil]; NSError *errorInfo = nil; [manager copyItemAtPath:@"/tmp/BaseSystem-out.dmg" toPath:[[spinRiteMountPoint stringByAppendingPathComponent:@"com.apple.recovery.boot"] stringByAppendingPathComponent:@"BaseSystem.dmg"] error:&errorInfo]; if (errorInfo!=nil) { MGMPrint(@"Encountered error replacing BaseSystem: %@", [errorInfo description]); [pool drain]; exit(1); } [pool drain]; return 0; }