Пример #1
0
    public override void Package(ProjectParams Params, DeploymentContext SC, int WorkingCL)
    {
        Log("Package {0}", Params.RawProjectPath);

        // ensure the ue4game binary exists, if applicable
        string FullExePath = CombinePaths(Path.GetDirectoryName(Params.ProjectGameExeFilename), SC.StageExecutables[0] + (UnrealBuildTool.BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac ? ".stub" : ""));

        if (!SC.IsCodeBasedProject && !FileExists_NoExceptions(FullExePath))
        {
            Log("Failed to find game binary " + FullExePath);
            throw new AutomationException(ErrorCodes.Error_MissingExecutable, "Stage Failed. Could not find binary {0}. You may need to build the UE4 project with your target configuration and platform.", FullExePath);
        }

        //@TODO: We should be able to use this code on both platforms, when the following issues are sorted:
        //   - Raw executable is unsigned & unstripped (need to investigate adding stripping to IPP)
        //   - IPP needs to be able to codesign a raw directory
        //   - IPP needs to be able to take a .app directory instead of a Payload directory when doing RepackageFromStage (which would probably be renamed)
        //   - Some discrepancy in the loading screen pngs that are getting packaged, which needs to be investigated
        //   - Code here probably needs to be updated to write 0 byte files as 1 byte (difference with IPP, was required at one point when using Ionic.Zip to prevent issues on device, maybe not needed anymore?)
        if (UnrealBuildTool.BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Mac)
        {
            // copy in all of the artwork and plist
            var DeployHandler = UEBuildDeploy.GetBuildDeploy(UnrealTargetPlatform.IOS);

            DeployHandler.PrepForUATPackageOrDeploy(Params.ShortProjectName,
                                                    Path.GetDirectoryName(Params.RawProjectPath),
                                                    CombinePaths(Path.GetDirectoryName(Params.ProjectGameExeFilename), SC.StageExecutables[0]),
                                                    CombinePaths(SC.LocalRoot, "Engine"),
                                                    Params.Distribution,
                                                    "",
                                                    false);

            // figure out where to pop in the staged files
            string AppDirectory = string.Format("{0}/Payload/{1}.app",
                                                Path.GetDirectoryName(Params.ProjectGameExeFilename),
                                                Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename));

            // delete the old cookeddata
            InternalUtils.SafeDeleteDirectory(AppDirectory + "/cookeddata", true);
            InternalUtils.SafeDeleteFile(AppDirectory + "/ue4commandline.txt", true);

            if (!Params.IterativeDeploy)
            {
                // copy the Staged files to the AppDirectory
                string[] StagedFiles = Directory.GetFiles(SC.StageDirectory, "*", SearchOption.AllDirectories);
                foreach (string Filename in StagedFiles)
                {
                    string DestFilename = Filename.Replace(SC.StageDirectory, AppDirectory);
                    Directory.CreateDirectory(Path.GetDirectoryName(DestFilename));
                    InternalUtils.SafeCopyFile(Filename, DestFilename, true);
                }
            }
            else
            {
                // copy just the root stage directory files
                string[] StagedFiles = Directory.GetFiles(SC.StageDirectory, "*", SearchOption.TopDirectoryOnly);
                foreach (string Filename in StagedFiles)
                {
                    string DestFilename = Filename.Replace(SC.StageDirectory, AppDirectory);
                    Directory.CreateDirectory(Path.GetDirectoryName(DestFilename));
                    InternalUtils.SafeCopyFile(Filename, DestFilename, true);
                }
            }
        }

        if (SC.StageTargetConfigurations.Count != 1)
        {
            throw new AutomationException("iOS is currently only able to package one target configuration at a time, but StageTargetConfigurations contained {0} configurations", SC.StageTargetConfigurations.Count);
        }
        bCreatedIPA = false;
        bool bNeedsIPA = false;

        if (Params.IterativeDeploy)
        {
            String NonUFSManifestPath = SC.GetNonUFSDeploymentDeltaPath();
            // check to determine if we need to update the IPA
            if (File.Exists(NonUFSManifestPath))
            {
                string   NonUFSFiles = File.ReadAllText(NonUFSManifestPath);
                string[] Lines       = NonUFSFiles.Split('\n');
                bNeedsIPA = Lines.Length > 0 && !string.IsNullOrWhiteSpace(Lines[0]);
            }
        }

        if (UnrealBuildTool.BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac)
        {
            var TargetConfiguration = SC.StageTargetConfigurations[0];
            var ProjectIPA          = MakeIPAFileName(TargetConfiguration, Params);
            var ProjectStub         = Path.GetFullPath(Params.ProjectGameExeFilename);

            // package a .ipa from the now staged directory
            var IPPExe = CombinePaths(CmdEnv.LocalRoot, "Engine/Binaries/DotNET/IOS/IPhonePackager.exe");

            Log("ProjectName={0}", Params.ShortProjectName);
            Log("ProjectStub={0}", ProjectStub);
            Log("ProjectIPA={0}", ProjectIPA);
            Log("IPPExe={0}", IPPExe);

            bool cookonthefly = Params.CookOnTheFly || Params.SkipCookOnTheFly;

            // if we are incremental check to see if we need to even update the IPA
            if (!Params.IterativeDeploy || !File.Exists(ProjectIPA) || bNeedsIPA)
            {
                // delete the .ipa to make sure it was made
                DeleteFile(ProjectIPA);

                bCreatedIPA = true;

                if (RemoteToolChain.bUseRPCUtil)
                {
                    string IPPArguments = "RepackageFromStage \"" + (Params.IsCodeBasedProject ? Params.RawProjectPath : "Engine") + "\"";
                    IPPArguments += " -config " + TargetConfiguration.ToString();

                    if (TargetConfiguration == UnrealTargetConfiguration.Shipping)
                    {
                        IPPArguments += " -compress=best";
                    }

                    // Determine if we should sign
                    bool bNeedToSign = GetCodeSignDesirability(Params);

                    if (!String.IsNullOrEmpty(Params.BundleName))
                    {
                        // Have to sign when a bundle name is specified
                        bNeedToSign   = true;
                        IPPArguments += " -bundlename " + Params.BundleName;
                    }

                    if (bNeedToSign)
                    {
                        IPPArguments += " -sign";
                        if (Params.Distribution)
                        {
                            IPPArguments += " -distribution";
                        }
                    }

                    IPPArguments += (cookonthefly ? " -cookonthefly" : "");
                    IPPArguments += " -stagedir \"" + CombinePaths(Params.BaseStageDirectory, "IOS") + "\"";
                    IPPArguments += " -project \"" + Params.RawProjectPath + "\"";
                    if (Params.IterativeDeploy)
                    {
                        IPPArguments += " -iterate";
                    }

                    RunAndLog(CmdEnv, IPPExe, IPPArguments);
                }
                else
                {
                    List <string> IPPArguments = new List <string>();
                    IPPArguments.Add("RepackageFromStage");
                    IPPArguments.Add(Params.IsCodeBasedProject ? Params.RawProjectPath : "Engine");
                    IPPArguments.Add("-config");
                    IPPArguments.Add(TargetConfiguration.ToString());

                    if (TargetConfiguration == UnrealTargetConfiguration.Shipping)
                    {
                        IPPArguments.Add("-compress=best");
                    }

                    // Determine if we should sign
                    bool bNeedToSign = GetCodeSignDesirability(Params);

                    if (!String.IsNullOrEmpty(Params.BundleName))
                    {
                        // Have to sign when a bundle name is specified
                        bNeedToSign = true;
                        IPPArguments.Add("-bundlename");
                        IPPArguments.Add(Params.BundleName);
                    }

                    if (bNeedToSign)
                    {
                        IPPArguments.Add("-sign");
                    }

                    if (cookonthefly)
                    {
                        IPPArguments.Add(" -cookonthefly");
                    }
                    IPPArguments.Add(" -stagedir");
                    IPPArguments.Add(CombinePaths(Params.BaseStageDirectory, "IOS"));
                    IPPArguments.Add(" -project");
                    IPPArguments.Add(Params.RawProjectPath);
                    if (Params.IterativeDeploy)
                    {
                        IPPArguments.Add(" -iterate");
                    }

                    if (RunIPP(IPPArguments.ToArray()) != 0)
                    {
                        throw new AutomationException("IPP Failed");
                    }
                }
            }

            // verify the .ipa exists
            if (!FileExists(ProjectIPA))
            {
                throw new AutomationException(ErrorCodes.Error_FailedToCreateIPA, "PACKAGE FAILED - {0} was not created", ProjectIPA);
            }

            if (WorkingCL > 0)
            {
                // Open files for add or edit
                var ExtraFilesToCheckin = new List <string>
                {
                    ProjectIPA
                };

                // check in the .ipa along with everything else
                UE4Build.AddBuildProductsToChangelist(WorkingCL, ExtraFilesToCheckin);
            }

            //@TODO: This automatically deploys after packaging, useful for testing on PC when iterating on IPP
            //Deploy(Params, SC);
        }
        else
        {
            // create the ipa
            string IPAName = CombinePaths(Path.GetDirectoryName(Params.RawProjectPath), "Binaries", "IOS", (Params.Distribution ? "Distro_" : "") + Params.ShortProjectName + (SC.StageTargetConfigurations[0] != UnrealTargetConfiguration.Development ? ("-IOS-" + SC.StageTargetConfigurations[0].ToString()) : "") + ".ipa");

            if (!Params.IterativeDeploy || !File.Exists(IPAName) || bNeedsIPA)
            {
                bCreatedIPA = true;

                // code sign the app
                CodeSign(Path.GetDirectoryName(Params.ProjectGameExeFilename), Params.IsCodeBasedProject ? Params.ShortProjectName : Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename), Params.RawProjectPath, SC.StageTargetConfigurations[0], SC.LocalRoot, Params.ShortProjectName, Path.GetDirectoryName(Params.RawProjectPath), SC.IsCodeBasedProject, Params.Distribution);

                // now generate the ipa
                PackageIPA(Path.GetDirectoryName(Params.ProjectGameExeFilename), Params.IsCodeBasedProject ? Params.ShortProjectName : Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename), Params.ShortProjectName, Path.GetDirectoryName(Params.RawProjectPath), SC.StageTargetConfigurations[0], Params.Distribution);
            }
        }

        PrintRunTime();
    }
    public override void Deploy(ProjectParams Params, DeploymentContext SC)
    {
        string DeviceArchitecture = GetBestDeviceArchitecture(Params);
        string GPUArchitecture    = GetBestGPUArchitecture(Params);

        string ApkName = GetFinalApkName(Params, SC.StageExecutables[0], true, DeviceArchitecture, GPUArchitecture);

        // make sure APK is up to date (this is fast if so)
        var Deploy = UEBuildDeploy.GetBuildDeploy(UnrealTargetPlatform.Android);

        if (!Params.Prebuilt)
        {
            string CookFlavor = SC.FinalCookPlatform.IndexOf("_") > 0 ? SC.FinalCookPlatform.Substring(SC.FinalCookPlatform.IndexOf("_")) : "";
            string SOName     = GetSONameWithoutArchitecture(Params, SC.StageExecutables[0]);
            Deploy.PrepForUATPackageOrDeploy(Params.ShortProjectName, SC.ProjectRoot, SOName, SC.LocalRoot + "/Engine", Params.Distribution, CookFlavor, true);
        }

        // now we can use the apk to get more info
        string PackageName = GetPackageInfo(ApkName, false);

        // try uninstalling an old app with the same identifier.
        int    SuccessCode          = 0;
        string UninstallCommandline = "uninstall " + PackageName;

        RunAndLogAdbCommand(Params, UninstallCommandline, out SuccessCode);

        // install the apk
        string InstallCommandline = "install \"" + ApkName + "\"";
        string InstallOutput      = RunAndLogAdbCommand(Params, InstallCommandline, out SuccessCode);
        int    FailureIndex       = InstallOutput.IndexOf("Failure");

        // adb install doesn't always return an error code on failure, and instead prints "Failure", followed by an error code.
        if (SuccessCode != 0 || FailureIndex != -1)
        {
            string ErrorMessage = String.Format("Installation of apk '{0}' failed", ApkName);
            if (FailureIndex != -1)
            {
                string FailureString = InstallOutput.Substring(FailureIndex + 7).Trim();
                if (FailureString != "")
                {
                    ErrorMessage += ": " + FailureString;
                }
            }

            ErrorReporter.Error(ErrorMessage, (int)ErrorCodes.Error_AppInstallFailed);
            throw new AutomationException(ErrorMessage);
        }

        // update the ue4commandline.txt
        // update and deploy ue4commandline.txt
        // always delete the existing commandline text file, so it doesn't reuse an old one
        string IntermediateCmdLineFile = CombinePaths(SC.StageDirectory, "UE4CommandLine.txt");

        Project.WriteStageCommandline(IntermediateCmdLineFile, Params, SC);

        // Setup the OBB name and add the storage path (queried from the device) to it
        string        DeviceStorageQueryCommand = GetStorageQueryCommand();
        ProcessResult Result          = RunAdbCommand(Params, DeviceStorageQueryCommand, null, ERunOptions.AppMustExist);
        String        StorageLocation = Result.Output.Trim();
        string        DeviceObbName   = StorageLocation + "/" + GetDeviceObbName(ApkName);

        // copy files to device if we were staging
        if (SC.Stage)
        {
            // cache some strings
            string BaseCommandline  = "push";
            string RemoteDir        = StorageLocation + "/UE4Game/" + Params.ShortProjectName;
            string UE4GameRemoteDir = StorageLocation + "/UE4Game/" + Params.ShortProjectName;

            // make sure device is at a clean state
            RunAdbCommand(Params, "shell rm -r " + RemoteDir);
            RunAdbCommand(Params, "shell rm -r " + UE4GameRemoteDir);

            // Copy UFS files..
            string[] Files = Directory.GetFiles(SC.StageDirectory, "*", SearchOption.AllDirectories);
            System.Array.Sort(Files);

            // Find all the files we exclude from copying. And include
            // the directories we need to individually copy.
            HashSet <string>   ExcludedFiles = new HashSet <string>();
            SortedSet <string> IndividualCopyDirectories
                = new SortedSet <string>((IComparer <string>) new LongestFirst());
            foreach (string Filename in Files)
            {
                bool Exclude = false;
                // Don't push the apk, we install it
                Exclude |= Path.GetExtension(Filename).Equals(".apk", StringComparison.InvariantCultureIgnoreCase);
                // For excluded files we add the parent dirs to our
                // tracking of stuff to individually copy.
                if (Exclude)
                {
                    ExcludedFiles.Add(Filename);
                    // We include all directories up to the stage root in having
                    // to individually copy the files.
                    for (string FileDirectory = Path.GetDirectoryName(Filename);
                         !FileDirectory.Equals(SC.StageDirectory);
                         FileDirectory = Path.GetDirectoryName(FileDirectory))
                    {
                        if (!IndividualCopyDirectories.Contains(FileDirectory))
                        {
                            IndividualCopyDirectories.Add(FileDirectory);
                        }
                    }
                    if (!IndividualCopyDirectories.Contains(SC.StageDirectory))
                    {
                        IndividualCopyDirectories.Add(SC.StageDirectory);
                    }
                }
            }

            // The directories are sorted above in "deepest" first. We can
            // therefore start copying those individual dirs which will
            // recreate the tree. As the subtrees will get copied at each
            // possible individual level.
            HashSet <string> EntriesToDeploy = new HashSet <string>();
            foreach (string DirectoryName in IndividualCopyDirectories)
            {
                string[] Entries
                    = Directory.GetFileSystemEntries(DirectoryName, "*", SearchOption.TopDirectoryOnly);
                foreach (string Entry in Entries)
                {
                    // We avoid excluded files and the individual copy dirs
                    // (the individual copy dirs will get handled as we iterate).
                    if (ExcludedFiles.Contains(Entry) || IndividualCopyDirectories.Contains(Entry))
                    {
                        continue;
                    }
                    else
                    {
                        EntriesToDeploy.Add(Entry);
                    }
                }
            }
            if (EntriesToDeploy.Count == 0)
            {
                EntriesToDeploy.Add(SC.StageDirectory);
            }

            // We now have a minimal set of file & dir entries we need
            // to deploy. Files we deploy will get individually copied
            // and dirs will get the tree copies by default (that's
            // what ADB does).
            HashSet <ProcessResult> DeployCommands = new HashSet <ProcessResult>();
            foreach (string Entry in EntriesToDeploy)
            {
                string FinalRemoteDir = RemoteDir;
                string RemotePath     = Entry.Replace(SC.StageDirectory, FinalRemoteDir).Replace("\\", "/");
                string Commandline    = string.Format("{0} \"{1}\" \"{2}\"", BaseCommandline, Entry, RemotePath);
                // We run deploy commands in parallel to maximize the connection
                // throughput.
                DeployCommands.Add(
                    RunAdbCommand(Params, Commandline, null,
                                  ERunOptions.Default | ERunOptions.NoWaitForExit));
                // But we limit the parallel commands to avoid overwhelming
                // memory resources.
                if (DeployCommands.Count == DeployMaxParallelCommands)
                {
                    while (DeployCommands.Count > DeployMaxParallelCommands / 2)
                    {
                        Thread.Sleep(10);
                        DeployCommands.RemoveWhere(
                            delegate(ProcessResult r)
                        {
                            return(r.HasExited);
                        });
                    }
                }
            }
            foreach (ProcessResult deploy_result in DeployCommands)
            {
                deploy_result.WaitForExit();
            }

            // delete the .obb file, since it will cause nothing we just deployed to be used
            RunAdbCommand(Params, "shell rm " + DeviceObbName);
        }
        else if (SC.Archive)
        {
            // deploy the obb if there is one
            string ObbPath = Path.Combine(SC.StageDirectory, GetFinalObbName(ApkName));
            if (File.Exists(ObbPath))
            {
                // cache some strings
                string BaseCommandline = "push";

                string Commandline = string.Format("{0} \"{1}\" \"{2}\"", BaseCommandline, ObbPath, DeviceObbName);
                RunAdbCommand(Params, Commandline);
            }
        }
        else
        {
            // cache some strings
            string BaseCommandline = "push";
            string RemoteDir       = StorageLocation + "/UE4Game/" + Params.ShortProjectName;

            string FinalRemoteDir = RemoteDir;

            /*
             * // handle the special case of the UE4Commandline.txt when using content only game (UE4Game)
             * if (!Params.IsCodeBasedProject)
             * {
             *      FinalRemoteDir = "/mnt/sdcard/UE4Game";
             * }
             */

            string RemoteFilename = IntermediateCmdLineFile.Replace(SC.StageDirectory, FinalRemoteDir).Replace("\\", "/");
            string Commandline    = string.Format("{0} \"{1}\" \"{2}\"", BaseCommandline, IntermediateCmdLineFile, RemoteFilename);
            RunAdbCommand(Params, Commandline);
        }
    }
