You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
300 lines
9.5 KiB
C#
300 lines
9.5 KiB
C#
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
using EpicGames.Core;
|
|
using EpicGames.Perforce;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Win32;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
using UnrealGameSync;
|
|
|
|
namespace UnrealGameSyncLauncher
|
|
{
|
|
static class Program
|
|
{
|
|
[STAThread]
|
|
static int Main(string[] Args)
|
|
{
|
|
Application.EnableVisualStyles();
|
|
Application.SetCompatibleTextRenderingDefault(false);
|
|
|
|
bool bFirstInstance;
|
|
using(Mutex InstanceMutex = new Mutex(true, "UnrealGameSyncRunning", out bFirstInstance))
|
|
{
|
|
if(!bFirstInstance)
|
|
{
|
|
using(EventWaitHandle ActivateEvent = new EventWaitHandle(false, EventResetMode.AutoReset, "ActivateUnrealGameSync"))
|
|
{
|
|
ActivateEvent.Set();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Figure out if we should sync the unstable build by default
|
|
bool bUnstable = Args.Contains("-unstable", StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
// Read the settings
|
|
string? ServerAndPort = null;
|
|
string? UserName = null;
|
|
string? DepotPath = DeploymentSettings.DefaultDepotPath;
|
|
GlobalPerforceSettings.ReadGlobalPerforceSettings(ref ServerAndPort, ref UserName, ref DepotPath);
|
|
|
|
// If the shift key is held down, immediately show the settings window
|
|
SettingsWindow.SyncAndRunDelegate SyncAndRunWrapper = (Perforce, DepotParam, bUnstableParam, LogWriter, CancellationToken) => SyncAndRun(Perforce, DepotParam, bUnstableParam, Args, InstanceMutex, LogWriter, CancellationToken);
|
|
if ((Control.ModifierKeys & Keys.Shift) != 0)
|
|
{
|
|
// Show the settings window immediately
|
|
SettingsWindow UpdateError = new SettingsWindow(null, null, ServerAndPort, UserName, DepotPath, bUnstable, SyncAndRunWrapper);
|
|
if(UpdateError.ShowDialog() == DialogResult.OK)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Try to do a sync with the current settings first
|
|
CaptureLogger Logger = new CaptureLogger();
|
|
|
|
IPerforceSettings Settings = PerforceSettings.Default.MergeWith(newServerAndPort: ServerAndPort, newUserName: UserName);
|
|
|
|
ModalTask? Task = PerforceModalTask.Execute(null, "Updating", "Checking for updates, please wait...", Settings, (p, c) => SyncAndRun(p, DepotPath, bUnstable, Args, InstanceMutex, Logger, c), Logger);
|
|
if (Task == null)
|
|
{
|
|
Logger.LogInformation("Canceled by user");
|
|
}
|
|
else if (Task.Succeeded)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
SettingsWindow UpdateError = new SettingsWindow("Unable to update UnrealGameSync from Perforce. Verify that your connection settings are correct.", Logger.Render(Environment.NewLine), ServerAndPort, UserName, DepotPath, bUnstable, SyncAndRunWrapper);
|
|
if(UpdateError.ShowDialog() == DialogResult.OK)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
public static async Task SyncAndRun(IPerforceConnection Perforce, string? BaseDepotPath, bool bUnstable, string[] Args, Mutex InstanceMutex, ILogger Logger, CancellationToken CancellationToken)
|
|
{
|
|
try
|
|
{
|
|
if (String.IsNullOrEmpty(BaseDepotPath))
|
|
{
|
|
throw new UserErrorException($"Invalid setting for sync path");
|
|
}
|
|
|
|
string SyncPath = BaseDepotPath.TrimEnd('/') + (bUnstable ? "/UnstableRelease/..." : "/Release/...");
|
|
Logger.LogInformation("Syncing from {SyncPath}", SyncPath);
|
|
|
|
// Create the target folder
|
|
string ApplicationFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "UnrealGameSync", "Latest");
|
|
if (!SafeCreateDirectory(ApplicationFolder))
|
|
{
|
|
throw new UserErrorException($"Couldn't create directory: {ApplicationFolder}");
|
|
}
|
|
|
|
// Find the most recent changelist
|
|
List<ChangesRecord> Changes = await Perforce.GetChangesAsync(ChangesOptions.None, 1, ChangeStatus.Submitted, SyncPath, CancellationToken);
|
|
int RequiredChangeNumber = Changes[0].Number;
|
|
|
|
// Read the current version
|
|
string SyncVersionFile = Path.Combine(ApplicationFolder, "SyncVersion.txt");
|
|
string RequiredSyncText = String.Format("{0}\n{1}@{2}", Perforce.Settings.ServerAndPort ?? "", SyncPath, RequiredChangeNumber);
|
|
|
|
// Check the application exists
|
|
string ApplicationExe = Path.Combine(ApplicationFolder, "UnrealGameSync.exe");
|
|
|
|
// Check if the version has changed
|
|
string? SyncText;
|
|
if (!File.Exists(SyncVersionFile) || !File.Exists(ApplicationExe) || !TryReadAllText(SyncVersionFile, out SyncText) || SyncText != RequiredSyncText)
|
|
{
|
|
// Try to delete the directory contents. Retry for a while, in case we've been spawned by an application in this folder to do an update.
|
|
for (int NumRetries = 0; !SafeDeleteDirectoryContents(ApplicationFolder); NumRetries++)
|
|
{
|
|
if (NumRetries > 20)
|
|
{
|
|
throw new UserErrorException($"Couldn't delete contents of {ApplicationFolder} (retried {NumRetries} times).");
|
|
}
|
|
Thread.Sleep(500);
|
|
}
|
|
|
|
// Find all the files in the sync path at this changelist
|
|
List<FStatRecord> FileRecords = await Perforce.FStatAsync(FStatOptions.None, $"{SyncPath}@{RequiredChangeNumber}", CancellationToken).ToListAsync(CancellationToken);
|
|
if (FileRecords.Count == 0)
|
|
{
|
|
throw new UserErrorException($"Couldn't find any matching files for {SyncPath}@{RequiredChangeNumber}");
|
|
}
|
|
|
|
// Sync all the files in this list to the same directory structure under the application folder
|
|
string DepotPathPrefix = SyncPath.Substring(0, SyncPath.LastIndexOf('/') + 1);
|
|
foreach (FStatRecord FileRecord in FileRecords)
|
|
{
|
|
if (FileRecord.DepotFile == null)
|
|
{
|
|
throw new UserErrorException("Missing depot path for returned file");
|
|
}
|
|
|
|
string LocalPath = Path.Combine(ApplicationFolder, FileRecord.DepotFile.Substring(DepotPathPrefix.Length).Replace('/', Path.DirectorySeparatorChar));
|
|
if (!SafeCreateDirectory(Path.GetDirectoryName(LocalPath)!))
|
|
{
|
|
throw new UserErrorException($"Couldn't create folder {Path.GetDirectoryName(LocalPath)}");
|
|
}
|
|
|
|
await Perforce.PrintAsync(LocalPath, FileRecord.DepotFile, CancellationToken);
|
|
}
|
|
|
|
// Check the application exists
|
|
if (!File.Exists(ApplicationExe))
|
|
{
|
|
throw new UserErrorException($"Application was not synced from Perforce. Check that UnrealGameSync exists at {SyncPath}/UnrealGameSync.exe, and you have access to it.");
|
|
}
|
|
|
|
// Update the version
|
|
if (!TryWriteAllText(SyncVersionFile, RequiredSyncText))
|
|
{
|
|
throw new UserErrorException("Couldn't write sync text to {SyncVersionFile}");
|
|
}
|
|
}
|
|
Logger.LogInformation("");
|
|
|
|
// Build the command line for the synced application, including the sync path to monitor for updates
|
|
StringBuilder NewCommandLine = new StringBuilder(String.Format("-updatepath=\"{0}@>{1}\" -updatespawn=\"{2}\"{3}", SyncPath, RequiredChangeNumber, Assembly.GetEntryAssembly()!.Location, bUnstable ? " -unstable" : ""));
|
|
foreach (string Arg in Args)
|
|
{
|
|
NewCommandLine.AppendFormat(" {0}", QuoteArgument(Arg));
|
|
}
|
|
|
|
// Release the mutex now so that the new application can start up
|
|
InstanceMutex.Close();
|
|
|
|
// Spawn the application
|
|
Logger.LogInformation("Spawning {App} with command line: {CmdLine}", ApplicationExe, NewCommandLine.ToString());
|
|
using (Process ChildProcess = new Process())
|
|
{
|
|
ChildProcess.StartInfo.FileName = ApplicationExe;
|
|
ChildProcess.StartInfo.Arguments = NewCommandLine.ToString();
|
|
ChildProcess.StartInfo.UseShellExecute = false;
|
|
ChildProcess.StartInfo.CreateNoWindow = false;
|
|
if (!ChildProcess.Start())
|
|
{
|
|
throw new UserErrorException("Failed to start process");
|
|
}
|
|
}
|
|
}
|
|
catch (UserErrorException Ex)
|
|
{
|
|
Logger.LogError("{Message}", Ex.Message);
|
|
throw;
|
|
}
|
|
catch (Exception Ex)
|
|
{
|
|
Logger.LogError(Ex, "Error while syncing application.");
|
|
foreach (string Line in Ex.ToString().Split('\n'))
|
|
{
|
|
Logger.LogError("{Line}", Line);
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
|
|
static string QuoteArgument(string Arg)
|
|
{
|
|
if(Arg.IndexOf(' ') != -1 && !Arg.StartsWith("\""))
|
|
{
|
|
return String.Format("\"{0}\"", Arg);
|
|
}
|
|
else
|
|
{
|
|
return Arg;
|
|
}
|
|
}
|
|
|
|
static bool TryReadAllText(string FileName, [NotNullWhen(true)] out string? Text)
|
|
{
|
|
try
|
|
{
|
|
Text = File.ReadAllText(FileName);
|
|
return true;
|
|
}
|
|
catch(Exception)
|
|
{
|
|
Text = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool TryWriteAllText(string FileName, string Text)
|
|
{
|
|
try
|
|
{
|
|
File.WriteAllText(FileName, Text);
|
|
return true;
|
|
}
|
|
catch(Exception)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool SafeCreateDirectory(string DirectoryName)
|
|
{
|
|
try
|
|
{
|
|
Directory.CreateDirectory(DirectoryName);
|
|
return true;
|
|
}
|
|
catch(Exception)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool SafeDeleteDirectory(string DirectoryName)
|
|
{
|
|
try
|
|
{
|
|
Directory.Delete(DirectoryName, true);
|
|
return true;
|
|
}
|
|
catch(Exception)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool SafeDeleteDirectoryContents(string DirectoryName)
|
|
{
|
|
try
|
|
{
|
|
DirectoryInfo Directory = new DirectoryInfo(DirectoryName);
|
|
foreach(FileInfo ChildFile in Directory.EnumerateFiles("*", SearchOption.AllDirectories))
|
|
{
|
|
ChildFile.Attributes = ChildFile.Attributes & ~FileAttributes.ReadOnly;
|
|
ChildFile.Delete();
|
|
}
|
|
foreach(DirectoryInfo ChildDirectory in Directory.EnumerateDirectories())
|
|
{
|
|
SafeDeleteDirectory(ChildDirectory.FullName);
|
|
}
|
|
return true;
|
|
}
|
|
catch(Exception)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|