void Load (string fileName)
		{
			this.FileName = fileName;
			string fileText = File.ReadAllText (fileName);
			
			//find the raw plist within the .mobileprovision file
			int start = fileText.IndexOf ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
			int length;
			if (start < 0 || (length = (fileText.IndexOf ("</plist>", start) - start)) < 1)
				throw new Exception ("Did not find XML plist in '" + fileName + "'");
			
			length += "</plist>".Length;
			string rawPlist = fileText.Substring (start, length);
			
			
			var doc = new PlistDocument ();
			doc.LoadFromXml (rawPlist);
			var rootDict = (PlistDictionary) doc.Root;
			
			var prefixes = rootDict["ApplicationIdentifierPrefix"] as PlistArray;
			if (prefixes != null)
				this.ApplicationIdentifierPrefix = prefixes.OfType<PlistString> ().Select (x => x.Value).ToArray ();
			
			var creationDate = rootDict ["CreationDate"] as PlistDate;
			if (creationDate != null)
				this.CreationDate = creationDate.Value;
			
			var devCerts = rootDict ["DeveloperCertificates"] as PlistArray;
			if (devCerts != null)
				this.DeveloperCertificates = devCerts.OfType<PlistData> ().Select (x => new X509Certificate2 (x.Value)).ToArray ();
			
			var entl = rootDict ["Entitlements"] as PlistDictionary;
			if (entl != null)
				this.Entitlements = entl; //string application-identifier, bool get-task-allow, string[] keychain-access-groups
			
			var expirationDate = rootDict ["ExpirationDate"] as PlistDate;
			if (expirationDate != null)
				this.ExpirationDate = expirationDate.Value;
			
			var name = rootDict ["Name"] as PlistString;
			if (name != null)
				this.Name = name.Value;
			
			var provDevs = rootDict.TryGetValue ("ProvisionedDevices") as PlistArray;
			if (provDevs != null)
				this.ProvisionedDevices = provDevs.OfType<PlistString> ().Select (x => x.Value).ToArray ();
			
			var ttl = rootDict ["TimeToLive"] as PlistInteger;
			if (ttl != null)
				this.TimeToLive = ttl.Value;
			
			var uuid = rootDict ["UUID"] as PlistString;
			if (uuid != null)
				this.Uuid = uuid.Value;
			
			var version = rootDict ["Version"] as PlistInteger;
			if (version != null)
				this.Version = version.Value;
		}
        protected override BuildResult Build(IProgressMonitor monitor, SolutionEntityItem item, ConfigurationSelector configuration)
        {
            MonobjcProject proj = item as MonobjcProject;
            if (proj == null || proj.CompileTarget != CompileTarget.Exe)
                return base.Build(monitor, item, configuration);

            BuildResult result = base.Build(monitor, item, configuration);
            if (result.ErrorCount > 0)
                return result;

            var conf = (MonobjcProjectConfiguration) proj.GetConfiguration(configuration);

            // Create directories
            monitor.BeginTask("Creating application bundle", 0);
            foreach (var path in new [] { conf.ResourcesDirectory, conf.MacOSDirectory }) {
                if (!Directory.Exists(path))
                    Directory.CreateDirectory(path);
            }
            monitor.EndTask();

            string exeName = Path.GetFileNameWithoutExtension(conf.CompiledOutputName);

            // Write Info.plist into 'Contents' directory

            monitor.BeginTask("Updating application manifest", 0);
            var doc = new PlistDocument();
            var docRoot = new PlistDictionary();
              			docRoot["CFBundleExecutable"]            = exeName;
            docRoot["CFBundleInfoDictionaryVersion"] = "6.0";
            docRoot["CFBundlePackageType"]           = "APPL";
            docRoot["CFBundleName"]                  = proj.BundleDisplayName ?? proj.Name;
            docRoot["CFBundleDisplayName"]           = proj.BundleDisplayName ?? proj.Name;
            docRoot["CFBundleIdentifier"]            = proj.BundleIdentifier ?? String.Format("com.yourcompany.{0}", proj.Name);
            docRoot["CFBundleVersion"]               = proj.BundleVersion ?? "1.0";
            docRoot["CFBundleDevelopmentRegion"]     = proj.BundleDevelopmentRegion ?? "English";

            FilePath icon = proj.BundleIcon.ToRelative (proj.BaseDirectory);
            if (!(icon.IsNullOrEmpty || icon.ToString () == "."))
                docRoot["CFBundleIconFile"] = icon.FileName;

            if (!String.IsNullOrEmpty (proj.MainNibFile.ToString ()))
                docRoot["NSMainNibFile"] = Path.GetFileNameWithoutExtension(proj.MainNibFile.ToString ());

            doc.Root = docRoot;

            var plistOut = conf.ContentsDirectory.Combine("Info.plist");
            using (XmlTextWriter writer = new XmlTextWriter(plistOut, Encoding.UTF8)) {
                doc.Write(writer);
            }
            monitor.EndTask();

            // Copy project binary into 'Resources' directory
            monitor.BeginTask("Copying application binary", 0);
            File.Copy(conf.CompiledOutputName, conf.ResourcesDirectory.Combine(conf.CompiledOutputName.FileName), true);
            if (File.Exists(conf.CompiledOutputName + ".mdb"))
                File.Copy(conf.CompiledOutputName + ".mdb", conf.ResourcesDirectory.Combine(conf.CompiledOutputName.FileName + ".mdb"), true);
            monitor.EndTask();

            // Copy references into 'Resources' directory
            var references = BuildUtils.GetReferencesFilePairs(proj, configuration);
            foreach (var pair in references) {
                pair.EnsureOutputDirectory();
                monitor.Log.WriteLine("Copying '{0}' to '{1}'", pair.Input, pair.Output);
                File.Copy(pair.Input, pair.Output, true);
                monitor.Step(1);
            }
            monitor.EndTask();

            // Write launcher script into 'MacOS' directory
            monitor.BeginTask("Writing launcher script", 0);
            string scriptPath = conf.MacOSDirectory.Combine(exeName);
            var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("LaunchScript.sh");
            using (StreamReader reader = new StreamReader(stream)) {
                using (StreamWriter writer = new StreamWriter(scriptPath)) {
                    writer.Write(reader.ReadToEnd());
                }
            }

            // Make executable (755)
            new UnixFileInfo(scriptPath).FileAccessPermissions = FileAccessPermissions.UserReadWriteExecute |
                FileAccessPermissions.GroupRead | FileAccessPermissions.GroupExecute |
                FileAccessPermissions.OtherRead | FileAccessPermissions.OtherExecute;

            monitor.EndTask();

            return result;
        }
        public override bool Execute()
        {
            string displayName = DisplayName ?? Name;
            string identifier  = Identifier ?? String.Format("com.yourcompany.{0}", Name);
            string version     = Version ?? "1.0";
            string region      = DevelopmentRegion ?? "English";

            var doc = new PlistDocument();
            var docRoot = new PlistDictionary();
              			docRoot["CFBundleExecutable"]            = ExeName;
            docRoot["CFBundleInfoDictionaryVersion"] = "6.0";
            docRoot["CFBundlePackageType"]           = "APPL";
            docRoot["CFBundleName"]                  = displayName;
            docRoot["CFBundleDisplayName"]           = displayName;
            docRoot["CFBundleIdentifier"]            = identifier;
            docRoot["CFBundleVersion"]               = version;
            docRoot["CFBundleDevelopmentRegion"]     = region;

            if (!String.IsNullOrEmpty(Icon))
                docRoot["CFBundleIconFile"] = Icon;

            if (!String.IsNullOrEmpty(MainNibFile))
                docRoot["NSMainNibFile"] = Path.GetFileNameWithoutExtension(MainNibFile);

            doc.Root = docRoot;

            using (XmlTextWriter writer = new XmlTextWriter(FileName, Encoding.UTF8)) {
                doc.Write(writer);
            }

            Log.LogMessage("Wrote {0}", FileName);

            return true;
        }
		static void WriteXcent (PlistDocument doc, string file)
		{
			//write the plist to a byte[] as UTF8 without a BOM
			var ms = new MemoryStream ();
			var xmlSettings = new XmlWriterSettings () {
				Encoding = new UTF8Encoding (false), //no BOM
				CloseOutput = false,
				Indent = true,
				IndentChars = "\t",
				NewLineChars = "\n",
			};
			using (var writer = XmlTextWriter.Create (ms, xmlSettings))
				doc.Write (writer);
			
			//HACK: workaround for bug in Apple's entitlements XML parser
			//having written to a UTF8 stream to convince the xmlwriter to do the right thing,
			//we now convert to string and back to do some substitutions to work around bugs
			//in Apple's braindead entitlements XML parser.
			//Specifically, it chokes on "<true />" but accepts "<true">
			//Hence, to be on the safe side, we produce EXACTLY the same format
			var sb = new StringBuilder (Encoding.UTF8.GetString (ms.GetBuffer ()));
			sb.Replace ("-//Apple Computer//DTD PLIST 1.0//EN", "-//Apple//DTD PLIST 1.0//EN");
			sb.Replace ("<?xml version=\"1.0\" encoding=\"utf-8\"?>", "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
			sb.Replace ("\n\t", "\n");
			sb.Replace (" />\n", "/>\n");
			sb.Append ("\n");
			var buf = Encoding.UTF8.GetBytes (sb.ToString ());
			
			//write the xcent file with the magic header, length, and the plist
			byte[] magic = new byte[] { 0xfa, 0xde, 0x71, 0x71 };
			byte[] fileLen = Mono.DataConverter.BigEndian.GetBytes ((uint)buf.Length + 8); // 8 = magic.length + magicLen.Length
			using (var fs = File.Open (file, FileMode.Create)) {
				fs.Write (magic, 0, magic.Length);
				fs.Write (fileLen, 0, fileLen.Length);
				fs.Write (buf, 0, (int)buf.Length);
			}
		}
		static BuildResult GenXcent (IProgressMonitor monitor, IPhoneProject proj, IPhoneProjectConfiguration conf,
		                      IPhoneAppIdentity identity, out string xcentName)
		{
			xcentName = conf.CompiledOutputName.ChangeExtension (".xcent");
			
			monitor.BeginTask (GettextCatalog.GetString ("Processing entitlements file"), 0);
			
			string srcFile;
			
			if (!string.IsNullOrEmpty (conf.CodesignEntitlements)) {
				if (!File.Exists (conf.CodesignEntitlements))
					return BuildError ("Entitlements file \"" + conf.CodesignEntitlements + "\" not found.");
				srcFile = conf.CodesignEntitlements;
			} else {
				srcFile = "/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS" + conf.MtouchSdkVersion
					+ ".sdk/Entitlements.plist";
			}
			
			var doc = new PlistDocument ();
			try {
				doc.LoadFromXmlFile (srcFile);
			} catch (Exception ex) {
				monitor.Log.WriteLine (ex.ToString ());
				return BuildError ("Error loading entitlements source file '" + srcFile +"'.");
			}
			
			//insert the app ID into the plist at the beginning
			var oldDict = doc.Root as PlistDictionary;
			var newDict = new PlistDictionary ();
			doc.Root = newDict;
			newDict["application-identifier"] = identity.AppID;
			var keychainGroups = new PlistArray (new [] { identity.AppID } );
			newDict["keychain-access-groups"] = keychainGroups;
			
			//merge in the user's values
			foreach (var item in oldDict) {
				//FIXME: we currently ignore these items, and write our own, but maybe we should do substitutes
				//i.e. $(AppIdentifierPrefix)$(CFBundleIdentifier)
				if (item.Key == "application-identifier") {
					var str = item.Value as PlistString;
					if (str == null || string.IsNullOrEmpty (str.Value) || str.Value.Contains ('$'))
						continue;
				} else if (item.Key == "keychain-access-groups") {
					//special handling, merge into the array
					var keyArr = item.Value as PlistArray;
					foreach (var key in keyArr) {
						var str = key as PlistString;
						if (str != null && !string.IsNullOrEmpty (str.Value) && !str.Value.Contains ('$')) {
							keychainGroups.Add (str.Value);
						}
					}
					continue;
				}
				newDict[item.Key] = item.Value;
			}
			
			//merge in the settings from the provisioning profile, skipping some
			foreach (var item in identity.Profile.Entitlements)
				if (item.Key != "application-identifier" && item.Key != "keychain-access-groups")
					newDict[item.Key] = item.Value;
			
			try {
				WriteXcent (doc, xcentName);
			} catch (Exception ex) {
				monitor.Log.WriteLine (ex.ToString ());
				return BuildError ("Error writing entitlements file '" + xcentName +"'.");
			}
			
			monitor.EndTask ();
			return null;
		}
		static BuildResult CreateMergedPlist (IProgressMonitor monitor, IPhoneProjectConfiguration conf,
			ProjectFile template, string outPath,
			Func<IPhoneProjectConfiguration, PlistDocument,BuildResult> merge)
		{
			var result = new BuildResult ();
			
			var doc = new PlistDocument ();
			if (template != null) {
				try {
					doc.LoadFromXmlFile (template.FilePath);
				} catch (Exception ex) {
					if (ex is XmlException)
						result.AddError (template.FilePath, ((XmlException)ex).LineNumber,
						                 ((XmlException)ex).LinePosition, null, ex.Message);
					else
						result.AddError (template.FilePath, 0, 0, null, ex.Message);
					monitor.ReportError (GettextCatalog.GetString ("Could not load file '{0}': {1}",
					                                               template.FilePath, ex.Message), null);
					return result;
				}
			}
			
			if (result.Append (merge (conf, doc)).ErrorCount > 0)
				return result;
			
			try {
				EnsureDirectoryForFile (outPath);
				using (XmlTextWriter writer = new XmlTextWriter (outPath, Encoding.UTF8)) {
					writer.Formatting = Formatting.Indented;
					doc.Write (writer);
				}
			} catch (Exception ex) {
				result.AddError (outPath, 0, 0, null, ex.Message);
				monitor.ReportError (GettextCatalog.GetString ("Could not write file '{0}'", outPath), ex);
			}
			return result;
		}