Files
Daybreak/Daybreak.Installer/Program.cs
T

322 lines
10 KiB
C#

using System.Diagnostics;
using System.IO.Compression;
using System.Net;
using System.Text;
const string LatestUrl = "https://github.com/gwdevhub/Daybreak/releases/latest";
static void RenderProgressBar(int currentStep, int totalSteps, int barSize)
{
Console.CursorLeft = 0; // Reset cursor position to the start of the line
Console.Write("["); // Start of the progress bar
double pctComplete = (double)currentStep / totalSteps;
int chars = (int)Math.Round(pctComplete * barSize);
Console.Write(new string('=', chars)); // The 'completed' part of the bar
Console.Write(new string(' ', barSize - chars)); // The 'remaining' part of the bar
Console.Write("] ");
Console.Write($"{pctComplete:P0}"); // Display the percentage completed
}
static void CleanWorkingDirectory(string workingDirectory)
{
if (!Directory.Exists(workingDirectory))
{
return;
}
var installerPath = Path.GetFullPath(Path.Combine(workingDirectory, "Installer"));
var optionsPath = Path.GetFullPath(Path.Combine(workingDirectory, "Daybreak.options"));
var preserve = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
installerPath,
optionsPath
};
var preserveExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".tpf"
};
foreach (var entry in Directory.GetFileSystemEntries(workingDirectory))
{
var fullPath = Path.GetFullPath(entry);
if (preserve.Contains(fullPath))
{
continue;
}
if (preserveExtensions.Contains(Path.GetExtension(fullPath)))
{
continue;
}
try
{
if (Directory.Exists(fullPath))
{
Directory.Delete(fullPath, true);
}
else if (File.Exists(fullPath))
{
File.Delete(fullPath);
}
}
catch (Exception e)
{
Console.WriteLine($"Failed to delete {fullPath}.\n{e}");
}
}
}
static async ValueTask PerformUpdate(string workingDirectory)
{
if (!Directory.Exists(workingDirectory))
{
Console.WriteLine("Working directory does not exist");
Console.ReadKey();
return;
}
Console.WriteLine($"Working directory: {workingDirectory}");
var tempFile = Path.GetFullPath("tempfile.zip", workingDirectory);
var updatePkg = Path.GetFullPath("update.pkg", workingDirectory);
var executableName = Path.GetFullPath("Daybreak.exe", workingDirectory);
if (File.Exists(updatePkg))
{
Console.WriteLine("Unpacking files...");
using var fileStream = new FileStream(updatePkg, FileMode.Open);
var copyBuffer = new Memory<byte>(new byte[4096]);
while (fileStream.Position < fileStream.Length - 1)
{
/*
* For each file downloaded, we write to the package the following:
* Size of the name string
* UTF-8 encoded name string
* Size of the relative path string
* UTF-8 encoded relative path string
* Size of the binary
* Binary data
*/
var sizeBuffer = copyBuffer[..4];
await fileStream.ReadExactlyAsync(sizeBuffer);
var fileNameSize = BitConverter.ToInt32(sizeBuffer.Span);
var fileNameBuffer = copyBuffer[..fileNameSize];
await fileStream.ReadExactlyAsync(fileNameBuffer);
var fileName = Encoding.UTF8.GetString(fileNameBuffer.Span);
RenderProgressBar((int)fileStream.Position, (int)fileStream.Length, 40);
await fileStream.ReadExactlyAsync(sizeBuffer);
var relativePathSize = BitConverter.ToInt32(sizeBuffer.Span);
var relativePathBuffer = copyBuffer[..relativePathSize];
await fileStream.ReadExactlyAsync(relativePathBuffer);
var relativePath = Encoding.UTF8.GetString(relativePathBuffer.Span);
RenderProgressBar((int)fileStream.Position, (int)fileStream.Length, 40);
await fileStream.ReadExactlyAsync(sizeBuffer);
var binarySize = BitConverter.ToInt32(sizeBuffer.Span);
var fileInfo = new FileInfo(relativePath);
fileInfo.Directory!.Create();
using var destinationStream = new FileStream(Path.GetFullPath(workingDirectory, relativePath), FileMode.Create);
while (binarySize > 0)
{
var toRead = Math.Min(binarySize, copyBuffer.Length);
var readBuffer = copyBuffer[..toRead];
await fileStream.ReadExactlyAsync(readBuffer);
await destinationStream.WriteAsync(readBuffer);
binarySize -= toRead;
RenderProgressBar((int)fileStream.Position, (int)fileStream.Length, 40);
}
}
fileStream.Close();
fileStream.Dispose();
File.Delete(updatePkg);
}
else if (File.Exists(tempFile))
{
Console.WriteLine("Unpacking files...");
try
{
ZipFile.ExtractToDirectory(tempFile, Path.GetFullPath(workingDirectory), true);
}
catch
{
}
Console.WriteLine("Deleting package");
try
{
File.Delete(tempFile);
}
catch (Exception e)
{
Console.WriteLine($"Failed to delete {tempFile}.\n{e}");
}
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Unable to find launcher package. Aborting installation");
Console.ReadKey();
return;
}
Console.WriteLine("Launching application");
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = executableName
}
};
if (process.Start() is false)
{
Console.WriteLine("Failed to launch application");
Console.ReadKey();
}
}
static async ValueTask PerformFreshInstall(string workingDirectory)
{
using var handler = new HttpClientHandler
{
AllowAutoRedirect = false
};
var client = new HttpClient(handler);
using var latestResponse = await client.GetAsync(LatestUrl);
var isRedirect = latestResponse.StatusCode is HttpStatusCode.Redirect
or HttpStatusCode.MovedPermanently
or HttpStatusCode.Found
or HttpStatusCode.SeeOther
or HttpStatusCode.TemporaryRedirect
or HttpStatusCode.PermanentRedirect;
if (!isRedirect)
{
Console.WriteLine($"Failed to get latest release information. Status: {latestResponse.StatusCode}");
Console.ReadKey();
return;
}
var redirectTarget = latestResponse.Headers.Location;
if (redirectTarget is null)
{
Console.WriteLine("Latest release redirect location missing.");
Console.ReadKey();
return;
}
var finalUri = redirectTarget.IsAbsoluteUri
? redirectTarget
: new Uri(latestResponse.RequestMessage!.RequestUri!, redirectTarget);
var version = finalUri.Segments.Last().TrimEnd('/');
var downloadUri = new Uri($"https://github.com/gwdevhub/Daybreak/releases/download/{version}/daybreak{version}.zip");
var tempFile = Path.GetFullPath("tempfile.zip", workingDirectory);
Console.WriteLine($"Cleaning installation directory {workingDirectory}");
CleanWorkingDirectory(workingDirectory);
Console.WriteLine($"Downloading Daybreak {version}...");
handler.Dispose();
client.Dispose();
client = new HttpClient();
using var downloadResponse = await client.GetAsync(downloadUri, HttpCompletionOption.ResponseHeadersRead);
if (!downloadResponse.IsSuccessStatusCode)
{
Console.WriteLine($"Failed to download Daybreak package. Status: {downloadResponse.StatusCode}");
Console.ReadKey();
return;
}
var totalBytes = downloadResponse.Content.Headers.ContentLength ?? -1L;
using var contentStream = await downloadResponse.Content.ReadAsStreamAsync();
using var fileStream = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.None);
var totalRead = 0L;
var buffer = new Memory<byte>(new byte[8192]);
int bytesRead;
while ((bytesRead = await contentStream.ReadAsync(buffer)) > 0)
{
await fileStream.WriteAsync(buffer[..bytesRead]);
totalRead += bytesRead;
if (totalBytes != -1)
{
RenderProgressBar((int)totalRead, (int)totalBytes, 40);
}
}
fileStream.Close();
fileStream.Dispose();
client.Dispose();
Console.WriteLine("\nDownload complete. Extracting files...");
try
{
ZipFile.ExtractToDirectory(tempFile, Path.GetFullPath(workingDirectory), true);
}
catch (Exception e)
{
Console.WriteLine($"Failed to extract package.\n{e}");
Console.ReadKey();
return;
}
File.Delete(tempFile);
Console.WriteLine("Installation complete. Launching application...");
var executableName = Path.GetFullPath("Daybreak.exe", workingDirectory);
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = executableName
}
};
if (process.Start() is false)
{
Console.WriteLine("Failed to launch application");
Console.ReadKey();
}
}
Console.Title = "Daybreak Installer";
Console.WriteLine("Starting installation...");
while (Process.GetProcesses().Where(p => p.ProcessName == "Daybreak").FirstOrDefault()?.HasExited is false)
{
Console.WriteLine($"Detected Daybreak process is still running. Waiting 5s and retrying");
await Task.Delay(5000);
}
while (Process.GetProcessesByName("gw").FirstOrDefault()?.HasExited is false)
{
Console.WriteLine($"Detected Guild Wars process is still running. Waiting 5s and retrying");
await Task.Delay(5000);
}
var mode = args.Length > 1
? args[0] : "install";
var workingDirectory = args.Length > 2
? args[1] : Path.GetFullPath("..", AppContext.BaseDirectory);
if (mode is "install")
{
await PerformFreshInstall(workingDirectory);
}
else
{
await PerformUpdate(workingDirectory);
}