diff --git a/Bloxstrap/App.xaml.cs b/Bloxstrap/App.xaml.cs index d1f13ff0..76d32f27 100644 --- a/Bloxstrap/App.xaml.cs +++ b/Bloxstrap/App.xaml.cs @@ -13,6 +13,7 @@ using Microsoft.Win32; +using Bloxstrap.Enums; using Bloxstrap.Extensions; using Bloxstrap.Models; using Bloxstrap.Models.Attributes; @@ -34,6 +35,7 @@ public partial class App : Application // used only for communicating between app and menu - use Directories.Base for anything else public static string BaseDirectory = null!; + public static bool ShouldSaveConfigs { get; set; } = false; public static bool IsSetupComplete { get; set; } = true; public static bool IsFirstRun { get; private set; } = true; @@ -56,39 +58,23 @@ public partial class App : Application public static System.Windows.Forms.NotifyIcon Notification { get; private set; } = null!; - public static void Terminate(int code = Bootstrapper.ERROR_SUCCESS) - { - Logger.WriteLine($"[App::Terminate] Terminating with exit code {code}"); - Settings.Save(); - State.Save(); - Notification.Dispose(); - Environment.Exit(code); - } - - private void InitLog() + public static void Terminate(ErrorCode exitCode = ErrorCode.ERROR_SUCCESS) { - // if we're running for the first time or uninstalling, log to temp folder - // else, log to bloxstrap folder + if (IsFirstRun) + { + if (exitCode == ErrorCode.ERROR_CANCELLED) + exitCode = ErrorCode.ERROR_INSTALL_USEREXIT; + } - bool isUsingTempDir = IsFirstRun || IsUninstall; - string logdir = isUsingTempDir ? Path.Combine(Directories.LocalAppData, "Temp") : Path.Combine(Directories.Base, "Logs"); - string timestamp = DateTime.UtcNow.ToString("yyyyMMdd'T'HHmmss'Z'"); - int processId = Process.GetCurrentProcess().Id; + int exitCodeNum = (int)exitCode; - Logger.Initialize(Path.Combine(logdir, $"{ProjectName}_{timestamp}_{processId}.log")); + Logger.WriteLine($"[App::Terminate] Terminating with exit code {exitCodeNum} ({exitCode})"); - // clean up any logs older than a week - if (!isUsingTempDir) - { - foreach (FileInfo log in new DirectoryInfo(logdir).GetFiles()) - { - if (log.LastWriteTimeUtc.AddDays(7) > DateTime.UtcNow) - continue; + Settings.Save(); + State.Save(); + Notification.Dispose(); - Logger.WriteLine($"[App::InitLog] Cleaning up old log file '{log.Name}'"); - log.Delete(); - } - } + Environment.Exit(exitCodeNum); } void GlobalExceptionHandler(object sender, DispatcherUnhandledExceptionEventArgs e) @@ -111,7 +97,7 @@ void FinalizeExceptionHandling(Exception exception) if (!IsQuiet) Controls.ShowExceptionDialog(exception); - Terminate(Bootstrapper.ERROR_INSTALL_FAILURE); + Terminate(ErrorCode.ERROR_INSTALL_FAILURE); #pragma warning restore 162 } @@ -193,7 +179,7 @@ protected override void OnStartup(StartupEventArgs e) Logger.WriteLine("[App::OnStartup] Running first-time install"); BaseDirectory = Path.Combine(Directories.LocalAppData, ProjectName); - InitLog(); + Logger.Initialize(true); if (!IsQuiet) { @@ -213,7 +199,7 @@ protected override void OnStartup(StartupEventArgs e) if (!IsSetupComplete) { Logger.WriteLine("[App::OnStartup] Installation cancelled!"); - Environment.Exit(Bootstrapper.ERROR_INSTALL_USEREXIT); + Terminate(ErrorCode.ERROR_CANCELLED); } Directories.Initialize(BaseDirectory); @@ -222,7 +208,7 @@ protected override void OnStartup(StartupEventArgs e) // just in case the user decides to cancel the install if (!IsFirstRun) { - InitLog(); + Logger.Initialize(IsUninstall); Settings.Load(); State.Load(); FastFlags.Load(); diff --git a/Bloxstrap/Bootstrapper.cs b/Bloxstrap/Bootstrapper.cs index 2b75af00..ec9e417c 100644 --- a/Bloxstrap/Bootstrapper.cs +++ b/Bloxstrap/Bootstrapper.cs @@ -24,12 +24,6 @@ namespace Bloxstrap public class Bootstrapper { #region Properties - - // https://learn.microsoft.com/en-us/windows/win32/msi/error-codes - public const int ERROR_SUCCESS = 0; - public const int ERROR_INSTALL_USEREXIT = 1602; - public const int ERROR_INSTALL_FAILURE = 1603; - // in case a new package is added, you can find the corresponding directory // by opening the stock bootstrapper in a hex editor // TODO - there ideally should be a less static way to do this that's not hardcoded? @@ -109,7 +103,7 @@ private void SetStatus(string message) Dialog.Message = message; } - private void UpdateProgressbar() + private void UpdateProgressBar() { int newProgress = (int)Math.Floor(_progressIncrement * _totalDownloadedBytes); @@ -378,7 +372,7 @@ public void CancelInstall() { if (!_isInstalling) { - App.Terminate(ERROR_INSTALL_USEREXIT); + App.Terminate(ErrorCode.ERROR_CANCELLED); return; } @@ -401,7 +395,7 @@ public void CancelInstall() App.Logger.WriteLine($"[Bootstrapper::CancelInstall] {ex}"); } - App.Terminate(ERROR_INSTALL_USEREXIT); + App.Terminate(ErrorCode.ERROR_CANCELLED); } #endregion @@ -581,9 +575,9 @@ private async Task CheckForUpdates() return; } - App.Logger.WriteLine($"[Bootstrapper::CheckForUpdates] Checking for {App.ProjectName} updates..."); + App.Logger.WriteLine($"[Bootstrapper::CheckForUpdates] Checking for updates..."); - var releaseInfo = await Utilities.GetJson($"https://api.github.com/repos/{App.ProjectRepository}/releases/latest"); + var releaseInfo = await Utility.Http.GetJson($"https://api.github.com/repos/{App.ProjectRepository}/releases/latest"); if (releaseInfo is null || releaseInfo.Assets is null) { @@ -629,10 +623,11 @@ private async Task CheckForUpdates() startInfo.ArgumentList.Add(arg); App.Settings.Save(); + App.ShouldSaveConfigs = false; Process.Start(startInfo); - Environment.Exit(0); + App.Terminate(); } private void Uninstall() @@ -649,7 +644,7 @@ private void Uninstall() ); if (result != MessageBoxResult.OK) - Environment.Exit(ERROR_INSTALL_USEREXIT); + App.Terminate(ErrorCode.ERROR_CANCELLED); try { @@ -669,7 +664,6 @@ private void Uninstall() SetStatus($"Uninstalling {App.ProjectName}..."); - //App.Settings.ShouldSave = false; App.ShouldSaveConfigs = false; // check if stock bootstrapper is still installed @@ -759,7 +753,7 @@ private async Task InstallLatestVersion() MessageBoxImage.Error ); - App.Terminate(ERROR_INSTALL_FAILURE); + App.Terminate(ErrorCode.ERROR_INSTALL_FAILURE); return; } @@ -1135,7 +1129,7 @@ private async Task DownloadPackage(Package package) { App.Logger.WriteLine($"[Bootstrapper::DownloadPackage] {package.Name} is already downloaded, skipping..."); _totalDownloadedBytes += package.PackedSize; - UpdateProgressbar(); + UpdateProgressBar(); return; } } @@ -1147,7 +1141,7 @@ private async Task DownloadPackage(Package package) App.Logger.WriteLine($"[Bootstrapper::DownloadPackage] Found existing version of {package.Name} ({robloxPackageLocation})! Copying to Downloads folder..."); File.Copy(robloxPackageLocation, packageLocation); _totalDownloadedBytes += package.PackedSize; - UpdateProgressbar(); + UpdateProgressBar(); return; } @@ -1179,7 +1173,7 @@ private async Task DownloadPackage(Package package) await fileStream.WriteAsync(buffer, 0, bytesRead, _cancelTokenSource.Token); _totalDownloadedBytes += bytesRead; - UpdateProgressbar(); + UpdateProgressBar(); } } diff --git a/Bloxstrap/Enums/ErrorCode.cs b/Bloxstrap/Enums/ErrorCode.cs new file mode 100644 index 00000000..d9fbd421 --- /dev/null +++ b/Bloxstrap/Enums/ErrorCode.cs @@ -0,0 +1,14 @@ +namespace Bloxstrap.Enums +{ + // https://learn.microsoft.com/en-us/windows/win32/msi/error-codes + // https://i-logic.com/serial/errorcodes.htm + // just the ones that we're interested in + + public enum ErrorCode + { + ERROR_SUCCESS = 0, + ERROR_INSTALL_USEREXIT = 1602, + ERROR_INSTALL_FAILURE = 1603, + ERROR_CANCELLED = 1223 + } +} diff --git a/Bloxstrap/FastFlagManager.cs b/Bloxstrap/FastFlagManager.cs index 104905e9..0685e864 100644 --- a/Bloxstrap/FastFlagManager.cs +++ b/Bloxstrap/FastFlagManager.cs @@ -139,8 +139,6 @@ public override void Load() public override void Save() { - App.Logger.WriteLine($"[FastFlagManager::Save] Attempting to save JSON to {FileLocation}..."); - // reload for any changes made while the menu was open Load(); @@ -163,12 +161,9 @@ public override void Save() Prop[change.Key] = change.Value; } - Directory.CreateDirectory(Path.GetDirectoryName(FileLocation)!); - File.WriteAllText(FileLocation, JsonSerializer.Serialize(Prop, new JsonSerializerOptions { WriteIndented = true })); + base.Save(); Changes.Clear(); - - App.Logger.WriteLine($"[FastFlagManager::Save] JSON saved!"); } } } diff --git a/Bloxstrap/Integrations/DiscordRichPresence.cs b/Bloxstrap/Integrations/DiscordRichPresence.cs index 12a733db..f6d40288 100644 --- a/Bloxstrap/Integrations/DiscordRichPresence.cs +++ b/Bloxstrap/Integrations/DiscordRichPresence.cs @@ -112,7 +112,7 @@ public async Task SetCurrentGame() App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Setting presence for Place ID {_activityWatcher.ActivityPlaceId}"); - var universeIdResponse = await Utilities.GetJson($"https://apis.roblox.com/universes/v1/places/{_activityWatcher.ActivityPlaceId}/universe"); + var universeIdResponse = await Utility.Http.GetJson($"https://apis.roblox.com/universes/v1/places/{_activityWatcher.ActivityPlaceId}/universe"); if (universeIdResponse is null) { App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Could not get Universe ID!"); @@ -129,7 +129,7 @@ public async Task SetCurrentGame() _activityWatcher.ActivityIsTeleport = false; _currentUniverseId = universeId; - var gameDetailResponse = await Utilities.GetJson>($"https://games.roblox.com/v1/games?universeIds={universeId}"); + var gameDetailResponse = await Utility.Http.GetJson>($"https://games.roblox.com/v1/games?universeIds={universeId}"); if (gameDetailResponse is null || !gameDetailResponse.Data.Any()) { App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Could not get Universe info!"); @@ -139,7 +139,7 @@ public async Task SetCurrentGame() GameDetailResponse universeDetails = gameDetailResponse.Data.ToArray()[0]; App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Got Universe details"); - var universeThumbnailResponse = await Utilities.GetJson>($"https://thumbnails.roblox.com/v1/games/icons?universeIds={universeId}&returnPolicy=PlaceHolder&size=512x512&format=Png&isCircular=false"); + var universeThumbnailResponse = await Utility.Http.GetJson>($"https://thumbnails.roblox.com/v1/games/icons?universeIds={universeId}&returnPolicy=PlaceHolder&size=512x512&format=Png&isCircular=false"); if (universeThumbnailResponse is null || !universeThumbnailResponse.Data.Any()) { App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Could not get Universe thumbnail info!"); diff --git a/Bloxstrap/JsonManager.cs b/Bloxstrap/JsonManager.cs index f888341a..095819ab 100644 --- a/Bloxstrap/JsonManager.cs +++ b/Bloxstrap/JsonManager.cs @@ -11,7 +11,7 @@ namespace Bloxstrap public virtual void Load() { - App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Load] Loading JSON from {FileLocation}..."); + App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Load] Loading from {FileLocation}..."); try { @@ -22,28 +22,28 @@ public virtual void Load() Prop = settings; - App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Load] JSON loaded successfully!"); + App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Load] Loaded successfully!"); } catch (Exception ex) { - App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Load] Failed to load JSON! ({ex.Message})"); + App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Load] Failed to load! ({ex.Message})"); } } public virtual void Save() { - App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Save] Attempting to save JSON to {FileLocation}..."); - if (!App.ShouldSaveConfigs) { - App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Save] Aborted save (ShouldSave set to false)"); + App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Save] Save request ignored"); return; } + App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Save] Saving to {FileLocation}..."); + Directory.CreateDirectory(Path.GetDirectoryName(FileLocation)!); File.WriteAllText(FileLocation, JsonSerializer.Serialize(Prop, new JsonSerializerOptions { WriteIndented = true })); - App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Save] JSON saved!"); + App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Save] Save complete!"); } } } diff --git a/Bloxstrap/Logger.cs b/Bloxstrap/Logger.cs index d2f5f9cb..f9bcd18a 100644 --- a/Bloxstrap/Logger.cs +++ b/Bloxstrap/Logger.cs @@ -20,27 +20,44 @@ public class Logger public readonly List Backlog = new(); public bool Initialized = false; - public string? Filename; + public string? FileLocation; - public void Initialize(string filename) + public void Initialize(bool useTempDir = false) { - if (_filestream is not null) - throw new Exception("Logger is already initialized"); + string directory = useTempDir ? Path.Combine(Directories.LocalAppData, "Temp") : Path.Combine(Directories.Base, "Logs"); + string timestamp = DateTime.UtcNow.ToString("yyyyMMdd'T'HHmmss'Z'"); + string filename = $"{App.ProjectName}_{timestamp}.log"; + string location = Path.Combine(directory, filename); + + WriteLine($"[Logger::Initialize] Initializing at {location}"); - string? directory = Path.GetDirectoryName(filename); + if (Initialized) + throw new Exception("Logger is already initialized"); - if (directory is not null) - Directory.CreateDirectory(directory); + Directory.CreateDirectory(directory); - _filestream = File.Open(filename, FileMode.Create, FileAccess.Write, FileShare.Read); + _filestream = File.Open(location, FileMode.Create, FileAccess.Write, FileShare.Read); if (Backlog.Count > 0) WriteToLog(string.Join("\r\n", Backlog)); - WriteLine($"[Logger::Logger] Initialized at {filename}"); + WriteLine($"[Logger::Initialize] Finished initializing!"); Initialized = true; - Filename = filename; + FileLocation = location; + + // clean up any logs older than a week + if (!useTempDir) + { + foreach (FileInfo log in new DirectoryInfo(directory).GetFiles()) + { + if (log.LastWriteTimeUtc.AddDays(7) > DateTime.UtcNow) + continue; + + App.Logger.WriteLine($"[Logger::Initialize] Cleaning up old log file '{log.Name}'"); + log.Delete(); + } + } } public void WriteLine(string message) diff --git a/Bloxstrap/ProtocolHandler.cs b/Bloxstrap/ProtocolHandler.cs index 77eac1ac..ce1a4ace 100644 --- a/Bloxstrap/ProtocolHandler.cs +++ b/Bloxstrap/ProtocolHandler.cs @@ -63,7 +63,7 @@ public static string ParseUri(string protocol) MessageBoxResult result = App.Settings.Prop.ChannelChangeMode == ChannelChangeMode.Automatic ? MessageBoxResult.Yes : Controls.ShowMessageBox( - $"{App.ProjectName} was launched with the Roblox build channel set to {val}, however your current preferred channel is {App.Settings.Prop.Channel}.\n\n" + + $"Roblox is attempting to set your channel to {val}, however your current preferred channel is {App.Settings.Prop.Channel}.\n\n" + $"Would you like to switch channels from {App.Settings.Prop.Channel} to {val}?", MessageBoxImage.Question, MessageBoxButton.YesNo diff --git a/Bloxstrap/UI/Elements/ExceptionDialog.xaml.cs b/Bloxstrap/UI/Elements/ExceptionDialog.xaml.cs index 9727d53e..1af61aa7 100644 --- a/Bloxstrap/UI/Elements/ExceptionDialog.xaml.cs +++ b/Bloxstrap/UI/Elements/ExceptionDialog.xaml.cs @@ -29,7 +29,7 @@ public ExceptionDialog(Exception exception) LocateLogFileButton.Click += delegate { if (App.Logger.Initialized) - Process.Start("explorer.exe", $"/select,\"{App.Logger.Filename}\""); + Process.Start("explorer.exe", $"/select,\"{App.Logger.FileLocation}\""); else Clipboard.SetText(String.Join("\r\n", App.Logger.Backlog)); }; diff --git a/Bloxstrap/Updater.cs b/Bloxstrap/Updater.cs index c3b39347..b438634d 100644 --- a/Bloxstrap/Updater.cs +++ b/Bloxstrap/Updater.cs @@ -98,6 +98,7 @@ public static void CheckInstalledVersion() ); Controls.ShowMenu(); + App.Terminate(); } } diff --git a/Bloxstrap/Utilities.cs b/Bloxstrap/Utilities.cs index 896288de..32194fad 100644 --- a/Bloxstrap/Utilities.cs +++ b/Bloxstrap/Utilities.cs @@ -1,8 +1,6 @@ using System; using System.Diagnostics; using System.IO; -using System.Text.Json; -using System.Threading.Tasks; namespace Bloxstrap { @@ -21,21 +19,6 @@ public static long GetFreeDiskSpace(string path) public static void ShellExecute(string website) => Process.Start(new ProcessStartInfo { FileName = website, UseShellExecute = true }); - public static async Task GetJson(string url) - { - try - { - string json = await App.HttpClient.GetStringAsync(url); - return JsonSerializer.Deserialize(json); - } - catch (Exception ex) - { - App.Logger.WriteLine($"[Utilities::GetJson<{typeof(T).Name}>] Failed to deserialize JSON for {url}!"); - App.Logger.WriteLine($"[Utilities::GetJson<{typeof(T).Name}>] {ex}"); - return default; - } - } - public static int VersionToNumber(string version) { // yes this is kinda stupid lol diff --git a/Bloxstrap/Utility/Http.cs b/Bloxstrap/Utility/Http.cs new file mode 100644 index 00000000..dbdec6bd --- /dev/null +++ b/Bloxstrap/Utility/Http.cs @@ -0,0 +1,25 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Bloxstrap.Utility +{ + internal static class Http + { + public static async Task GetJson(string url) + { + string json = await App.HttpClient.GetStringAsync(url); + + try + { + return JsonSerializer.Deserialize(json); + } + catch (Exception ex) + { + App.Logger.WriteLine($"[Http::GetJson<{typeof(T).Name}>] Failed to deserialize JSON for {url}!"); + App.Logger.WriteLine($"[Http::GetJson<{typeof(T).Name}>] {ex}"); + return default; + } + } + } +}