// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.IO; using System.Security.Cryptography.X509Certificates; using System.Diagnostics; using System.Runtime.InteropServices; namespace iPhonePackager { public partial class ToolsHub : Form { [DllImport("user32.dll")] public static extern int MessageBox(IntPtr hWnd, String Text, String Caption, uint type); protected Dictionary CheckStateImages = new Dictionary(); public static string IpaFilter = "iOS packaged applications (*.ipa)|*.ipa|All Files (*.*)|*.*"; public static string CertificateRequestFilter = "Certificate Request (*.csr)|*.csr|All Files (*.*)|*.*"; public static string MobileProvisionFilter = "Mobile provision files (*.mobileprovision)|*.mobileprovision|All files (*.*)|*.*"; public static string CertificatesFilter = "Code signing certificates (*.cer;*.p12)|*.cer;*.p12|All files (*.*)|*.*"; public static string KeysFilter = "Keys (*.key;*.p12)|*.key;*.p12|All files (*.*)|*.*"; public static string JustXmlKeysFilter = "XML Public/Private Key Pair (*.key)|*.key|All Files (*.*)|*.*"; public static string PListFilter = "Property Lists (*.plist)|*.plist|All Files (*.*)|*.*"; public static string JustP12Certificates = "Code signing certificates (*.p12)|*.p12|All files (*.*)|*.*"; //@TODO public static string ChoosingFilesToInstallDirectory = null; public static string ChoosingIpaDirectory = null; /// /// Shows a file dialog, preserving the current working directory of the application /// /// Filter string for the dialog /// Display title for the dialog /// Default extension /// Initial filename (can be null) /// Starting directory for the dialog (will be updated on a successful pick) /// (out) The picked filename, or null if the dialog was cancelled /// True on a successful pick (filename is in OutFilename) public static bool ShowOpenFileDialog(string Filter, string Title, string DefaultExtension, string StartingFilename, ref string SavedDirectory, out string OutFilename) { // Save off the current working directory string CWD = Directory.GetCurrentDirectory(); // Show the dialog System.Windows.Forms.OpenFileDialog OpenDialog = new System.Windows.Forms.OpenFileDialog(); OpenDialog.DefaultExt = DefaultExtension; OpenDialog.FileName = (StartingFilename != null) ? StartingFilename : ""; OpenDialog.Filter = Filter; OpenDialog.Title = Title; OpenDialog.InitialDirectory = (SavedDirectory != null) ? SavedDirectory : CWD; bool bDialogSucceeded = OpenDialog.ShowDialog() == DialogResult.OK; // Restore the current working directory Directory.SetCurrentDirectory(CWD); if (bDialogSucceeded) { SavedDirectory = Path.GetDirectoryName(OpenDialog.FileName); OutFilename = OpenDialog.FileName; return true; } else { OutFilename = null; return false; } } public ToolsHub() { InitializeComponent(); CheckStateImages.Add(0, iPhonePackager.Properties.Resources.GreyCheck); CheckStateImages.Add(1, iPhonePackager.Properties.Resources.YellowCheck); CheckStateImages.Add(2, iPhonePackager.Properties.Resources.GreenCheck); Text = Config.AppDisplayName + " Wizard"; } public static ToolsHub CreateShowingTools() { ToolsHub Result = new ToolsHub(); Result.tabControl1.SelectTab(Result.tabPage1); return Result; } private void ToolsHub_Shown(object sender, EventArgs e) { // Check the status of the various steps UpdateStatus(); CreateCSRButton.Enabled = ImportMobileProvisionButton.Enabled = ImportProvisionButton2.Enabled = ImportCertificateButton.Enabled = ImportCertificateButton2.Enabled = !string.IsNullOrEmpty(Config.ProjectFile); } void UpdateStatus() { MobileProvision Provision; X509Certificate2 Cert; bool bOverridesExists; bool bNameMatch; CodeSignatureBuilder.FindRequiredFiles(out Provision, out Cert, out bOverridesExists, out bNameMatch, false); int ProvisionVal = 0; if (Provision != null) { ProvisionVal = 1; if (bNameMatch) { ProvisionVal = 2; } } int CertVal = 0; if (Cert != null) { CertVal = 2; } MobileProvisionCheck2.Image = MobileProvisionCheck.Image = CheckStateImages[ProvisionVal]; CertificatePresentCheck2.Image = CertificatePresentCheck.Image = CheckStateImages[CertVal]; // OverridesPresentCheck2.Image = OverridesPresentCheck.Image = CheckStateImages[bOverridesExists]; // ReadyToPackageButton.Enabled = /*bOverridesExists && */(Provision != null) && (Cert != null); } private void CreateCSRButton_Click(object sender, EventArgs e) { GenerateSigningRequestDialog Dlg = new GenerateSigningRequestDialog(); Dlg.ShowDialog(this); } public static void ShowError(string Message) { System.Windows.Forms.MessageBox.Show(Message, Config.AppDisplayName, MessageBoxButtons.OK, MessageBoxIcon.Error); } public static bool IsProfileForDistribution(MobileProvision Provision) { return CryptoAdapter.GetCommonNameFromCert(Provision.DeveloperCertificates[0]).IndexOf("iPhone Distribution", StringComparison.InvariantCultureIgnoreCase) >= 0; } public static void TryInstallingMobileProvision(string ProvisionFilename, bool ShowPrompt = true) { if (!String.IsNullOrEmpty(ProvisionFilename) || ShowOpenFileDialog(MobileProvisionFilter, "Choose a mobile provision to install", "mobileprovision", "", ref ChoosingFilesToInstallDirectory, out ProvisionFilename)) { try { // Determine if this is a development or distribution certificate bool bIsDistribution = false; MobileProvision Provision = MobileProvisionParser.ParseFile(ProvisionFilename); bIsDistribution = IsProfileForDistribution(Provision); // use the input filename if the GameName is empty string DestName = string.IsNullOrEmpty(Program.GameName) ? Path.GetFileNameWithoutExtension(ProvisionFilename) : Program.GameName; // Copy the file into the destination location string EffectivePrefix = bIsDistribution ? "Distro_" : Config.SigningPrefix; string DestinationFilename = Path.Combine(Config.ProvisionDirectory, EffectivePrefix + DestName + ".mobileprovision"); DestinationFilename = DestinationFilename.Replace("\\", "/"); if (File.Exists(DestinationFilename)) { MobileProvision OldProvision = MobileProvisionParser.ParseFile(DestinationFilename); string MessagePrompt = String.Format( "{0} already contains a {1} mobile provision file. Do you want to replace the provision '{2}' with '{3}'?", Config.BuildDirectory, bIsDistribution ? "distribution" : "development", OldProvision.ProvisionName, Provision.ProvisionName); if (ShowPrompt && System.Windows.Forms.MessageBox.Show(MessagePrompt, Config.AppDisplayName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) { return; } if (DestinationFilename != ProvisionFilename) { FileOperations.DeleteFile(DestinationFilename); } } if (DestinationFilename != ProvisionFilename) { FileOperations.CopyRequiredFile(ProvisionFilename, DestinationFilename); } } catch (Exception ex) { ShowError(String.Format("Encountered an error '{0} while trying to install a mobile provision", ex.Message)); } } } private void ImportMobileProvisionButton_Click(object sender, EventArgs e) { TryInstallingMobileProvision(null); UpdateStatus(); } static private string CertToolData = ""; static public void OutputReceivedCertToolProcessCall(Object Sender, DataReceivedEventArgs Line) { if ((Line != null) && !String.IsNullOrEmpty(Line.Data)) { CertToolData += Line.Data + "\n"; } } public static void TryInstallingCertificate_PromptForKey(string CertificateFilename, bool ShowPrompt = true) { try { if (!String.IsNullOrEmpty(CertificateFilename) || ShowOpenFileDialog(CertificatesFilter, "Choose a code signing certificate to import", "", "", ref ChoosingFilesToInstallDirectory, out CertificateFilename)) { if (Environment.OSVersion.Platform == PlatformID.MacOSX || Environment.OSVersion.Platform == PlatformID.Unix) { // run certtool y to get the currently installed certificates CertToolData = ""; Process CertTool = new Process(); CertTool.StartInfo.FileName = "/usr/bin/security"; CertTool.StartInfo.UseShellExecute = false; CertTool.StartInfo.Arguments = "import \"" + CertificateFilename +"\" -k login.keychain"; CertTool.StartInfo.RedirectStandardOutput = true; CertTool.OutputDataReceived += new DataReceivedEventHandler(OutputReceivedCertToolProcessCall); CertTool.Start(); CertTool.BeginOutputReadLine(); CertTool.WaitForExit(); if (CertTool.ExitCode != 0) { // todo: provide some feedback that it failed } Console.Write(CertToolData); } else { // Load the certificate string CertificatePassword = ""; X509Certificate2 Cert = null; try { Cert = new X509Certificate2(CertificateFilename, CertificatePassword, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); } catch (System.Security.Cryptography.CryptographicException ex) { // Try once with a password if (PasswordDialog.RequestPassword(out CertificatePassword)) { Cert = new X509Certificate2(CertificateFilename, CertificatePassword, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); } else { // User cancelled dialog, rethrow throw ex; } } // If the certificate doesn't have a private key pair, ask the user to provide one if (!Cert.HasPrivateKey) { string ErrorMsg = "Certificate does not include a private key and cannot be used to code sign"; // Prompt for a key pair if (MessageBox(new IntPtr(0), "Next, please choose the key pair that you made when generating the certificate request.", Config.AppDisplayName, 0x00000000 | 0x00000040 | 0x00001000 | 0x00010000) == 1) { string KeyFilename; if (ShowOpenFileDialog(KeysFilter, "Choose the key pair that belongs with the signing certificate", "", "", ref ChoosingFilesToInstallDirectory, out KeyFilename)) { Cert = CryptoAdapter.CombineKeyAndCert(CertificateFilename, KeyFilename); if (Cert.HasPrivateKey) { ErrorMsg = null; } } } if (ErrorMsg != null) { throw new Exception(ErrorMsg); } } // Add the certificate to the store X509Store Store = new X509Store(); Store.Open(OpenFlags.ReadWrite); Store.Add(Cert); Store.Close(); } } } catch (Exception ex) { string ErrorMsg = String.Format("Failed to load or install certificate due to an error: '{0}'", ex.Message); Program.Error(ErrorMsg); System.Threading.Thread.Sleep(500); MessageBox(new IntPtr(0), ErrorMsg, Config.AppDisplayName, 0x00000000 | 0x00000010 | 0x00001000 | 0x00010000); } } private void ImportCertificateButton_Click(object sender, EventArgs e) { TryInstallingCertificate_PromptForKey(null); UpdateStatus(); } private void EditPlistButton_Click(object sender, EventArgs e) { ConfigureMobileGame Dlg = new ConfigureMobileGame(); Dlg.ShowDialog(this); UpdateStatus(); } private void CancelThisFormButton_Click(object sender, EventArgs e) { DialogResult = DialogResult.Cancel; Close(); } private void ReadyToPackageButton_Click(object sender, EventArgs e) { DialogResult = DialogResult.OK; Close(); } private void InstallIPAButton_Click(object sender, EventArgs e) { string PickedFilename; if (ShowOpenFileDialog(IpaFilter, "Choose an IPA to install", "ipa", "", ref ChoosingIpaDirectory, out PickedFilename)) { Program.ProgressDialog.OnBeginBackgroundWork = delegate { DeploymentHelper.InstallIPAOnConnectedDevices(PickedFilename); }; Program.ProgressDialog.ShowDialog(); } } private void ResignIPAButton_Click(object sender, EventArgs e) { GraphicalResignTool ResignTool = GraphicalResignTool.GetActiveInstance(); ResignTool.TabBook.SelectTab(ResignTool.ResignPage); ResignTool.Show(); } private void ProvisionCertToolsButton_Click(object sender, EventArgs e) { // GraphicalResignTool ResignTool = GraphicalResignTool.GetActiveInstance(); // ResignTool.TabBook.SelectTab(ResignTool.ProvisionCertPage); // ResignTool.Show(); } private void OtherDeployToolsButton_Click(object sender, EventArgs e) { GraphicalResignTool ResignTool = GraphicalResignTool.GetActiveInstance(); ResignTool.TabBook.SelectTab(ResignTool.DeploymentPage); ResignTool.Show(); } void OpenHelpWebsite() { string Target = "http://udn.epicgames.com/Three/AppleiOSProvisioning.html"; ProcessStartInfo PSI = new ProcessStartInfo(Target); Process.Start(PSI); } private void ToolsHub_HelpButtonClicked(object sender, CancelEventArgs e) { OpenHelpWebsite(); } private void ToolsHub_KeyUp(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.F1) { OpenHelpWebsite(); } } private void HyperlinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { string Target = (sender as LinkLabel).Tag as string; ProcessStartInfo PSI = new ProcessStartInfo(Target); Process.Start(PSI); } private void InstallIPA_Click(object sender, EventArgs e) { InstallIPAButton_Click(sender, e); } } }