Пример #3
0
    public override void Package(ProjectParams Params, DeploymentContext SC, int WorkingCL)
    {
        string[] Architectures     = UnrealBuildTool.AndroidToolChain.GetAllArchitectures();
        string[] GPUArchitectures  = UnrealBuildTool.AndroidToolChain.GetAllGPUArchitectures();
        bool     bMakeSeparateApks = UnrealBuildTool.Android.UEDeployAndroid.ShouldMakeSeparateApks();

        foreach (string Architecture in Architectures)
        {
            foreach (string GPUArchitecture in GPUArchitectures)
            {
                string ApkName   = GetFinalApkName(Params, SC.StageExecutables[0], true, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "");
                string BatchName = GetFinalBatchName(ApkName, Params, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "");

                // packaging just takes a pak file and makes it the .obb
                UEBuildConfiguration.bOBBinAPK = Params.OBBinAPK;         // Make sure this setting is sync'd pre-build
                var Deploy = UEBuildDeploy.GetBuildDeploy(UnrealTargetPlatform.Android);
                if (!Params.Prebuilt)
                {
                    string CookFlavor = SC.FinalCookPlatform.IndexOf("_") > 0 ? SC.FinalCookPlatform.Substring(SC.FinalCookPlatform.IndexOf("_")) : "";
                    string SOName     = GetSONameWithoutArchitecture(Params, SC.StageExecutables[0]);
                    Deploy.PrepForUATPackageOrDeploy(Params.ShortProjectName, SC.ProjectRoot, SOName, SC.LocalRoot + "/Engine", Params.Distribution, CookFlavor);
                }

                // first, look for a .pak file in the staged directory
                string[] PakFiles = Directory.GetFiles(SC.StageDirectory, "*.pak", SearchOption.AllDirectories);

                bool bHasPakFile = PakFiles.Length >= 1;

                // for now, we only support 1 pak/obb file
                if (PakFiles.Length > 1)
                {
                    string ErrorString = String.Format("Can't package for Android with 0 or more than 1 pak file (found {0} pak files in {1})", PakFiles.Length, SC.StageDirectory);
                    ErrorReporter.Error(ErrorString, (int)ErrorCodes.Error_OnlyOneObbFileSupported);
                    throw new AutomationException(ErrorString);
                }

                string LocalObbName  = GetFinalObbName(ApkName);
                string DeviceObbName = GetDeviceObbName(ApkName);

                // Always delete the target OBB file if it exists
                if (File.Exists(LocalObbName))
                {
                    File.Delete(LocalObbName);
                }

                if (!Params.OBBinAPK && bHasPakFile)
                {
                    Log("Creating {0} from {1}", LocalObbName, PakFiles[0]);
                    File.Copy(PakFiles[0], LocalObbName);
                }

                Log("Writing bat for install with {0}", Params.OBBinAPK ? "OBB in APK" : "OBB separate");
                string PackageName = GetPackageInfo(ApkName, false);
                // make a batch file that can be used to install the .apk and .obb files
                string[] BatchLines = new string[] {
                    "setlocal",
                    "set ADB=%ANDROID_HOME%\\platform-tools\\adb.exe",
                    "set DEVICE=",
                    "if not \"%1\"==\"\" set DEVICE=-s %1",
                    "for /f \"delims=\" %%A in ('adb " + GetStorageQueryCommand() + "') do @set STORAGE=%%A",
                    "%ADB% %DEVICE% uninstall " + PackageName,
                    "%ADB% %DEVICE% install " + Path.GetFileName(ApkName),
                    "@if \"%ERRORLEVEL%\" NEQ \"0\" goto Error",
                    "%ADB% %DEVICE% shell rm -r %STORAGE%/" + Params.ShortProjectName,
                    "%ADB% %DEVICE% shell rm -r %STORAGE%/UE4Game/UE4CommandLine.txt",             // we need to delete the commandline in UE4Game or it will mess up loading
                    "%ADB% %DEVICE% shell rm -r %STORAGE%/obb/" + PackageName,
                    Params.OBBinAPK || !bHasPakFile ? "" : "%ADB% %DEVICE% push " + Path.GetFileName(LocalObbName) + " %STORAGE%/" + DeviceObbName,
                    Params.OBBinAPK || !bHasPakFile ? "" : "if \"%ERRORLEVEL%\" NEQ \"0\" goto Error",
                    "goto:eof",
                    ":Error",
                    "@echo.",
                    "@echo There was an error installing the game or the obb file. Look above for more info.",
                    "@echo.",
                    "@echo Things to try:",
                    "@echo Check that the device (and only the device) is listed with \"%ADB$ devices\" from a command prompt.",
                    "@echo Make sure all Developer options look normal on the device",
                    "@echo Check that the device has an SD card.",
                    "@pause"
                };
                File.WriteAllLines(BatchName, BatchLines);
            }
        }

        PrintRunTime();
    }
    public override void Package(ProjectParams Params, DeploymentContext SC, int WorkingCL)
    {
        string[] Architectures         = UnrealBuildTool.AndroidToolChain.GetAllArchitectures();
        string[] GPUArchitectures      = UnrealBuildTool.AndroidToolChain.GetAllGPUArchitectures();
        bool     bMakeSeparateApks     = UnrealBuildTool.Android.UEDeployAndroid.ShouldMakeSeparateApks();
        bool     bPackageDataInsideApk = UnrealBuildTool.Android.UEDeployAndroid.PackageDataInsideApk(false);

        var Deploy = UEBuildDeploy.GetBuildDeploy(UnrealTargetPlatform.Android);

        string BaseApkName = GetFinalApkName(Params, SC.StageExecutables[0], true, "", "");

        Log("BaseApkName = {0}", BaseApkName);

        // Create main OBB with entire contents of staging dir. This
        // includes any PAK files, movie files, etc.

        string LocalObbName = SC.StageDirectory.TrimEnd(new char[] { '/', '\\' }) + ".obb";

        // Always delete the target OBB file if it exists
        if (File.Exists(LocalObbName))
        {
            File.Delete(LocalObbName);
        }

        // Now create the OBB as a ZIP archive.
        Log("Creating {0} from {1}", LocalObbName, SC.StageDirectory);
        using (ZipFile ObbFile = new ZipFile(LocalObbName))
        {
            ObbFile.CompressionMethod = CompressionMethod.None;
            ObbFile.CompressionLevel  = Ionic.Zlib.CompressionLevel.None;
            int ObbFileCount = 0;
            ObbFile.AddProgress +=
                delegate(object sender, AddProgressEventArgs e)
            {
                if (e.EventType == ZipProgressEventType.Adding_AfterAddEntry)
                {
                    ObbFileCount += 1;
                    Log("[{0}/{1}] Adding {2} to OBB",
                        ObbFileCount, e.EntriesTotal,
                        e.CurrentEntry.FileName);
                }
            };
            ObbFile.AddDirectory(SC.StageDirectory + "/" + SC.ShortProjectName, SC.ShortProjectName);
            ObbFile.Save();
        }

        foreach (string Architecture in Architectures)
        {
            foreach (string GPUArchitecture in GPUArchitectures)
            {
                string ApkName = GetFinalApkName(Params, SC.StageExecutables[0], true, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "");
                if (!SC.IsCodeBasedProject)
                {
                    string UE4GameApkName = GetFinalApkName(Params, SC.StageExecutables[0], false, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "");
                    if (FileExists_NoExceptions(UE4GameApkName) == false)
                    {
                        Log("Failed to find game apk " + UE4GameApkName);
                        AutomationTool.ErrorReporter.Error("Stage Failed.", (int)AutomationTool.ErrorCodes.Error_MissingExecutable);
                        throw new AutomationException("Could not find apk {0}. You may need to build the UE4 project with your target configuration and platform.", UE4GameApkName);
                    }
                }

                string BatchName = GetFinalBatchName(ApkName, Params, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "");

                if (!Params.Prebuilt)
                {
                    string CookFlavor = SC.FinalCookPlatform.IndexOf("_") > 0 ? SC.FinalCookPlatform.Substring(SC.FinalCookPlatform.IndexOf("_")) : "";
                    string SOName     = GetSONameWithoutArchitecture(Params, SC.StageExecutables[0]);
                    Deploy.PrepForUATPackageOrDeploy(Params.ShortProjectName, SC.ProjectRoot, SOName, SC.LocalRoot + "/Engine", Params.Distribution, CookFlavor, false);
                }

                // Create APK specific OBB in case we have a detached OBB.
                string DeviceObbName = "";
                string ObbName       = "";
                if (!bPackageDataInsideApk)
                {
                    DeviceObbName = GetDeviceObbName(ApkName);
                    ObbName       = GetFinalObbName(ApkName);
                    CopyFile(LocalObbName, ObbName);
                }

                // Write install batch file(s).

                string PackageName = GetPackageInfo(ApkName, false);
                // make a batch file that can be used to install the .apk and .obb files
                string[] BatchLines;
                if (Utils.IsRunningOnMono)
                {
                    Log("Writing shell script for install with {0}", bPackageDataInsideApk ? "data in APK" : "separate obb");
                    BatchLines = new string[] {
                        "#!/bin/sh",
                        "cd \"`dirname \"$0\"`\"",
                        "ADB=",
                        "if [ \"$ANDROID_HOME\" != \"\" ]; then ADB=$ANDROID_HOME/platform-tools/adb; else ADB=" + Environment.GetEnvironmentVariable("ANDROID_HOME") + "; fi",
                        "DEVICE=",
                        "if [ \"$1\" != \"\" ]; then DEVICE=\"-s $1\"; fi",
                        "echo",
                        "echo Uninstalling existing application. Failures here can almost always be ignored.",
                        "$ADB $DEVICE uninstall " + PackageName,
                        "echo",
                        "echo Installing existing application. Failures here indicate a problem with the device \\(connection or storage permissions\\) and are fatal.",
                        "$ADB $DEVICE install " + Path.GetFileName(ApkName),
                        "if [ $? -eq 0 ]; then",
                        "\techo",
                        "\techo Removing old data. Failures here are usually fine - indicating the files were not on the device.",
                        "\t$ADB $DEVICE shell 'rm -r $EXTERNAL_STORAGE/UE4Game/" + Params.ShortProjectName + "'",
                        "\t$ADB $DEVICE shell 'rm -r $EXTERNAL_STORAGE/UE4Game/UE4CommandLine.txt" + "'",
                        "\t$ADB $DEVICE shell 'rm -r $EXTERNAL_STORAGE/" + TargetAndroidLocation + PackageName + "'",
                        bPackageDataInsideApk ? "" : "\techo",
                        bPackageDataInsideApk ? "" : "\techo Installing new data. Failures here indicate storage problems \\(missing SD card or bad permissions\\) and are fatal.",
                        bPackageDataInsideApk ? "" : "\tSTORAGE=$(echo \"`$ADB $DEVICE shell 'echo $EXTERNAL_STORAGE'`\" | cat -v | tr -d '^M')",
                        bPackageDataInsideApk ? "" : "\t$ADB $DEVICE push " + Path.GetFileName(ObbName) + " $STORAGE/" + DeviceObbName,
                        bPackageDataInsideApk ? "if [ 1 ]; then" : "\tif [ $? -eq 0 ]; then",
                        "\t\techo",
                        "\t\techo Installation successful",
                        "\t\texit 0",
                        "\tfi",
                        "fi",
                        "echo",
                        "echo There was an error installing the game or the obb file. Look above for more info.",
                        "echo",
                        "echo Things to try:",
                        "echo Check that the device (and only the device) is listed with \\\"$ADB devices\\\" from a command prompt.",
                        "echo Make sure all Developer options look normal on the device",
                        "echo Check that the device has an SD card.",
                        "exit 1"
                    };
                }
                else
                {
                    Log("Writing bat for install with {0}", bPackageDataInsideApk ? "data in APK" : "separate OBB");
                    BatchLines = new string[] {
                        "setlocal",
                        "set ANDROIDHOME=%ANDROID_HOME%",
                        "if \"%ANDROIDHOME%\"==\"\" set ANDROIDHOME=" + Environment.GetEnvironmentVariable("ANDROID_HOME"),
                        "set ADB=%ANDROIDHOME%\\platform-tools\\adb.exe",
                        "set DEVICE=",
                        "if not \"%1\"==\"\" set DEVICE=-s %1",
                        "for /f \"delims=\" %%A in ('%ADB% %DEVICE% " + GetStorageQueryCommand() + "') do @set STORAGE=%%A",
                        "@echo.",
                        "@echo Uninstalling existing application. Failures here can almost always be ignored.",
                        "%ADB% %DEVICE% uninstall " + PackageName,
                        "@echo.",
                        "@echo Installing existing application. Failures here indicate a problem with the device (connection or storage permissions) and are fatal.",
                        "%ADB% %DEVICE% install " + Path.GetFileName(ApkName),
                        "@if \"%ERRORLEVEL%\" NEQ \"0\" goto Error",
                        "%ADB% %DEVICE% shell rm -r %STORAGE%/UE4Game/" + Params.ShortProjectName,
                        "%ADB% %DEVICE% shell rm -r %STORAGE%/UE4Game/UE4CommandLine.txt",                         // we need to delete the commandline in UE4Game or it will mess up loading
                        "%ADB% %DEVICE% shell rm -r %STORAGE%/" + TargetAndroidLocation + PackageName,
                        bPackageDataInsideApk ? "" : "@echo.",
                        bPackageDataInsideApk ? "" : "@echo Installing new data. Failures here indicate storage problems (missing SD card or bad permissions) and are fatal.",
                        bPackageDataInsideApk ? "" : "%ADB% %DEVICE% push " + Path.GetFileName(ObbName) + " %STORAGE%/" + DeviceObbName,
                        bPackageDataInsideApk ? "" : "if \"%ERRORLEVEL%\" NEQ \"0\" goto Error",
                        "@echo.",
                        "@echo Installation successful",
                        "goto:eof",
                        ":Error",
                        "@echo.",
                        "@echo There was an error installing the game or the obb file. Look above for more info.",
                        "@echo.",
                        "@echo Things to try:",
                        "@echo Check that the device (and only the device) is listed with \"%ADB$ devices\" from a command prompt.",
                        "@echo Make sure all Developer options look normal on the device",
                        "@echo Check that the device has an SD card.",
                        "@pause"
                    };
                }
                File.WriteAllLines(BatchName, BatchLines);

                if (Utils.IsRunningOnMono)
                {
                    CommandUtils.FixUnixFilePermissions(BatchName);
                }
            }
        }

        PrintRunTime();
    }
    public override void Package(ProjectParams Params, DeploymentContext SC, int WorkingCL)
    {
        var  Architectures         = UnrealBuildTool.AndroidToolChain.GetAllArchitectures();
        var  GPUArchitectures      = UnrealBuildTool.AndroidToolChain.GetAllGPUArchitectures();
        bool bMakeSeparateApks     = UnrealBuildTool.Android.UEDeployAndroid.ShouldMakeSeparateApks();
        bool bPackageDataInsideApk = UnrealBuildTool.Android.UEDeployAndroid.PackageDataInsideApk(false);

        var Deploy = UEBuildDeploy.GetBuildDeploy(UnrealTargetPlatform.Android);

        string BaseApkName = GetFinalApkName(Params, SC.StageExecutables[0], true, "", "");

        Log("BaseApkName = {0}", BaseApkName);

        // Create main OBB with entire contents of staging dir. This
        // includes any PAK files, movie files, etc.

        string LocalObbName = SC.StageDirectory.TrimEnd(new char[] { '/', '\\' }) + ".obb";

        // Always delete the target OBB file if it exists
        if (File.Exists(LocalObbName))
        {
            File.Delete(LocalObbName);
        }

        // Now create the OBB as a ZIP archive.
        Log("Creating {0} from {1}", LocalObbName, SC.StageDirectory);
        using (ZipFile ObbFile = new ZipFile(LocalObbName))
        {
            ObbFile.CompressionMethod = CompressionMethod.None;
            ObbFile.CompressionLevel  = Ionic.Zlib.CompressionLevel.None;
            int ObbFileCount = 0;
            ObbFile.AddProgress +=
                delegate(object sender, AddProgressEventArgs e)
            {
                if (e.EventType == ZipProgressEventType.Adding_AfterAddEntry)
                {
                    ObbFileCount += 1;
                    Log("[{0}/{1}] Adding {2} to OBB",
                        ObbFileCount, e.EntriesTotal,
                        e.CurrentEntry.FileName);
                }
            };
            ObbFile.AddDirectory(SC.StageDirectory + "/" + SC.ShortProjectName, SC.ShortProjectName);
            ObbFile.Save();
        }

        foreach (string Architecture in Architectures)
        {
            foreach (string GPUArchitecture in GPUArchitectures)
            {
                string ApkName = GetFinalApkName(Params, SC.StageExecutables[0], true, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "");
                if (!SC.IsCodeBasedProject)
                {
                    string UE4SOName = GetFinalApkName(Params, SC.StageExecutables[0], false, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "");
                    UE4SOName = UE4SOName.Replace(".apk", ".so");
                    if (FileExists_NoExceptions(UE4SOName) == false)
                    {
                        Log("Failed to find game .so " + UE4SOName);
                        throw new AutomationException(ErrorCodes.Error_MissingExecutable, "Stage Failed. Could not find .so {0}. You may need to build the UE4 project with your target configuration and platform.", UE4SOName);
                    }
                }


                if (!Params.Prebuilt)
                {
                    string CookFlavor = SC.FinalCookPlatform.IndexOf("_") > 0 ? SC.FinalCookPlatform.Substring(SC.FinalCookPlatform.IndexOf("_")) : "";
                    string SOName     = GetSONameWithoutArchitecture(Params, SC.StageExecutables[0]);
                    Deploy.PrepForUATPackageOrDeploy(Params.ShortProjectName, SC.ProjectRoot, SOName, SC.LocalRoot + "/Engine", Params.Distribution, CookFlavor, false);
                }

                // Create APK specific OBB in case we have a detached OBB.
                string DeviceObbName = "";
                string ObbName       = "";
                if (!bPackageDataInsideApk)
                {
                    DeviceObbName = GetDeviceObbName(ApkName);
                    ObbName       = GetFinalObbName(ApkName);
                    CopyFile(LocalObbName, ObbName);
                }

                // Write install batch file(s).
                string BatchName   = GetFinalBatchName(ApkName, Params, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "", false);
                string PackageName = GetPackageInfo(ApkName, false);
                // make a batch file that can be used to install the .apk and .obb files
                string[] BatchLines = GenerateInstallBatchFile(bPackageDataInsideApk, PackageName, ApkName, Params, ObbName, DeviceObbName, false);
                File.WriteAllLines(BatchName, BatchLines);

                // If we aren't packaging data in the APK then lets write out a bat file to also let us test without the OBB
                // on the device.
                String NoInstallBatchName = GetFinalBatchName(ApkName, Params, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "", true);
                // if(!bPackageDataInsideApk)
                {
                    BatchLines = GenerateInstallBatchFile(bPackageDataInsideApk, PackageName, ApkName, Params, ObbName, DeviceObbName, true);
                    File.WriteAllLines(NoInstallBatchName, BatchLines);
                }

                if (Utils.IsRunningOnMono)
                {
                    CommandUtils.FixUnixFilePermissions(BatchName);
                    if (File.Exists(NoInstallBatchName))
                    {
                        CommandUtils.FixUnixFilePermissions(NoInstallBatchName);
                    }
                }
            }
        }

        PrintRunTime();
    }