diff --git a/Bloxstrap/App.xaml.cs b/Bloxstrap/App.xaml.cs index 1423e82..db72422 100644 --- a/Bloxstrap/App.xaml.cs +++ b/Bloxstrap/App.xaml.cs @@ -1,11 +1,11 @@ using System.Reflection; +using System.Security.Cryptography; using System.Windows; using System.Windows.Threading; using Microsoft.Win32; -using Bloxstrap.Resources; -using Bloxstrap.Models.SettingTasks; +using Bloxstrap.Models.SettingTasks.Base; namespace Bloxstrap { @@ -29,11 +29,13 @@ namespace Bloxstrap public static string Version = Assembly.GetExecutingAssembly().GetName().Version!.ToString()[..^2]; + public static readonly MD5 MD5Provider = MD5.Create(); + public static NotifyIconWrapper? NotifyIcon { get; set; } public static readonly Logger Logger = new(); - public static readonly Dictionary PendingSettingTasks = new(); + public static readonly Dictionary PendingSettingTasks = new(); public static readonly JsonManager Settings = new(); @@ -84,7 +86,7 @@ namespace Bloxstrap _showingExceptionDialog = true; - if (!LaunchSettings.IsQuiet) + if (!LaunchSettings.QuietFlag.Active) Frontend.ShowExceptionDialog(exception); Terminate(ErrorCode.ERROR_INSTALL_FAILURE); @@ -188,7 +190,7 @@ namespace Bloxstrap if (Paths.Process != Paths.Application && !File.Exists(Paths.Application)) File.Copy(Paths.Process, Paths.Application); - Logger.Initialize(LaunchSettings.IsUninstall); + Logger.Initialize(LaunchSettings.UninstallFlag.Active); if (!Logger.Initialized && !Logger.NoWriteMode) { @@ -202,7 +204,7 @@ namespace Bloxstrap // we can only parse them now as settings need // to be loaded first to know what our channel is - LaunchSettings.ParseRoblox(); + // LaunchSettings.ParseRoblox(); if (!Locale.SupportedLocales.ContainsKey(Settings.Prop.Locale)) { @@ -212,7 +214,7 @@ namespace Bloxstrap Locale.Set(Settings.Prop.Locale); - if (!LaunchSettings.IsUninstall) + if (!LaunchSettings.UninstallFlag.Active) Installer.HandleUpgrade(); LaunchHandler.ProcessLaunchArgs(); diff --git a/Bloxstrap/AppData/CommonAppData.cs b/Bloxstrap/AppData/CommonAppData.cs new file mode 100644 index 0000000..5b202ab --- /dev/null +++ b/Bloxstrap/AppData/CommonAppData.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Bloxstrap.AppData +{ + public abstract class CommonAppData + { + // in case a new package is added, you can find the corresponding directory + // by opening the stock bootstrapper in a hex editor + private IReadOnlyDictionary _commonMap { get; } = new Dictionary() + { + { "Libraries.zip", @"" }, + { "shaders.zip", @"shaders\" }, + { "ssl.zip", @"ssl\" }, + + // the runtime installer is only extracted if it needs installing + { "WebView2.zip", @"" }, + { "WebView2RuntimeInstaller.zip", @"WebView2RuntimeInstaller\" }, + + { "content-avatar.zip", @"content\avatar\" }, + { "content-configs.zip", @"content\configs\" }, + { "content-fonts.zip", @"content\fonts\" }, + { "content-sky.zip", @"content\sky\" }, + { "content-sounds.zip", @"content\sounds\" }, + { "content-textures2.zip", @"content\textures\" }, + { "content-models.zip", @"content\models\" }, + + { "content-textures3.zip", @"PlatformContent\pc\textures\" }, + { "content-terrain.zip", @"PlatformContent\pc\terrain\" }, + { "content-platform-fonts.zip", @"PlatformContent\pc\fonts\" }, + + { "extracontent-luapackages.zip", @"ExtraContent\LuaPackages\" }, + { "extracontent-translations.zip", @"ExtraContent\translations\" }, + { "extracontent-models.zip", @"ExtraContent\models\" }, + { "extracontent-textures.zip", @"ExtraContent\textures\" }, + { "extracontent-places.zip", @"ExtraContent\places\" }, + }; + + public virtual IReadOnlyDictionary PackageDirectoryMap { get; set; } + + public CommonAppData() + { + if (PackageDirectoryMap is null) + { + PackageDirectoryMap = _commonMap; + return; + } + + var merged = new Dictionary(); + + foreach (var entry in _commonMap) + merged[entry.Key] = entry.Value; + + foreach (var entry in PackageDirectoryMap) + merged[entry.Key] = entry.Value; + + PackageDirectoryMap = merged; + } + } +} diff --git a/Bloxstrap/AppData/IAppData.cs b/Bloxstrap/AppData/IAppData.cs new file mode 100644 index 0000000..c637027 --- /dev/null +++ b/Bloxstrap/AppData/IAppData.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Bloxstrap.AppData +{ + internal interface IAppData + { + string ProductName { get; } + + string BinaryType { get; } + + string RegistryName { get; } + + string ExecutableName { get; } + + string StartEvent { get; } + + IReadOnlyDictionary PackageDirectoryMap { get; set; } + } +} diff --git a/Bloxstrap/AppData/RobloxPlayerData.cs b/Bloxstrap/AppData/RobloxPlayerData.cs new file mode 100644 index 0000000..3bc8785 --- /dev/null +++ b/Bloxstrap/AppData/RobloxPlayerData.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Bloxstrap.AppData +{ + public class RobloxPlayerData : CommonAppData, IAppData + { + public string ProductName { get; } = "Roblox"; + + public string BinaryType { get; } = "WindowsPlayer"; + + public string RegistryName { get; } = "RobloxPlayer"; + + public string ExecutableName { get; } = "RobloxPlayerBeta.exe"; + + public string StartEvent { get; } = "www.roblox.com/robloxStartedEvent"; + + public override IReadOnlyDictionary PackageDirectoryMap { get; set; } = new Dictionary() + { + { "RobloxApp.zip", @"" } + }; + } +} diff --git a/Bloxstrap/AppData/RobloxStudioData.cs b/Bloxstrap/AppData/RobloxStudioData.cs new file mode 100644 index 0000000..2c40ef8 --- /dev/null +++ b/Bloxstrap/AppData/RobloxStudioData.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Bloxstrap.AppData +{ + public class RobloxStudioData : CommonAppData, IAppData + { + public string ProductName { get; } = "Roblox Studio"; + + public string BinaryType { get; } = "WindowsStudio64"; + + public string RegistryName { get; } = "RobloxStudio"; + + public string ExecutableName { get; } = "RobloxStudioBeta.exe"; + + public string StartEvent { get; } = "www.roblox.com/robloxStudioStartedEvent"; + + public override IReadOnlyDictionary PackageDirectoryMap { get; set; } = new Dictionary() + { + { "RobloxStudio.zip", @"" }, + { "redist.zip", @"" }, + { "LibrariesQt5.zip", @"" }, + + { "content-studio_svg_textures.zip", @"content\studio_svg_textures\"}, + { "content-qt_translations.zip", @"content\qt_translations\" }, + { "content-api-docs.zip", @"content\api_docs\" }, + + { "extracontent-scripts.zip", @"ExtraContent\scripts\" }, + + { "BuiltInPlugins.zip", @"BuiltInPlugins\" }, + { "BuiltInStandalonePlugins.zip", @"BuiltInStandalonePlugins\" }, + + { "ApplicationConfig.zip", @"ApplicationConfig\" }, + { "Plugins.zip", @"Plugins\" }, + { "Qml.zip", @"Qml\" }, + { "StudioFonts.zip", @"StudioFonts\" } + }; + } +} diff --git a/Bloxstrap/Bootstrapper.cs b/Bloxstrap/Bootstrapper.cs index 906f3c9..c23d17d 100644 --- a/Bloxstrap/Bootstrapper.cs +++ b/Bloxstrap/Bootstrapper.cs @@ -5,6 +5,7 @@ using Microsoft.Win32; using Bloxstrap.Integrations; using Bloxstrap.Resources; +using Bloxstrap.AppData; namespace Bloxstrap { @@ -24,11 +25,12 @@ namespace Bloxstrap private bool FreshInstall => String.IsNullOrEmpty(_versionGuid); - private string _playerFileName => _launchMode == LaunchMode.Player ? "RobloxPlayerBeta.exe" : "RobloxStudioBeta.exe"; - private string _playerLocation => Path.Combine(_versionFolder, _playerFileName); + private IAppData AppData; - private string _launchCommandLine; - private LaunchMode _launchMode; + private string _playerLocation => Path.Combine(_versionFolder, AppData.ExecutableName); + + private string _launchCommandLine = App.LaunchSettings.RobloxLaunchArgs; + private LaunchMode _launchMode = App.LaunchSettings.RobloxLaunchMode; private bool _installWebView2; private string _versionGuid @@ -73,33 +75,27 @@ namespace Bloxstrap private int _packagesExtracted = 0; private bool _cancelFired = false; - private IReadOnlyDictionary _packageDirectories; - public IBootstrapperDialog? Dialog = null; public bool IsStudioLaunch => _launchMode != LaunchMode.Player; #endregion #region Core - public Bootstrapper(string launchCommandLine, LaunchMode launchMode, bool installWebView2) + public Bootstrapper(bool installWebView2) { - _launchCommandLine = launchCommandLine; - _launchMode = launchMode; _installWebView2 = installWebView2; - _packageDirectories = _launchMode == LaunchMode.Player ? PackageMap.Player : PackageMap.Studio; + if (_launchMode == LaunchMode.Player) + AppData = new RobloxPlayerData(); + else + AppData = new RobloxStudioData(); } private void SetStatus(string message) { App.Logger.WriteLine("Bootstrapper::SetStatus", message); - string productName = "Roblox"; - - if (_launchMode != LaunchMode.Player) - productName = "Roblox Studio"; - - message = message.Replace("{product}", productName); + message = message.Replace("{product}", AppData.ProductName); if (Dialog is not null) Dialog.Message = message; @@ -217,7 +213,7 @@ namespace Bloxstrap await mutex.ReleaseAsync(); - if (!App.LaunchSettings.IsNoLaunch && !_cancelFired) + if (!App.LaunchSettings.NoLaunchFlag.Active && !_cancelFired) await StartRoblox(); } @@ -225,32 +221,55 @@ namespace Bloxstrap { const string LOG_IDENT = "Bootstrapper::CheckLatestVersion"; - ClientVersion clientVersion; + // before we do anything, we need to query our channel + // if it's set in the launch uri, we need to use it and set the registry key for it + // else, check if the registry key for it exists, and use it - string binaryType = _launchMode == LaunchMode.Player ? "WindowsPlayer" : "WindowsStudio64"; + string channel = "production"; + + using var key = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\ROBLOX Corporation\\Environments\\{AppData.RegistryName}\\Channel"); + + var match = Regex.Match(App.LaunchSettings.RobloxLaunchArgs, "channel:([a-zA-Z0-9-_]+)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + if (match.Groups.Count == 2) + { + channel = match.Groups[1].Value.ToLowerInvariant(); + } + else if (key.GetValue("www.roblox.com") is string value) + { + channel = value; + } + + ClientVersion clientVersion; try { - clientVersion = await RobloxDeployment.GetInfo(App.Settings.Prop.Channel, binaryType: binaryType); + clientVersion = await RobloxDeployment.GetInfo(channel, AppData.BinaryType); } catch (HttpResponseException ex) { - if (ex.ResponseMessage.StatusCode is not HttpStatusCode.Unauthorized and not HttpStatusCode.Forbidden and not HttpStatusCode.NotFound) + if (ex.ResponseMessage.StatusCode + is not HttpStatusCode.Unauthorized + and not HttpStatusCode.Forbidden + and not HttpStatusCode.NotFound) throw; - App.Logger.WriteLine(LOG_IDENT, $"Reverting enrolled channel to {RobloxDeployment.DefaultChannel} because HTTP {(int)ex.ResponseMessage.StatusCode}"); - App.Settings.Prop.Channel = RobloxDeployment.DefaultChannel; - clientVersion = await RobloxDeployment.GetInfo(App.Settings.Prop.Channel, binaryType: binaryType); + App.Logger.WriteLine(LOG_IDENT, $"Changing channel from {channel} to {RobloxDeployment.DefaultChannel} because HTTP {(int)ex.ResponseMessage.StatusCode}"); + + channel = RobloxDeployment.DefaultChannel; + clientVersion = await RobloxDeployment.GetInfo(channel, AppData.BinaryType); } if (clientVersion.IsBehindDefaultChannel) { - App.Logger.WriteLine(LOG_IDENT, $"Changed Roblox channel from {App.Settings.Prop.Channel} to {RobloxDeployment.DefaultChannel}"); + App.Logger.WriteLine(LOG_IDENT, $"Changing channel from {channel} to {RobloxDeployment.DefaultChannel} because channel is behind production"); - App.Settings.Prop.Channel = RobloxDeployment.DefaultChannel; - clientVersion = await RobloxDeployment.GetInfo(App.Settings.Prop.Channel, binaryType: binaryType); + channel = RobloxDeployment.DefaultChannel; + clientVersion = await RobloxDeployment.GetInfo(channel, AppData.BinaryType); } + key.SetValue("www.roblox.com", channel); + _latestVersionGuid = clientVersion.VersionGuid; _versionFolder = Path.Combine(Paths.Versions, _latestVersionGuid); _versionPackageManifest = await PackageManifest.Get(_latestVersionGuid); @@ -262,28 +281,12 @@ namespace Bloxstrap SetStatus(Strings.Bootstrapper_Status_Starting); - if (_launchMode != LaunchMode.StudioAuth) + if (App.Settings.Prop.ForceRobloxLanguage) { - _launchCommandLine = _launchCommandLine.Replace("LAUNCHTIMEPLACEHOLDER", DateTimeOffset.Now.ToUnixTimeMilliseconds().ToString()); + var match = Regex.Match(_launchCommandLine, "gameLocale:([a-z_]+)", RegexOptions.CultureInvariant); - - if (_launchCommandLine.StartsWith("roblox-player:1")) - _launchCommandLine += "+channel:"; - else - _launchCommandLine += " -channel "; - - if (App.Settings.Prop.Channel.ToLowerInvariant() == RobloxDeployment.DefaultChannel.ToLowerInvariant()) - _launchCommandLine += "production"; - else - _launchCommandLine += App.Settings.Prop.Channel.ToLowerInvariant(); - - if (App.Settings.Prop.ForceRobloxLanguage) - { - var match = Regex.Match(_launchCommandLine, "gameLocale:([a-z_]+)"); - - if (match.Groups.Count == 2) - _launchCommandLine = _launchCommandLine.Replace("robloxLocale:en_us", $"robloxLocale:{match.Groups[1].Value}"); - } + if (match.Groups.Count == 2) + _launchCommandLine = _launchCommandLine.Replace("robloxLocale:en_us", $"robloxLocale:{match.Groups[1].Value}", StringComparison.InvariantCultureIgnoreCase); } // whether we should wait for roblox to exit to handle stuff in the background or clean up after roblox closes @@ -316,13 +319,13 @@ namespace Bloxstrap App.Logger.WriteLine(LOG_IDENT, $"Started Roblox (PID {gameClientPid})"); - string eventName = _launchMode == LaunchMode.Player ? "www.roblox.com/robloxStartedEvent" : "www.roblox.com/robloxQTStudioStartedEvent"; - using (SystemEvent startEvent = new(eventName)) + using (var startEvent = new SystemEvent(AppData.StartEvent)) { bool startEventFired = await startEvent.WaitForEvent(); startEvent.Close(); + // TODO: this cannot silently exit like this if (!startEventFired) return; } @@ -479,8 +482,8 @@ namespace Bloxstrap // this doesn't go under register, so we check every launch // just in case the stock bootstrapper changes it back - ProtocolHandler.Register("roblox", "Roblox", Paths.Application); - ProtocolHandler.Register("roblox-player", "Roblox", Paths.Application); + ProtocolHandler.Register("roblox", "Roblox", Paths.Application, "-player \"%1\""); + ProtocolHandler.Register("roblox-player", "Roblox", Paths.Application, "-player \"%1\""); #if STUDIO_FEATURES ProtocolHandler.Register("roblox-studio", "Roblox", Paths.Application); ProtocolHandler.Register("roblox-studio-auth", "Roblox", Paths.Application); @@ -686,7 +689,7 @@ namespace Bloxstrap // move old compatibility flags for the old location using (RegistryKey appFlagsKey = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers")) { - string oldGameClientLocation = Path.Combine(oldVersionFolder, _playerFileName); + string oldGameClientLocation = Path.Combine(oldVersionFolder, AppData.ExecutableName); string? appFlags = (string?)appFlagsKey.GetValue(oldGameClientLocation); if (appFlags is not null) @@ -803,7 +806,7 @@ namespace Bloxstrap { const string LOG_IDENT = "Bootstrapper::ApplyModifications"; - if (Process.GetProcessesByName(_playerFileName[..^4]).Any()) + if (Process.GetProcessesByName(AppData.ExecutableName[..^4]).Any()) { App.Logger.WriteLine(LOG_IDENT, "Roblox is running, aborting mod check"); return; @@ -864,72 +867,6 @@ namespace Bloxstrap if (!Directory.Exists(Paths.Modifications)) Directory.CreateDirectory(Paths.Modifications); - // cursors - await CheckModPreset(App.Settings.Prop.CursorType == CursorType.From2006, new Dictionary - { - { @"content\textures\Cursors\KeyboardMouse\ArrowCursor.png", "Cursor.From2006.ArrowCursor.png" }, - { @"content\textures\Cursors\KeyboardMouse\ArrowFarCursor.png", "Cursor.From2006.ArrowFarCursor.png" } - }); - - await CheckModPreset(App.Settings.Prop.CursorType == CursorType.From2013, new Dictionary - { - { @"content\textures\Cursors\KeyboardMouse\ArrowCursor.png", "Cursor.From2013.ArrowCursor.png" }, - { @"content\textures\Cursors\KeyboardMouse\ArrowFarCursor.png", "Cursor.From2013.ArrowFarCursor.png" } - }); - - // character sounds - await CheckModPreset(App.Settings.Prop.UseOldDeathSound, @"content\sounds\ouch.ogg", "Sounds.OldDeath.ogg"); - - await CheckModPreset(App.Settings.Prop.UseOldCharacterSounds, new Dictionary - { - { @"content\sounds\action_footsteps_plastic.mp3", "Sounds.OldWalk.mp3" }, - { @"content\sounds\action_jump.mp3", "Sounds.OldJump.mp3" }, - { @"content\sounds\action_get_up.mp3", "Sounds.OldGetUp.mp3" }, - { @"content\sounds\action_falling.mp3", "Sounds.Empty.mp3" }, - { @"content\sounds\action_jump_land.mp3", "Sounds.Empty.mp3" }, - { @"content\sounds\action_swim.mp3", "Sounds.Empty.mp3" }, - { @"content\sounds\impact_water.mp3", "Sounds.Empty.mp3" } - }); - - // Mobile.rbxl - await CheckModPreset(App.Settings.Prop.UseOldAvatarBackground, @"ExtraContent\places\Mobile.rbxl", "OldAvatarBackground.rbxl"); - - // emoji presets are downloaded remotely from github due to how large they are - string contentFonts = Path.Combine(Paths.Modifications, "content\\fonts"); - string emojiFontLocation = Path.Combine(contentFonts, "TwemojiMozilla.ttf"); - string emojiFontHash = File.Exists(emojiFontLocation) ? MD5Hash.FromFile(emojiFontLocation) : ""; - - if (App.Settings.Prop.EmojiType == EmojiType.Default && EmojiTypeEx.Hashes.Values.Contains(emojiFontHash)) - { - App.Logger.WriteLine(LOG_IDENT, "Reverting to default emoji font"); - - File.Delete(emojiFontLocation); - } - else if (App.Settings.Prop.EmojiType != EmojiType.Default && emojiFontHash != App.Settings.Prop.EmojiType.GetHash()) - { - App.Logger.WriteLine(LOG_IDENT, $"Configuring emoji font as {App.Settings.Prop.EmojiType}"); - - if (emojiFontHash != "") - File.Delete(emojiFontLocation); - - Directory.CreateDirectory(contentFonts); - - try - { - var response = await App.HttpClient.GetAsync(App.Settings.Prop.EmojiType.GetUrl()); - response.EnsureSuccessStatusCode(); - await using var fileStream = new FileStream(emojiFontLocation, FileMode.CreateNew); - await response.Content.CopyToAsync(fileStream); - } - catch (HttpRequestException ex) - { - App.Logger.WriteLine(LOG_IDENT, $"Failed to fetch emoji preset from Github"); - App.Logger.WriteException(LOG_IDENT, ex); - Frontend.ShowMessageBox(string.Format(Strings.Bootstrapper_EmojiPresetFetchFailed, App.Settings.Prop.EmojiType), MessageBoxImage.Warning); - App.Settings.Prop.EmojiType = EmojiType.Default; - } - } - // check custom font mod // instead of replacing the fonts themselves, we'll just alter the font family manifests @@ -1015,7 +952,7 @@ namespace Bloxstrap if (modFolderFiles.Contains(fileLocation)) continue; - var package = _packageDirectories.SingleOrDefault(x => x.Value != "" && fileLocation.StartsWith(x.Value)); + var package = AppData.PackageDirectoryMap.SingleOrDefault(x => x.Value != "" && fileLocation.StartsWith(x.Value)); // package doesn't exist, likely mistakenly placed file if (String.IsNullOrEmpty(package.Key)) @@ -1043,54 +980,6 @@ namespace Bloxstrap App.Logger.WriteLine(LOG_IDENT, $"Finished checking file mods"); } - private static async Task CheckModPreset(bool condition, string location, string name) - { - string LOG_IDENT = $"Bootstrapper::CheckModPreset.{name}"; - - string fullLocation = Path.Combine(Paths.Modifications, location); - string fileHash = File.Exists(fullLocation) ? MD5Hash.FromFile(fullLocation) : ""; - - if (!condition && fileHash == "") - return; - - byte[] embeddedData = string.IsNullOrEmpty(name) ? Array.Empty() : await Resource.Get(name); - string embeddedHash = MD5Hash.FromBytes(embeddedData); - - if (!condition) - { - if (fileHash == embeddedHash) - { - App.Logger.WriteLine(LOG_IDENT, $"Deleting '{location}' as preset is disabled, and mod file matches preset"); - - Filesystem.AssertReadOnly(fullLocation); - File.Delete(fullLocation); - } - - return; - } - - if (fileHash != embeddedHash) - { - App.Logger.WriteLine(LOG_IDENT, $"Writing '{location}' as preset is enabled, and mod file does not exist or does not match preset"); - - Directory.CreateDirectory(Path.GetDirectoryName(fullLocation)!); - - if (File.Exists(fullLocation)) - { - Filesystem.AssertReadOnly(fullLocation); - File.Delete(fullLocation); - } - - await File.WriteAllBytesAsync(fullLocation, embeddedData); - } - } - - private static async Task CheckModPreset(bool condition, Dictionary mapping) - { - foreach (var pair in mapping) - await CheckModPreset(condition, pair.Key, pair.Value); - } - private async Task DownloadPackage(Package package) { string LOG_IDENT = $"Bootstrapper::DownloadPackage.{package.Name}"; @@ -1233,7 +1122,7 @@ namespace Bloxstrap return Task.CompletedTask; string packageLocation = Path.Combine(Paths.Downloads, package.Signature); - string packageFolder = Path.Combine(_versionFolder, _packageDirectories[package.Name]); + string packageFolder = Path.Combine(_versionFolder, AppData.PackageDirectoryMap[package.Name]); App.Logger.WriteLine(LOG_IDENT, $"Extracting {package.Name}..."); @@ -1263,7 +1152,7 @@ namespace Bloxstrap if (entry is null) return; - string extractionPath = Path.Combine(_versionFolder, _packageDirectories[package.Name], entry.FullName); + string extractionPath = Path.Combine(_versionFolder, AppData.PackageDirectoryMap[package.Name], entry.FullName); entry.ExtractToFile(extractionPath, true); } #endregion diff --git a/Bloxstrap/Enums/CursorType.cs b/Bloxstrap/Enums/CursorType.cs index 76e5038..8b0fa60 100644 --- a/Bloxstrap/Enums/CursorType.cs +++ b/Bloxstrap/Enums/CursorType.cs @@ -2,9 +2,14 @@ { public enum CursorType { + [EnumSort(Order = 1)] [EnumName(FromTranslation = "Common.Default")] Default, + + [EnumSort(Order = 3)] From2006, + + [EnumSort(Order = 2)] From2013 } } diff --git a/Bloxstrap/Enums/LaunchMode.cs b/Bloxstrap/Enums/LaunchMode.cs index d1a34e9..c045d4f 100644 --- a/Bloxstrap/Enums/LaunchMode.cs +++ b/Bloxstrap/Enums/LaunchMode.cs @@ -2,6 +2,7 @@ { public enum LaunchMode { + None, Player, Studio, StudioAuth diff --git a/Bloxstrap/Extensions/CursorTypeEx.cs b/Bloxstrap/Extensions/CursorTypeEx.cs deleted file mode 100644 index 35efd7b..0000000 --- a/Bloxstrap/Extensions/CursorTypeEx.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Bloxstrap.Extensions -{ - static class CursorTypeEx - { - public static IReadOnlyCollection Selections => new CursorType[] - { - CursorType.Default, - CursorType.From2013, - CursorType.From2006 - }; - } -} diff --git a/Bloxstrap/Extensions/EmojiTypeEx.cs b/Bloxstrap/Extensions/EmojiTypeEx.cs index aa826e5..e1b32fd 100644 --- a/Bloxstrap/Extensions/EmojiTypeEx.cs +++ b/Bloxstrap/Extensions/EmojiTypeEx.cs @@ -2,15 +2,6 @@ { static class EmojiTypeEx { - public static IReadOnlyCollection Selections => new EmojiType[] - { - EmojiType.Default, - EmojiType.Catmoji, - EmojiType.Windows11, - EmojiType.Windows10, - EmojiType.Windows8 - }; - public static IReadOnlyDictionary Filenames => new Dictionary { { EmojiType.Catmoji, "Catmoji.ttf" }, diff --git a/Bloxstrap/FastFlagManager.cs b/Bloxstrap/FastFlagManager.cs index db4addf..492bb68 100644 --- a/Bloxstrap/FastFlagManager.cs +++ b/Bloxstrap/FastFlagManager.cs @@ -1,8 +1,4 @@ using Bloxstrap.Enums.FlagPresets; -using System.Windows.Forms; - -using Windows.Win32; -using Windows.Win32.Graphics.Gdi; namespace Bloxstrap { diff --git a/Bloxstrap/GlobalUsings.cs b/Bloxstrap/GlobalUsings.cs index 15c873c..9c10516 100644 --- a/Bloxstrap/GlobalUsings.cs +++ b/Bloxstrap/GlobalUsings.cs @@ -22,5 +22,6 @@ global using Bloxstrap.Models.Attributes; global using Bloxstrap.Models.BloxstrapRPC; global using Bloxstrap.Models.RobloxApi; global using Bloxstrap.Models.Manifest; +global using Bloxstrap.Resources; global using Bloxstrap.UI; global using Bloxstrap.Utility; \ No newline at end of file diff --git a/Bloxstrap/Installer.cs b/Bloxstrap/Installer.cs index b07634c..ccb458c 100644 --- a/Bloxstrap/Installer.cs +++ b/Bloxstrap/Installer.cs @@ -61,8 +61,8 @@ namespace Bloxstrap // only register player, for the scenario where the user installs bloxstrap, closes it, // and then launches from the website expecting it to work // studio can be implicitly registered when it's first launched manually - ProtocolHandler.Register("roblox", "Roblox", Paths.Application); - ProtocolHandler.Register("roblox-player", "Roblox", Paths.Application); + ProtocolHandler.Register("roblox", "Roblox", Paths.Application, "-player \"%1\""); + ProtocolHandler.Register("roblox-player", "Roblox", Paths.Application, "-player \"%1\""); // TODO: implicit installation needs to reregister studio @@ -167,17 +167,15 @@ namespace Bloxstrap // prompt to shutdown roblox if its currently running if (processes.Any()) { - if (!App.LaunchSettings.IsQuiet) - { - MessageBoxResult result = Frontend.ShowMessageBox( - Strings.Bootstrapper_Uninstall_RobloxRunning, - MessageBoxImage.Information, - MessageBoxButton.OKCancel - ); + var result = Frontend.ShowMessageBox( + Strings.Bootstrapper_Uninstall_RobloxRunning, + MessageBoxImage.Information, + MessageBoxButton.OKCancel, + MessageBoxResult.OK + ); - if (result != MessageBoxResult.OK) - App.Terminate(ErrorCode.ERROR_CANCELLED); - } + if (result != MessageBoxResult.OK) + App.Terminate(ErrorCode.ERROR_CANCELLED); try { @@ -343,7 +341,7 @@ namespace Bloxstrap return; // silently upgrade version if the command line flag is set or if we're launching from an auto update - if (!App.LaunchSettings.IsUpgrade && !isAutoUpgrade) + if (!App.LaunchSettings.UpgradeFlag.Active && !isAutoUpgrade) { var result = Frontend.ShowMessageBox( Strings.InstallChecker_VersionDifferentThanInstalled, @@ -459,6 +457,9 @@ namespace Bloxstrap } Registry.CurrentUser.DeleteSubKeyTree("Software\\Bloxstrap", false); + + ProtocolHandler.Register("roblox", "Roblox", Paths.Application, "-player \"%1\""); + ProtocolHandler.Register("roblox-player", "Roblox", Paths.Application, "-player \"%1\""); } App.Settings.Save(); @@ -469,7 +470,7 @@ namespace Bloxstrap { Utilities.ShellExecute($"https://github.com/{App.ProjectRepository}/wiki/Release-notes-for-Bloxstrap-v{currentVer}"); } - else if (!App.LaunchSettings.IsQuiet) + else { Frontend.ShowMessageBox( string.Format(Strings.InstallChecker_Updated, currentVer), diff --git a/Bloxstrap/JsonManager.cs b/Bloxstrap/JsonManager.cs index e1078e7..1a91949 100644 --- a/Bloxstrap/JsonManager.cs +++ b/Bloxstrap/JsonManager.cs @@ -2,9 +2,10 @@ namespace Bloxstrap { - public class JsonManager where T : new() + public class JsonManager where T : class, new() { public T Prop { get; set; } = new(); + public virtual string FileLocation => Path.Combine(Paths.Base, $"{typeof(T).Name}.json"); private string LOG_IDENT_CLASS => $"JsonManager<{typeof(T).Name}>"; diff --git a/Bloxstrap/LaunchHandler.cs b/Bloxstrap/LaunchHandler.cs index cc40ef1..58ef421 100644 --- a/Bloxstrap/LaunchHandler.cs +++ b/Bloxstrap/LaunchHandler.cs @@ -1,12 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; +using System.Windows; using Bloxstrap.UI.Elements.Dialogs; -using Bloxstrap.Resources; using Microsoft.Win32; using Windows.Win32; @@ -38,28 +32,36 @@ namespace Bloxstrap { // this order is specific - if (App.LaunchSettings.IsUninstall) + if (App.LaunchSettings.UninstallFlag.Active) LaunchUninstaller(); - else if (App.LaunchSettings.IsMenuLaunch) + else if (App.LaunchSettings.MenuFlag.Active) LaunchSettings(); - else if (App.LaunchSettings.IsRobloxLaunch) + else if (App.LaunchSettings.WatcherFlag.Active) + LaunchWatcher(); + else if (App.LaunchSettings.RobloxLaunchMode != LaunchMode.None) LaunchRoblox(); - else if (!App.LaunchSettings.IsQuiet) + else if (!App.LaunchSettings.QuietFlag.Active) LaunchMenu(); } public static void LaunchInstaller() { - // TODO: detect duplicate launch, mutex maybe? + using var interlock = new InterProcessLock("Installer"); - if (App.LaunchSettings.IsUninstall) + if (!interlock.IsAcquired) + { + Frontend.ShowMessageBox(Strings.Dialog_AlreadyRunning_Installer, MessageBoxImage.Stop); + return; + } + + if (App.LaunchSettings.UninstallFlag.Active) { Frontend.ShowMessageBox(Strings.Bootstrapper_FirstRunUninstall, MessageBoxImage.Error); App.Terminate(ErrorCode.ERROR_INVALID_FUNCTION); return; } - if (App.LaunchSettings.IsQuiet) + if (App.LaunchSettings.QuietFlag.Active) { var installer = new Installer(); @@ -68,6 +70,8 @@ namespace Bloxstrap installer.DoInstall(); + interlock.Dispose(); + ProcessLaunchArgs(); } else @@ -77,6 +81,8 @@ namespace Bloxstrap var installer = new UI.Elements.Installer.MainWindow(); installer.ShowDialog(); + interlock.Dispose(); + ProcessNextAction(installer.CloseAction, !installer.Finished); } @@ -84,10 +90,18 @@ namespace Bloxstrap public static void LaunchUninstaller() { + using var interlock = new InterProcessLock("Uninstaller"); + + if (!interlock.IsAcquired) + { + Frontend.ShowMessageBox(Strings.Dialog_AlreadyRunning_Uninstaller, MessageBoxImage.Stop); + return; + } + bool confirmed = false; bool keepData = true; - if (App.LaunchSettings.IsQuiet) + if (App.LaunchSettings.QuietFlag.Active) { confirmed = true; } @@ -112,20 +126,21 @@ namespace Bloxstrap { const string LOG_IDENT = "LaunchHandler::LaunchSettings"; - // TODO: move to mutex (especially because multi language whatever) + using var interlock = new InterProcessLock("Settings"); - Process? menuProcess = Utilities.GetProcessesSafe().Where(x => x.MainWindowTitle == Strings.Menu_Title).FirstOrDefault(); - - if (menuProcess is not null) + if (interlock.IsAcquired) { - var handle = menuProcess.MainWindowHandle; - App.Logger.WriteLine(LOG_IDENT, $"Found an already existing menu window with handle {handle}"); - PInvoke.SetForegroundWindow((HWND)handle); + bool showAlreadyRunningWarning = Process.GetProcessesByName(App.ProjectName).Length > 1; + new UI.Elements.Settings.MainWindow(showAlreadyRunningWarning).ShowDialog(); } else { - bool showAlreadyRunningWarning = Process.GetProcessesByName(App.ProjectName).Length > 1 && !App.LaunchSettings.IsQuiet; - new UI.Elements.Settings.MainWindow(showAlreadyRunningWarning).ShowDialog(); + App.Logger.WriteLine(LOG_IDENT, $"Found an already existing menu window"); + + var process = Utilities.GetProcessesSafe().Where(x => x.MainWindowTitle == Strings.Menu_Title).FirstOrDefault(); + + if (process is not null) + PInvoke.SetForegroundWindow((HWND)process.MainWindowHandle); } } @@ -141,18 +156,18 @@ namespace Bloxstrap { const string LOG_IDENT = "LaunchHandler::LaunchRoblox"; - bool installWebView2 = false; if (!File.Exists(Path.Combine(Paths.System, "mfplat.dll"))) { Frontend.ShowMessageBox(Strings.Bootstrapper_WMFNotFound, MessageBoxImage.Error); - if (!App.LaunchSettings.IsQuiet) + if (!App.LaunchSettings.QuietFlag.Active) Utilities.ShellExecute("https://support.microsoft.com/en-us/topic/media-feature-pack-list-for-windows-n-editions-c1c6fffa-d052-8338-7a79-a4bb980a700a"); App.Terminate(ErrorCode.ERROR_FILE_NOT_FOUND); } + bool installWebView2 = false; { using var hklmKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"); using var hkcuKey = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"); @@ -180,10 +195,10 @@ namespace Bloxstrap // start bootstrapper and show the bootstrapper modal if we're not running silently App.Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper"); - var bootstrapper = new Bootstrapper(App.LaunchSettings.RobloxLaunchArgs, App.LaunchSettings.RobloxLaunchMode, installWebView2); + var bootstrapper = new Bootstrapper(installWebView2); IBootstrapperDialog? dialog = null; - if (!App.LaunchSettings.IsQuiet) + if (!App.LaunchSettings.QuietFlag.Active) { App.Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper dialog"); dialog = App.Settings.Prop.BootstrapperStyle.GetNew(); @@ -219,12 +234,17 @@ namespace Bloxstrap // this ordering is very important as all wpf windows are shown as modal dialogs, mess it up and you'll end up blocking input to one of them dialog?.ShowBootstrapper(); - if (!App.LaunchSettings.IsNoLaunch && App.Settings.Prop.EnableActivityTracking) + if (!App.LaunchSettings.NoLaunchFlag.Active && App.Settings.Prop.EnableActivityTracking) App.NotifyIcon?.InitializeContextMenu(); App.Logger.WriteLine(LOG_IDENT, "Waiting for bootstrapper task to finish"); bootstrapperTask.Wait(); } + + public static void LaunchWatcher() + { + + } } } diff --git a/Bloxstrap/LaunchSettings.cs b/Bloxstrap/LaunchSettings.cs index e2062d0..a217d09 100644 --- a/Bloxstrap/LaunchSettings.cs +++ b/Bloxstrap/LaunchSettings.cs @@ -12,182 +12,97 @@ namespace Bloxstrap { public class LaunchSettings { - [LaunchFlag(new[] { "-preferences", "-menu", "-settings" })] - public bool IsMenuLaunch { get; set; } = false; + public LaunchFlag MenuFlag { get; } = new("preferences,menu,settings"); - [LaunchFlag(new[] { "-player", "-studio" })] - public bool IsRobloxLaunch { get; set; } = false; + public LaunchFlag WatcherFlag { get; } = new("watcher"); - [LaunchFlag("-quiet")] - public bool IsQuiet { get; private set; } = false; + public LaunchFlag QuietFlag { get; } = new("quiet"); - [LaunchFlag("-uninstall")] - public bool IsUninstall { get; private set; } = false; + public LaunchFlag UninstallFlag { get; } = new("uninstall"); - [LaunchFlag("-nolaunch")] - public bool IsNoLaunch { get; private set; } = false; + public LaunchFlag NoLaunchFlag { get; } = new("nolaunch"); - [LaunchFlag("-upgrade")] - public bool IsUpgrade { get; private set; } = false; + public LaunchFlag UpgradeFlag { get; } = new("upgrade"); + + public LaunchFlag PlayerFlag { get; } = new("player"); + + public LaunchFlag StudioFlag { get; } = new("studio"); - public LaunchMode RobloxLaunchMode { get; private set; } = LaunchMode.Player; + public LaunchMode RobloxLaunchMode { get; private set; } = LaunchMode.None; - public string RobloxLaunchArgs { get; private set; } = "--app"; + public string RobloxLaunchArgs { get; private set; } = ""; /// /// Original launch arguments /// public string[] Args { get; private set; } - private Dictionary? _flagMap; - - private string? _robloxArg; - - // pizzaboxer wanted this - private void ParseLaunchFlagProps() - { - _flagMap = new Dictionary(); - - foreach (var prop in typeof(LaunchSettings).GetProperties()) - { - var attr = prop.GetCustomAttribute(); - - if (attr == null) - continue; - - if (!string.IsNullOrEmpty(attr.Name)) - { - _flagMap[attr.Name] = prop; - } - else - { - foreach (var name in attr.Names!) - _flagMap[name] = prop; - } - } - } - - private void ParseFlag(string arg) - { - const string LOG_IDENT = "LaunchSettings::ParseFlag"; - - arg = arg.ToLowerInvariant(); - - if (_flagMap!.ContainsKey(arg)) - { - var prop = _flagMap[arg]; - prop.SetValue(this, true); - App.Logger.WriteLine(LOG_IDENT, $"Started with {prop.Name} flag"); - } - } - - // private void ParseRoblox(string arg, ref int i) - public void ParseRoblox() - { - string? arg = _robloxArg; - - if (arg is null) - return; - - if (arg.StartsWith("roblox-player:")) - { - IsRobloxLaunch = true; - - RobloxLaunchArgs = ProtocolHandler.ParseUri(arg); - - RobloxLaunchMode = LaunchMode.Player; - } - else if (arg.StartsWith("roblox:")) - { - IsRobloxLaunch = true; - - RobloxLaunchArgs = $"--app --deeplink {arg}"; - - RobloxLaunchMode = LaunchMode.Player; - } -#if STUDIO_FEATURES - else if (arg.StartsWith("roblox-studio:")) - { - RobloxLaunchArgs = ProtocolHandler.ParseUri(arg); - - if (!RobloxLaunchArgs.Contains("-startEvent")) - RobloxLaunchArgs += " -startEvent www.roblox.com/robloxQTStudioStartedEvent"; - - RobloxLaunchMode = LaunchMode.Studio; - } - else if (arg.StartsWith("roblox-studio-auth:")) - { - RobloxLaunchArgs = HttpUtility.UrlDecode(arg); - - RobloxLaunchMode = LaunchMode.StudioAuth; - } - else if (arg == "-ide") - { - RobloxLaunchMode = LaunchMode.Studio; - - if (Args.Length >= 2) - { - string pathArg = Args[i + 1]; - - if (pathArg.StartsWith('-')) - return; // likely a launch flag, ignore it. - - i++; // path arg - RobloxLaunchArgs = $"-task EditFile -localPlaceFile \"{pathArg}\""; - } - } -#endif - } - - private void Parse() - { - const string LOG_IDENT = "LaunchSettings::Parse"; - - App.Logger.WriteLine(LOG_IDENT, "Parsing launch arguments"); - -#if DEBUG - App.Logger.WriteLine(LOG_IDENT, $"Launch arguments: {string.Join(' ', Args)}"); -#endif - - if (Args.Length == 0) - { - App.Logger.WriteLine(LOG_IDENT, "No launch arguments to parse"); - return; - } - - int idx = 0; - string firstArg = Args[0]; - - // check & handle roblox arg - if (!firstArg.StartsWith('-') || firstArg == "-ide") - { - // ParseRoblox(firstArg, ref idx); - _robloxArg = firstArg; - idx++; // roblox arg - } - - // check if there are any launch flags - if (idx > Args.Length - 1) - return; - - App.Logger.WriteLine(LOG_IDENT, "Parsing launch flags"); - - // map out launch flags - ParseLaunchFlagProps(); - - // parse any launch flags - for (int i = idx; i < Args.Length; i++) - ParseFlag(Args[i]); - - // cleanup flag map - _flagMap!.Clear(); - _flagMap = null; - } + private Dictionary _flagMap = new(); public LaunchSettings(string[] args) { + const string LOG_IDENT = "LaunchSettings"; + Args = args; - Parse(); + + // build flag map + foreach (var prop in this.GetType().GetProperties()) + { + if (prop.PropertyType != typeof(LaunchFlag)) + continue; + + if (prop.GetValue(this) is not LaunchFlag flag) + continue; + + foreach (string identifier in flag.Identifiers.Split(',')) + _flagMap.Add(identifier, flag); + } + + // parse + for (int i = 0; i < Args.Length; i++) + { + string arg = Args[i]; + + if (!arg.StartsWith('-')) + continue; + + string identifier = arg[1..]; + + if (_flagMap[identifier] is not LaunchFlag flag) + continue; + + flag.Active = true; + + if (i < Args.Length - 1 && Args[i+1] is string nextArg && !nextArg.StartsWith('-')) + { + flag.Data = nextArg; + App.Logger.WriteLine(LOG_IDENT, $"Identifier '{identifier}' is active with data"); + } + else + { + App.Logger.WriteLine(LOG_IDENT, $"Identifier '{identifier}' is active"); + } + } + + if (PlayerFlag.Active) + ParsePlayer(PlayerFlag.Data); + else if (StudioFlag.Active) + ParseStudio(StudioFlag.Data); + } + + private void ParsePlayer(string? data) + { + RobloxLaunchMode = LaunchMode.Player; + + if (!String.IsNullOrEmpty(data)) + RobloxLaunchArgs = data; + } + + private void ParseStudio(string? data) + { + RobloxLaunchMode = LaunchMode.Studio; + + // TODO: do this later } } } diff --git a/Bloxstrap/Models/Attributes/EnumSortAttribute.cs b/Bloxstrap/Models/Attributes/EnumSortAttribute.cs new file mode 100644 index 0000000..837a24d --- /dev/null +++ b/Bloxstrap/Models/Attributes/EnumSortAttribute.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Bloxstrap.Models.Attributes +{ + class EnumSortAttribute : Attribute + { + public int Order { get; set; } + } +} diff --git a/Bloxstrap/Models/Attributes/LaunchFlagAttribute.cs b/Bloxstrap/Models/Attributes/LaunchFlagAttribute.cs deleted file mode 100644 index 60c3886..0000000 --- a/Bloxstrap/Models/Attributes/LaunchFlagAttribute.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Bloxstrap.Models.Attributes -{ - public class LaunchFlagAttribute : Attribute - { - public string? Name { get; private set; } - public string[]? Names { get; private set; } - - public LaunchFlagAttribute(string name) - { - Name = name; - } - - public LaunchFlagAttribute(string[] names) - { - Names = names; - } - } -} diff --git a/Bloxstrap/Models/LaunchFlag.cs b/Bloxstrap/Models/LaunchFlag.cs new file mode 100644 index 0000000..ead3886 --- /dev/null +++ b/Bloxstrap/Models/LaunchFlag.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Bloxstrap.Models +{ + public class LaunchFlag + { + public string Identifiers { get; private set; } + + public bool Active = false; + + public string? Data; + + public LaunchFlag(string identifiers) + { + Identifiers = identifiers; + } + } +} diff --git a/Bloxstrap/Models/ModPresetFileData.cs b/Bloxstrap/Models/ModPresetFileData.cs new file mode 100644 index 0000000..d704891 --- /dev/null +++ b/Bloxstrap/Models/ModPresetFileData.cs @@ -0,0 +1,40 @@ +using System.Security.Cryptography; +using System.Windows.Markup; + +namespace Bloxstrap.Models +{ + public class ModPresetFileData + { + public string FilePath { get; private set; } + + public string FullFilePath => Path.Combine(Paths.Modifications, FilePath); + + public FileStream FileStream => File.OpenRead(FullFilePath); + + public string ResourceIdentifier { get; private set; } + + public Stream ResourceStream => Resource.GetStream(ResourceIdentifier); + + public byte[] ResourceHash { get; private set; } + + public ModPresetFileData(string contentPath, string resource) + { + FilePath = contentPath; + ResourceIdentifier = resource; + + using var stream = ResourceStream; + ResourceHash = App.MD5Provider.ComputeHash(stream); + } + + public bool HashMatches() + { + if (!File.Exists(FullFilePath)) + return false; + + using var fileStream = FileStream; + var fileHash = App.MD5Provider.ComputeHash(fileStream); + + return fileHash.SequenceEqual(ResourceHash); + } + } +} diff --git a/Bloxstrap/Models/SettingTasks/Base/BaseTask.cs b/Bloxstrap/Models/SettingTasks/Base/BaseTask.cs new file mode 100644 index 0000000..15f3ec8 --- /dev/null +++ b/Bloxstrap/Models/SettingTasks/Base/BaseTask.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Bloxstrap.Models.SettingTasks.Base +{ + public abstract class BaseTask + { + public string Name { get; private set; } + + public abstract bool Changed { get; } + + public BaseTask(string prefix, string name) => Name = $"{prefix}.{name}"; + + public override string ToString() => Name; + + public abstract void Execute(); + } +} diff --git a/Bloxstrap/Models/SettingTasks/BaseTask.cs b/Bloxstrap/Models/SettingTasks/Base/BoolBaseTask.cs similarity index 51% rename from Bloxstrap/Models/SettingTasks/BaseTask.cs rename to Bloxstrap/Models/SettingTasks/Base/BoolBaseTask.cs index 21d0c63..b22b067 100644 --- a/Bloxstrap/Models/SettingTasks/BaseTask.cs +++ b/Bloxstrap/Models/SettingTasks/Base/BoolBaseTask.cs @@ -4,36 +4,28 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Bloxstrap.Models.SettingTasks +namespace Bloxstrap.Models.SettingTasks.Base { - public class BaseTask : ISettingTask + public abstract class BoolBaseTask : BaseTask { private bool _originalState; - + private bool _newState; - public string Name { get; set; } = ""; - - public bool OriginalState + public virtual bool OriginalState { - get - { - return _originalState; - } + get => _originalState; - set + set { _originalState = value; _newState = value; } } - public bool NewState + public virtual bool NewState { - get - { - return _newState; - } + get => _newState; set { @@ -42,6 +34,8 @@ namespace Bloxstrap.Models.SettingTasks } } - public virtual void Execute() => throw new NotImplementedException(); + public override bool Changed => NewState != OriginalState; + + public BoolBaseTask(string prefix, string name) : base(prefix, name) { } } } diff --git a/Bloxstrap/Models/SettingTasks/Base/EnumBaseTask.cs b/Bloxstrap/Models/SettingTasks/Base/EnumBaseTask.cs new file mode 100644 index 0000000..56a8c47 --- /dev/null +++ b/Bloxstrap/Models/SettingTasks/Base/EnumBaseTask.cs @@ -0,0 +1,49 @@ +namespace Bloxstrap.Models.SettingTasks.Base +{ + public abstract class EnumBaseTask : BaseTask where T : struct, Enum + { + private T _originalState = default!; + + private T _newState = default!; + + public virtual T OriginalState + { + get => _originalState; + + set + { + _originalState = value; + _newState = value; + } + } + + public virtual T NewState + { + get => _newState; + + set + { + App.PendingSettingTasks[Name] = this; + _newState = value; + } + } + + public override bool Changed => !NewState.Equals(OriginalState); + + public IEnumerable Selections { get; private set; } + = Enum.GetValues(typeof(T)).Cast().OrderBy(x => + { + var attributes = x.GetType().GetMember(x.ToString())[0].GetCustomAttributes(typeof(EnumSortAttribute), false); + + if (attributes.Length > 0) + { + var attribute = (EnumSortAttribute)attributes[0]; + return attribute.Order; + } + + return 0; + }); + + public EnumBaseTask(string prefix, string name) : base(prefix, name) { } + } +} diff --git a/Bloxstrap/Models/SettingTasks/Base/StringBaseTask.cs b/Bloxstrap/Models/SettingTasks/Base/StringBaseTask.cs new file mode 100644 index 0000000..dd4a07f --- /dev/null +++ b/Bloxstrap/Models/SettingTasks/Base/StringBaseTask.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Bloxstrap.Models.SettingTasks.Base +{ + public abstract class StringBaseTask : BaseTask + { + private string _originalState = ""; + + private string _newState = ""; + + public virtual string OriginalState + { + get => _originalState; + + set + { + _originalState = value; + _newState = value; + } + } + + public virtual string NewState + { + get => _newState; + + set + { + App.PendingSettingTasks[Name] = this; + _newState = value; + } + } + + public override bool Changed => NewState != OriginalState; + + public StringBaseTask(string prefix, string name) : base(prefix, name) { } + } +} diff --git a/Bloxstrap/Models/SettingTasks/EmojiModPresetTask.cs b/Bloxstrap/Models/SettingTasks/EmojiModPresetTask.cs new file mode 100644 index 0000000..08c0edf --- /dev/null +++ b/Bloxstrap/Models/SettingTasks/EmojiModPresetTask.cs @@ -0,0 +1,69 @@ +using System.Windows; + +using Bloxstrap.Models.SettingTasks.Base; + +namespace Bloxstrap.Models.SettingTasks +{ + public class EmojiModPresetTask : EnumBaseTask + { + private string _filePath => Path.Combine(Paths.Modifications, @"content\fonts\TwemojiMozilla.ttf"); + + private IEnumerable>? QueryCurrentValue() + { + if (!File.Exists(_filePath)) + return null; + + using var fileStream = File.OpenRead(_filePath); + string hash = MD5Hash.Stringify(App.MD5Provider.ComputeHash(fileStream)); + + return EmojiTypeEx.Hashes.Where(x => x.Value == hash); + } + + public EmojiModPresetTask() : base("ModPreset", "EmojiFont") + { + var query = QueryCurrentValue(); + + if (query is not null) + OriginalState = query.FirstOrDefault().Key; + } + + public override async void Execute() + { + const string LOG_IDENT = "EmojiModPresetTask::Execute"; + + var query = QueryCurrentValue(); + + if (NewState != EmojiType.Default && (query is null || query.FirstOrDefault().Key != NewState)) + { + try + { + var response = await App.HttpClient.GetAsync(NewState.GetUrl()); + + response.EnsureSuccessStatusCode(); + + Directory.CreateDirectory(Path.GetDirectoryName(_filePath)!); + + await using var fileStream = new FileStream(_filePath, FileMode.CreateNew); + await response.Content.CopyToAsync(fileStream); + + OriginalState = NewState; + } + catch (Exception ex) + { + App.Logger.WriteException(LOG_IDENT, ex); + + Frontend.ShowMessageBox( + String.Format(Strings.Menu_Mods_Presets_EmojiType_Error, ex.Message), + MessageBoxImage.Warning); + } + } + else if (query is not null && query.Any()) + { + Filesystem.AssertReadOnly(_filePath); + File.Delete(_filePath); + + OriginalState = NewState; + } + } + } +} diff --git a/Bloxstrap/Models/SettingTasks/EnumModPresetTask.cs b/Bloxstrap/Models/SettingTasks/EnumModPresetTask.cs new file mode 100644 index 0000000..a06808e --- /dev/null +++ b/Bloxstrap/Models/SettingTasks/EnumModPresetTask.cs @@ -0,0 +1,68 @@ +using Bloxstrap.Models.SettingTasks.Base; + +namespace Bloxstrap.Models.SettingTasks +{ + public class EnumModPresetTask : EnumBaseTask where T : struct, Enum + { + private readonly Dictionary> _fileDataMap = new(); + + private readonly Dictionary> _map; + + public EnumModPresetTask(string name, Dictionary> map) : base("ModPreset", name) + { + _map = map; + + foreach (var enumPair in _map) + { + var dataMap = new Dictionary(); + + foreach (var resourcePair in enumPair.Value) + { + var data = new ModPresetFileData(resourcePair.Key, resourcePair.Value); + + if (data.HashMatches() && OriginalState.Equals(default(T))) + OriginalState = enumPair.Key; + + dataMap[resourcePair.Key] = data; + } + + _fileDataMap[enumPair.Key] = dataMap; + } + } + + public override void Execute() + { + if (!NewState.Equals(default(T))) + { + var resourceMap = _fileDataMap[NewState]; + + foreach (var resourcePair in resourceMap) + { + var data = resourcePair.Value; + + if (!data.HashMatches()) + { + Directory.CreateDirectory(Path.GetDirectoryName(data.FullFilePath)!); + + using var resourceStream = data.ResourceStream; + using var memoryStream = new MemoryStream(); + data.ResourceStream.CopyTo(memoryStream); + + Filesystem.AssertReadOnly(data.FullFilePath); + File.WriteAllBytes(data.FullFilePath, memoryStream.ToArray()); + } + } + } + else + { + foreach (var dataPair in _fileDataMap.First().Value) + { + Filesystem.AssertReadOnly(dataPair.Value.FullFilePath); + File.Delete(dataPair.Value.FullFilePath); + } + } + + OriginalState = NewState; + } + } +} diff --git a/Bloxstrap/Models/SettingTasks/FontModPresetTask.cs b/Bloxstrap/Models/SettingTasks/FontModPresetTask.cs new file mode 100644 index 0000000..b95d462 --- /dev/null +++ b/Bloxstrap/Models/SettingTasks/FontModPresetTask.cs @@ -0,0 +1,43 @@ +using Bloxstrap.Models.SettingTasks.Base; + +namespace Bloxstrap.Models.SettingTasks +{ + public class FontModPresetTask : StringBaseTask + { + public string? GetFileHash() + { + if (!File.Exists(Paths.CustomFont)) + return null; + + using var fileStream = File.OpenRead(Paths.CustomFont); + return MD5Hash.Stringify(App.MD5Provider.ComputeHash(fileStream)); + } + + public FontModPresetTask() : base("ModPreset", "TextFont") + { + if (File.Exists(Paths.CustomFont)) + OriginalState = Paths.CustomFont; + } + + public override void Execute() + { + if (!String.IsNullOrEmpty(NewState)) + { + if (String.Compare(NewState, Paths.CustomFont, StringComparison.InvariantCultureIgnoreCase) != 0 && File.Exists(NewState)) + { + Directory.CreateDirectory(Path.GetDirectoryName(Paths.CustomFont)!); + + Filesystem.AssertReadOnly(Paths.CustomFont); + File.Copy(NewState, Paths.CustomFont, true); + } + } + else if (File.Exists(Paths.CustomFont)) + { + Filesystem.AssertReadOnly(Paths.CustomFont); + File.Delete(Paths.CustomFont); + } + + OriginalState = NewState; + } + } +} diff --git a/Bloxstrap/Models/SettingTasks/ISettingTask.cs b/Bloxstrap/Models/SettingTasks/ISettingTask.cs deleted file mode 100644 index ff441af..0000000 --- a/Bloxstrap/Models/SettingTasks/ISettingTask.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Bloxstrap.Models.SettingTasks -{ - public interface ISettingTask - { - public bool OriginalState { get; set; } - - public bool NewState { get; set; } - - public void Execute(); - } -} diff --git a/Bloxstrap/Models/SettingTasks/ModPresetTask.cs b/Bloxstrap/Models/SettingTasks/ModPresetTask.cs new file mode 100644 index 0000000..99c20ee --- /dev/null +++ b/Bloxstrap/Models/SettingTasks/ModPresetTask.cs @@ -0,0 +1,59 @@ +using Bloxstrap.Models.SettingTasks.Base; + +namespace Bloxstrap.Models.SettingTasks +{ + public class ModPresetTask : BoolBaseTask + { + private Dictionary _fileDataMap = new(); + + private Dictionary _pathMap; + + public ModPresetTask(string name, string path, string resource) : this(name, new() {{ path, resource }}) { } + + public ModPresetTask(string name, Dictionary pathMap) : base("ModPreset", name) + { + _pathMap = pathMap; + + foreach (var pair in _pathMap) + { + var data = new ModPresetFileData(pair.Key, pair.Value); + + if (data.HashMatches() && !OriginalState) + OriginalState = true; + + _fileDataMap[pair.Key] = data; + } + } + + public override void Execute() + { + if (NewState == OriginalState) + return; + + foreach (var pair in _fileDataMap) + { + var data = pair.Value; + bool hashMatches = data.HashMatches(); + + if (NewState && !hashMatches) + { + Directory.CreateDirectory(Path.GetDirectoryName(data.FullFilePath)!); + + using var resourceStream = data.ResourceStream; + using var memoryStream = new MemoryStream(); + data.ResourceStream.CopyTo(memoryStream); + + Filesystem.AssertReadOnly(data.FullFilePath); + File.WriteAllBytes(data.FullFilePath, memoryStream.ToArray()); + } + else if (!NewState && hashMatches) + { + Filesystem.AssertReadOnly(data.FullFilePath); + File.Delete(data.FullFilePath); + } + } + + OriginalState = NewState; + } + } +} diff --git a/Bloxstrap/Models/SettingTasks/ShortcutTask.cs b/Bloxstrap/Models/SettingTasks/ShortcutTask.cs index 5b30995..ca14a45 100644 --- a/Bloxstrap/Models/SettingTasks/ShortcutTask.cs +++ b/Bloxstrap/Models/SettingTasks/ShortcutTask.cs @@ -1,33 +1,27 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Bloxstrap.Models.SettingTasks.Base; namespace Bloxstrap.Models.SettingTasks { - public class ShortcutTask : BaseTask, ISettingTask + public class ShortcutTask : BoolBaseTask { - public string ExeFlags { get; set; } = ""; + private string _shortcutPath; + + private string _exeFlags; - public string ShortcutPath { get; set; } - - public ShortcutTask(string shortcutPath) + public ShortcutTask(string name, string lnkFolder, string lnkName, string exeFlags = "") : base("Shortcut", name) { - ShortcutPath = shortcutPath; + _shortcutPath = Path.Combine(lnkFolder, lnkName); + _exeFlags = exeFlags; - OriginalState = File.Exists(ShortcutPath); + OriginalState = File.Exists(_shortcutPath); } public override void Execute() { - if (NewState == OriginalState) - return; - if (NewState) - Shortcut.Create(Paths.Application, ExeFlags, ShortcutPath); - else if (File.Exists(ShortcutPath)) - File.Delete(ShortcutPath); + Shortcut.Create(Paths.Application, _exeFlags, _shortcutPath); + else if (File.Exists(_shortcutPath)) + File.Delete(_shortcutPath); OriginalState = NewState; } diff --git a/Bloxstrap/Models/Settings.cs b/Bloxstrap/Models/Settings.cs index 5e854b9..bb032dc 100644 --- a/Bloxstrap/Models/Settings.cs +++ b/Bloxstrap/Models/Settings.cs @@ -16,9 +16,6 @@ namespace Bloxstrap.Models public bool ForceRobloxLanguage { get; set; } = false; public bool UseFastFlagManager { get; set; } = true; - // channel configuration - public string Channel { get; set; } = RobloxDeployment.DefaultChannel; - // integration configuration public bool EnableActivityTracking { get; set; } = true; public bool UseDiscordRichPresence { get; set; } = true; @@ -28,12 +25,7 @@ namespace Bloxstrap.Models public ObservableCollection CustomIntegrations { get; set; } = new(); // mod preset configuration - public bool UseOldDeathSound { get; set; } = true; - public bool UseOldCharacterSounds { get; set; } = false; public bool UseDisableAppPatch { get; set; } = false; - public bool UseOldAvatarBackground { get; set; } = false; - public CursorType CursorType { get; set; } = CursorType.Default; - public EmojiType EmojiType { get; set; } = EmojiType.Default; public bool DisableFullscreenOptimizations { get; set; } = false; } } diff --git a/Bloxstrap/PackageMap.cs b/Bloxstrap/PackageMap.cs deleted file mode 100644 index 6932921..0000000 --- a/Bloxstrap/PackageMap.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Bloxstrap -{ - internal class PackageMap - { - public static IReadOnlyDictionary Player - { - get { return CombineDictionaries(_common, _playerOnly); } - } - - public static IReadOnlyDictionary Studio - { - get { return CombineDictionaries(_common, _studioOnly); } - } - - // 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? - private static IReadOnlyDictionary _common = new Dictionary() - { - { "Libraries.zip", @"" }, - { "shaders.zip", @"shaders\" }, - { "ssl.zip", @"ssl\" }, - - // the runtime installer is only extracted if it needs installing - { "WebView2.zip", @"" }, - { "WebView2RuntimeInstaller.zip", @"WebView2RuntimeInstaller\" }, - - { "content-avatar.zip", @"content\avatar\" }, - { "content-configs.zip", @"content\configs\" }, - { "content-fonts.zip", @"content\fonts\" }, - { "content-sky.zip", @"content\sky\" }, - { "content-sounds.zip", @"content\sounds\" }, - { "content-textures2.zip", @"content\textures\" }, - { "content-models.zip", @"content\models\" }, - - { "content-textures3.zip", @"PlatformContent\pc\textures\" }, - { "content-terrain.zip", @"PlatformContent\pc\terrain\" }, - { "content-platform-fonts.zip", @"PlatformContent\pc\fonts\" }, - - { "extracontent-luapackages.zip", @"ExtraContent\LuaPackages\" }, - { "extracontent-translations.zip", @"ExtraContent\translations\" }, - { "extracontent-models.zip", @"ExtraContent\models\" }, - { "extracontent-textures.zip", @"ExtraContent\textures\" }, - { "extracontent-places.zip", @"ExtraContent\places\" }, - }; - - private static IReadOnlyDictionary _playerOnly = new Dictionary() - { - { "RobloxApp.zip", @"" } - }; - - private static IReadOnlyDictionary _studioOnly = new Dictionary() - { - { "RobloxStudio.zip", @"" }, - { "ApplicationConfig.zip", @"ApplicationConfig\" }, - { "content-studio_svg_textures.zip", @"content\studio_svg_textures\"}, - { "content-qt_translations.zip", @"content\qt_translations\" }, - { "content-api-docs.zip", @"content\api_docs\" }, - { "extracontent-scripts.zip", @"ExtraContent\scripts\" }, - { "BuiltInPlugins.zip", @"BuiltInPlugins\" }, - { "BuiltInStandalonePlugins.zip", @"BuiltInStandalonePlugins\" }, - { "LibrariesQt5.zip", @"" }, - { "Plugins.zip", @"Plugins\" }, - { "Qml.zip", @"Qml\" }, - { "StudioFonts.zip", @"StudioFonts\" }, - { "redist.zip", @"" }, - }; - - private static Dictionary CombineDictionaries(IReadOnlyDictionary d1, IReadOnlyDictionary d2) - { - Dictionary newD = new Dictionary(); - - foreach (var d in d1) - newD[d.Key] = d.Value; - - foreach (var d in d2) - newD[d.Key] = d.Value; - - return newD; - } - } -} diff --git a/Bloxstrap/Properties/launchSettings.json b/Bloxstrap/Properties/launchSettings.json index 7dc753d..e3f8ff7 100644 --- a/Bloxstrap/Properties/launchSettings.json +++ b/Bloxstrap/Properties/launchSettings.json @@ -21,11 +21,11 @@ }, "Bloxstrap (Deeplink)": { "commandName": "Project", - "commandLineArgs": "roblox://experiences/start?placeId=13700835620" + "commandLineArgs": "-player \"roblox://experiences/start?placeId=13700835620\"" }, "Bloxstrap (Studio Launch)": { "commandName": "Project", - "commandLineArgs": "-ide" + "commandLineArgs": "-studio" } } } \ No newline at end of file diff --git a/Bloxstrap/ProtocolHandler.cs b/Bloxstrap/ProtocolHandler.cs index 6338c77..ff57e2c 100644 --- a/Bloxstrap/ProtocolHandler.cs +++ b/Bloxstrap/ProtocolHandler.cs @@ -9,69 +9,9 @@ namespace Bloxstrap { private const string RobloxPlaceKey = "Roblox.Place"; - public static string ParseUri(string protocol) + public static void Register(string key, string name, string handler, string handlerParam = "%1") { - var args = new Dictionary(); - bool channelArgPresent = false; - - foreach (var parameter in protocol.Split('+')) - { - if (!parameter.Contains(':')) - continue; - - var kv = parameter.Split(':'); - string key = kv[0]; - string val = kv[1]; - - // we'll set this before launching because for some reason roblox just refuses to launch if its like a few minutes old so ??? - if (key == "launchtime") - val = "LAUNCHTIMEPLACEHOLDER"; - - if (key == "channel" && !String.IsNullOrEmpty(val)) - { - channelArgPresent = true; - EnrollChannel(val); - - // we'll set the arg when launching - continue; - } - - args.Add(key, val); - } - - if (!channelArgPresent) - EnrollChannel(RobloxDeployment.DefaultChannel); - - var pairs = args.Select(x => x.Key + ":" + x.Value).ToArray(); - return String.Join("+", pairs); - } - - public static void ChangeChannel(string channel) - { - if (channel.ToLowerInvariant() == App.Settings.Prop.Channel.ToLowerInvariant()) - return; - - // don't change if roblox is already running - if (Process.GetProcessesByName("RobloxPlayerBeta").Any()) - { - App.Logger.WriteLine("ProtocolHandler::ChangeChannel", $"Ignored channel change from {App.Settings.Prop.Channel} to {channel} because Roblox is already running"); - } - else - { - App.Logger.WriteLine("ProtocolHandler::ChangeChannel", $"Changed Roblox channel from {App.Settings.Prop.Channel} to {channel}"); - App.Settings.Prop.Channel = channel; - } - } - - public static void EnrollChannel(string channel) - { - ChangeChannel(channel); - App.State.Save(); - } - - public static void Register(string key, string name, string handler) - { - string handlerArgs = $"\"{handler}\" %1"; + string handlerArgs = $"\"{handler}\" {handlerParam}"; using var uriKey = Registry.CurrentUser.CreateSubKey($@"Software\Classes\{key}"); using var uriIconKey = uriKey.CreateSubKey("DefaultIcon"); diff --git a/Bloxstrap/Resource.cs b/Bloxstrap/Resource.cs index 98df7e6..46c8e82 100644 --- a/Bloxstrap/Resource.cs +++ b/Bloxstrap/Resource.cs @@ -7,18 +7,19 @@ namespace Bloxstrap static readonly Assembly assembly = Assembly.GetExecutingAssembly(); static readonly string[] resourceNames = assembly.GetManifestResourceNames(); - public static async Task Get(string name) + public static Stream GetStream(string name) { string path = resourceNames.Single(str => str.EndsWith(name)); + return assembly.GetManifestResourceStream(path)!; + } - using (Stream stream = assembly.GetManifestResourceStream(path)!) - { - using (MemoryStream memoryStream = new()) - { - await stream.CopyToAsync(memoryStream); - return memoryStream.ToArray(); - } - } + public static async Task Get(string name) + { + using var stream = GetStream(name); + using var memoryStream = new MemoryStream(); + + await stream.CopyToAsync(memoryStream); + return memoryStream.ToArray(); } } } diff --git a/Bloxstrap/Resources/Strings.Designer.cs b/Bloxstrap/Resources/Strings.Designer.cs index 99de85f..fb7b569 100644 --- a/Bloxstrap/Resources/Strings.Designer.cs +++ b/Bloxstrap/Resources/Strings.Designer.cs @@ -729,6 +729,24 @@ namespace Bloxstrap.Resources { } } + /// + /// Looks up a localized string similar to Please wait for installation to finish.. + /// + public static string Dialog_AlreadyRunning_Installer { + get { + return ResourceManager.GetString("Dialog.AlreadyRunning.Installer", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Please wait for uninstallation to finish.. + /// + public static string Dialog_AlreadyRunning_Uninstaller { + get { + return ResourceManager.GetString("Dialog.AlreadyRunning.Uninstaller", resourceCulture); + } + } + /// /// Looks up a localized string similar to Bloxstrap was unable to create shortcuts for the Desktop and Start menu. Try creating them later through the settings.. /// @@ -2751,6 +2769,17 @@ namespace Bloxstrap.Resources { } } + /// + /// Looks up a localized string similar to The emoji mod could not be applied because of a network error during download. + /// + ///{0}. + /// + public static string Menu_Mods_Presets_EmojiType_Error { + get { + return ResourceManager.GetString("Menu.Mods.Presets.EmojiType.Error", resourceCulture); + } + } + /// /// Looks up a localized string similar to Preferred emoji type. /// diff --git a/Bloxstrap/Resources/Strings.resx b/Bloxstrap/Resources/Strings.resx index 455a2df..f5a90d4 100644 --- a/Bloxstrap/Resources/Strings.resx +++ b/Bloxstrap/Resources/Strings.resx @@ -1125,4 +1125,15 @@ If not, then please report this exception to the maintainers of this fork. Do NO Connected to reserved server + + The emoji mod could not be applied because of a network error during download. + +{0} + + + Please wait for installation to finish. + + + Please wait for uninstallation to finish. + diff --git a/Bloxstrap/RobloxDeployment.cs b/Bloxstrap/RobloxDeployment.cs index 6c97d4e..b350eea 100644 --- a/Bloxstrap/RobloxDeployment.cs +++ b/Bloxstrap/RobloxDeployment.cs @@ -1,9 +1,8 @@ namespace Bloxstrap { - // TODO: this is a mess and desperately needs refactoring public static class RobloxDeployment { - public const string DefaultChannel = "LIVE"; + public const string DefaultChannel = "production"; public static string BaseUrl { get; private set; } = null!; @@ -88,14 +87,11 @@ return null; } - public static string GetLocation(string resource, string? channel = null) + public static string GetLocation(string resource, string channel = DefaultChannel) { - if (string.IsNullOrEmpty(channel)) - channel = App.Settings.Prop.Channel; - string location = BaseUrl; - if (channel.ToLowerInvariant() != DefaultChannel.ToLowerInvariant()) + if (String.Compare(channel, DefaultChannel, StringComparison.InvariantCultureIgnoreCase) != 0) { string channelName; @@ -112,11 +108,11 @@ return location; } - public static async Task GetInfo(string channel, bool extraInformation = false, string binaryType = "WindowsPlayer") + public static async Task GetInfo(string channel, string binaryType = "WindowsPlayer") { const string LOG_IDENT = "RobloxDeployment::GetInfo"; - App.Logger.WriteLine(LOG_IDENT, $"Getting deploy info for channel {channel} (extraInformation={extraInformation})"); + App.Logger.WriteLine(LOG_IDENT, $"Getting deploy info for channel {channel}"); string cacheKey = $"{channel}-{binaryType}"; ClientVersion clientVersion; @@ -128,7 +124,11 @@ } else { - string path = $"/v2/client-version/{binaryType}/channel/{channel}"; + string path = $"/v2/client-version/{binaryType}"; + + if (String.Compare(channel, DefaultChannel, StringComparison.InvariantCultureIgnoreCase) != 0) + path = $"/v2/client-version/{binaryType}/channel/{channel}"; + HttpResponseMessage deployInfoResponse; try @@ -173,24 +173,6 @@ clientVersion.IsBehindDefaultChannel = true; } - // for preferences - if (extraInformation && clientVersion.Timestamp is null) - { - App.Logger.WriteLine(LOG_IDENT, "Getting extra information..."); - - string manifestUrl = GetLocation($"/{clientVersion.VersionGuid}-rbxPkgManifest.txt", channel); - - // get an approximate deploy time from rbxpkgmanifest's last modified date - HttpResponseMessage pkgResponse = await App.HttpClient.GetAsync(manifestUrl); - - if (pkgResponse.Content.Headers.TryGetValues("last-modified", out var values)) - { - string lastModified = values.First(); - App.Logger.WriteLine(LOG_IDENT, $"{manifestUrl} - Last-Modified: {lastModified}"); - clientVersion.Timestamp = DateTime.Parse(lastModified).ToLocalTime(); - } - } - ClientVersionCache[cacheKey] = clientVersion; return clientVersion; diff --git a/Bloxstrap/RobloxFastFlags.cs b/Bloxstrap/RobloxFastFlags.cs index 6121861..04e45da 100644 --- a/Bloxstrap/RobloxFastFlags.cs +++ b/Bloxstrap/RobloxFastFlags.cs @@ -114,27 +114,23 @@ namespace Bloxstrap public static RobloxFastFlags PCDesktopClient { get; } = GetSettings("PCDesktopClient"); public static RobloxFastFlags PCClientBootstrapper { get; } = GetSettings("PCClientBootstrapper"); - public static RobloxFastFlags GetSettings(string applicationName, string? channelName = null, bool shouldCache = true) + public static RobloxFastFlags GetSettings(string applicationName, string channelName = RobloxDeployment.DefaultChannel, bool shouldCache = true) { - string channelNameLower; - if (!string.IsNullOrEmpty(channelName)) - channelNameLower = channelName.ToLowerInvariant(); - else - channelNameLower = App.Settings.Prop.Channel.ToLowerInvariant(); + channelName = channelName.ToLowerInvariant(); lock (_cache) { - if (_cache.ContainsKey(applicationName) && _cache[applicationName].ContainsKey(channelNameLower)) - return _cache[applicationName][channelNameLower]; + if (_cache.ContainsKey(applicationName) && _cache[applicationName].ContainsKey(channelName)) + return _cache[applicationName][channelName]; - var flags = new RobloxFastFlags(applicationName, channelNameLower); + var flags = new RobloxFastFlags(applicationName, channelName); if (shouldCache) { if (!_cache.ContainsKey(applicationName)) _cache[applicationName] = new(); - _cache[applicationName][channelNameLower] = flags; + _cache[applicationName][channelName] = flags; } return flags; diff --git a/Bloxstrap/UI/Elements/Base/WpfUiWindow.cs b/Bloxstrap/UI/Elements/Base/WpfUiWindow.cs index d941ea8..6291d03 100644 --- a/Bloxstrap/UI/Elements/Base/WpfUiWindow.cs +++ b/Bloxstrap/UI/Elements/Base/WpfUiWindow.cs @@ -11,10 +11,15 @@ using Wpf.Ui.Mvvm.Services; namespace Bloxstrap.UI.Elements.Base { - public class WpfUiWindow : UiWindow + public abstract class WpfUiWindow : UiWindow { private readonly IThemeService _themeService = new ThemeService(); + public WpfUiWindow() + { + ApplyTheme(); + } + public void ApplyTheme() { _themeService.SetTheme(App.Settings.Prop.Theme.GetFinal() == Enums.Theme.Dark ? ThemeType.Dark : ThemeType.Light); diff --git a/Bloxstrap/UI/Elements/Bootstrapper/ClassicFluentDialog.xaml.cs b/Bloxstrap/UI/Elements/Bootstrapper/ClassicFluentDialog.xaml.cs index c32d510..5139a3c 100644 --- a/Bloxstrap/UI/Elements/Bootstrapper/ClassicFluentDialog.xaml.cs +++ b/Bloxstrap/UI/Elements/Bootstrapper/ClassicFluentDialog.xaml.cs @@ -78,7 +78,6 @@ namespace Bloxstrap.UI.Elements.Bootstrapper public ClassicFluentDialog() { InitializeComponent(); - ApplyTheme(); _viewModel = new ClassicFluentDialogViewModel(this); DataContext = _viewModel; diff --git a/Bloxstrap/UI/Elements/Bootstrapper/FluentDialog.xaml.cs b/Bloxstrap/UI/Elements/Bootstrapper/FluentDialog.xaml.cs index 0759139..9048f02 100644 --- a/Bloxstrap/UI/Elements/Bootstrapper/FluentDialog.xaml.cs +++ b/Bloxstrap/UI/Elements/Bootstrapper/FluentDialog.xaml.cs @@ -88,7 +88,6 @@ namespace Bloxstrap.UI.Elements.Bootstrapper public FluentDialog(bool aero) { InitializeComponent(); - ApplyTheme(); _viewModel = new FluentDialogViewModel(this, aero); DataContext = _viewModel; diff --git a/Bloxstrap/UI/Elements/ContextMenu/MenuContainer.xaml.cs b/Bloxstrap/UI/Elements/ContextMenu/MenuContainer.xaml.cs index aec124a..9e34a29 100644 --- a/Bloxstrap/UI/Elements/ContextMenu/MenuContainer.xaml.cs +++ b/Bloxstrap/UI/Elements/ContextMenu/MenuContainer.xaml.cs @@ -31,7 +31,6 @@ namespace Bloxstrap.UI.Elements.ContextMenu public MenuContainer(ActivityWatcher? activityWatcher, DiscordRichPresence? richPresenceHandler, int? processId) { InitializeComponent(); - ApplyTheme(); _activityWatcher = activityWatcher; _richPresenceHandler = richPresenceHandler; diff --git a/Bloxstrap/UI/Elements/ContextMenu/ServerInformation.xaml b/Bloxstrap/UI/Elements/ContextMenu/ServerInformation.xaml index 23c2786..381215c 100644 --- a/Bloxstrap/UI/Elements/ContextMenu/ServerInformation.xaml +++ b/Bloxstrap/UI/Elements/ContextMenu/ServerInformation.xaml @@ -1,9 +1,10 @@ - - + diff --git a/Bloxstrap/UI/Elements/Dialogs/AddFastFlagDialog.xaml b/Bloxstrap/UI/Elements/Dialogs/AddFastFlagDialog.xaml index 669dc7c..f4ea9b6 100644 --- a/Bloxstrap/UI/Elements/Dialogs/AddFastFlagDialog.xaml +++ b/Bloxstrap/UI/Elements/Dialogs/AddFastFlagDialog.xaml @@ -1,10 +1,11 @@ - - + diff --git a/Bloxstrap/UI/Elements/Dialogs/FluentMessageBox.xaml b/Bloxstrap/UI/Elements/Dialogs/FluentMessageBox.xaml index 0681ea1..cf6ec08 100644 --- a/Bloxstrap/UI/Elements/Dialogs/FluentMessageBox.xaml +++ b/Bloxstrap/UI/Elements/Dialogs/FluentMessageBox.xaml @@ -1,10 +1,11 @@ - - + diff --git a/Bloxstrap/UI/Elements/Dialogs/LanguageSelectorDialog.xaml.cs b/Bloxstrap/UI/Elements/Dialogs/LanguageSelectorDialog.xaml.cs index 6faeeb9..1441b9e 100644 --- a/Bloxstrap/UI/Elements/Dialogs/LanguageSelectorDialog.xaml.cs +++ b/Bloxstrap/UI/Elements/Dialogs/LanguageSelectorDialog.xaml.cs @@ -26,7 +26,6 @@ namespace Bloxstrap.UI.Elements.Dialogs DataContext = viewModel; InitializeComponent(); - ApplyTheme(); viewModel.CloseRequestEvent += (_, _) => Close(); } diff --git a/Bloxstrap/UI/Elements/Dialogs/LaunchMenuDialog.xaml.cs b/Bloxstrap/UI/Elements/Dialogs/LaunchMenuDialog.xaml.cs index f51898f..bb66b22 100644 --- a/Bloxstrap/UI/Elements/Dialogs/LaunchMenuDialog.xaml.cs +++ b/Bloxstrap/UI/Elements/Dialogs/LaunchMenuDialog.xaml.cs @@ -36,7 +36,6 @@ namespace Bloxstrap.UI.Elements.Dialogs DataContext = viewModel; InitializeComponent(); - ApplyTheme(); } } } diff --git a/Bloxstrap/UI/Elements/Dialogs/UninstallerDialog.xaml.cs b/Bloxstrap/UI/Elements/Dialogs/UninstallerDialog.xaml.cs index 993f124..3302f95 100644 --- a/Bloxstrap/UI/Elements/Dialogs/UninstallerDialog.xaml.cs +++ b/Bloxstrap/UI/Elements/Dialogs/UninstallerDialog.xaml.cs @@ -39,7 +39,6 @@ namespace Bloxstrap.UI.Elements.Dialogs DataContext = viewModel; InitializeComponent(); - ApplyTheme(); } } } diff --git a/Bloxstrap/UI/Elements/Installer/MainWindow.xaml.cs b/Bloxstrap/UI/Elements/Installer/MainWindow.xaml.cs index 7d3eeb8..90108be 100644 --- a/Bloxstrap/UI/Elements/Installer/MainWindow.xaml.cs +++ b/Bloxstrap/UI/Elements/Installer/MainWindow.xaml.cs @@ -62,7 +62,6 @@ namespace Bloxstrap.UI.Elements.Installer DataContext = _viewModel; InitializeComponent(); - ApplyTheme(); App.Logger.WriteLine("MainWindow::MainWindow", "Initializing installer"); diff --git a/Bloxstrap/UI/Elements/Settings/MainWindow.xaml b/Bloxstrap/UI/Elements/Settings/MainWindow.xaml index b1cb96f..49f508a 100644 --- a/Bloxstrap/UI/Elements/Settings/MainWindow.xaml +++ b/Bloxstrap/UI/Elements/Settings/MainWindow.xaml @@ -1,5 +1,4 @@ - + diff --git a/Bloxstrap/UI/Elements/Settings/MainWindow.xaml.cs b/Bloxstrap/UI/Elements/Settings/MainWindow.xaml.cs index c4a4225..c877bae 100644 --- a/Bloxstrap/UI/Elements/Settings/MainWindow.xaml.cs +++ b/Bloxstrap/UI/Elements/Settings/MainWindow.xaml.cs @@ -18,7 +18,6 @@ namespace Bloxstrap.UI.Elements.Settings DataContext = viewModel; InitializeComponent(); - ApplyTheme(); App.Logger.WriteLine("MainWindow::MainWindow", "Initializing menu"); diff --git a/Bloxstrap/UI/Elements/Settings/Pages/AboutPage.xaml b/Bloxstrap/UI/Elements/Settings/Pages/AboutPage.xaml index d3f2c11..830e14b 100644 --- a/Bloxstrap/UI/Elements/Settings/Pages/AboutPage.xaml +++ b/Bloxstrap/UI/Elements/Settings/Pages/AboutPage.xaml @@ -599,7 +599,7 @@ - + diff --git a/Bloxstrap/UI/Elements/Settings/Pages/ModsPage.xaml b/Bloxstrap/UI/Elements/Settings/Pages/ModsPage.xaml index 68c6e3c..bb0e06f 100644 --- a/Bloxstrap/UI/Elements/Settings/Pages/ModsPage.xaml +++ b/Bloxstrap/UI/Elements/Settings/Pages/ModsPage.xaml @@ -7,7 +7,9 @@ xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" xmlns:resources="clr-namespace:Bloxstrap.Resources" xmlns:controls="clr-namespace:Bloxstrap.UI.Elements.Controls" + xmlns:dmodels="clr-namespace:Bloxstrap.UI.ViewModels.Settings" mc:Ignorable="d" + d:DataContext="{d:DesignInstance dmodels:ModsViewModel, IsDesignTimeCreatable=True}" d:DesignHeight="800" d:DesignWidth="800" Title="ModsPage" Scrollable="True"> @@ -42,13 +44,13 @@ - + - + @@ -60,19 +62,19 @@ - + - + - + diff --git a/Bloxstrap/UI/Elements/Settings/Pages/ShortcutsPage.xaml b/Bloxstrap/UI/Elements/Settings/Pages/ShortcutsPage.xaml index 4939ce4..6d10250 100644 --- a/Bloxstrap/UI/Elements/Settings/Pages/ShortcutsPage.xaml +++ b/Bloxstrap/UI/Elements/Settings/Pages/ShortcutsPage.xaml @@ -6,9 +6,10 @@ xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Settings.Pages" xmlns:controls="clr-namespace:Bloxstrap.UI.Elements.Controls" - xmlns:models="clr-namespace:Bloxstrap.UI.ViewModels.Settings" + xmlns:dmodels="clr-namespace:Bloxstrap.UI.ViewModels.Settings" xmlns:resources="clr-namespace:Bloxstrap.Resources" mc:Ignorable="d" + d:DataContext="{d:DesignInstance dmodels:ShortcutsViewModel, IsDesignTimeCreatable=True}" d:DesignHeight="600" d:DesignWidth="800" Title="ShortcutsPage" Scrollable="True"> @@ -26,11 +27,11 @@ - + - + @@ -44,11 +45,11 @@ - + - + diff --git a/Bloxstrap/UI/Frontend.cs b/Bloxstrap/UI/Frontend.cs index 509a323..af21245 100644 --- a/Bloxstrap/UI/Frontend.cs +++ b/Bloxstrap/UI/Frontend.cs @@ -14,10 +14,10 @@ namespace Bloxstrap.UI { App.Logger.WriteLine("Frontend::ShowMessageBox", message); - if (App.LaunchSettings.IsQuiet) + if (App.LaunchSettings.QuietFlag.Active) return defaultResult; - if (!App.LaunchSettings.IsRobloxLaunch) + if (App.LaunchSettings.RobloxLaunchMode != LaunchMode.None) return ShowFluentMessageBox(message, icon, buttons); switch (App.Settings.Prop.BootstrapperStyle) diff --git a/Bloxstrap/UI/NotifyIconWrapper.cs b/Bloxstrap/UI/NotifyIconWrapper.cs index e05f573..6b32da3 100644 --- a/Bloxstrap/UI/NotifyIconWrapper.cs +++ b/Bloxstrap/UI/NotifyIconWrapper.cs @@ -89,14 +89,15 @@ namespace Bloxstrap.UI string serverLocation = await _activityWatcher!.GetServerLocation(); string title = _activityWatcher.ActivityServerType switch { - ServerType.Public => Resources.Strings.ContextMenu_ServerInformation_Notification_Title_Public, - ServerType.Private => Resources.Strings.ContextMenu_ServerInformation_Notification_Title_Private, - ServerType.Reserved => Resources.Strings.ContextMenu_ServerInformation_Notification_Title_Reserved + ServerType.Public => Strings.ContextMenu_ServerInformation_Notification_Title_Public, + ServerType.Private => Strings.ContextMenu_ServerInformation_Notification_Title_Private, + ServerType.Reserved => Strings.ContextMenu_ServerInformation_Notification_Title_Reserved, + _ => "" }; ShowAlert( title, - String.Format(Resources.Strings.ContextMenu_ServerInformation_Notification_Text, serverLocation), + String.Format(Strings.ContextMenu_ServerInformation_Notification_Text, serverLocation), 10, (_, _) => _menuContainer?.ShowServerInformationWindow() ); diff --git a/Bloxstrap/UI/ViewModels/Settings/MainWindowViewModel.cs b/Bloxstrap/UI/ViewModels/Settings/MainWindowViewModel.cs index edf606b..df279e5 100644 --- a/Bloxstrap/UI/ViewModels/Settings/MainWindowViewModel.cs +++ b/Bloxstrap/UI/ViewModels/Settings/MainWindowViewModel.cs @@ -12,12 +12,22 @@ namespace Bloxstrap.UI.ViewModels.Settings private void SaveSettings() { + const string LOG_IDENT = "MainWindowViewModel::SaveSettings"; + App.Settings.Save(); App.State.Save(); App.FastFlags.Save(); - foreach (var task in App.PendingSettingTasks) - task.Value.Execute(); + foreach (var pair in App.PendingSettingTasks) + { + var task = pair.Value; + + if (task.Changed) + { + App.Logger.WriteLine(LOG_IDENT, $"Executing pending task '{task}'"); + task.Execute(); + } + } App.PendingSettingTasks.Clear(); diff --git a/Bloxstrap/UI/ViewModels/Settings/ModsViewModel.cs b/Bloxstrap/UI/ViewModels/Settings/ModsViewModel.cs index e078253..f36b897 100644 --- a/Bloxstrap/UI/ViewModels/Settings/ModsViewModel.cs +++ b/Bloxstrap/UI/ViewModels/Settings/ModsViewModel.cs @@ -5,14 +5,14 @@ using Microsoft.Win32; using CommunityToolkit.Mvvm.Input; +using Bloxstrap.Models.SettingTasks; + namespace Bloxstrap.UI.ViewModels.Settings { public class ModsViewModel : NotifyPropertyChangedViewModel { private void OpenModsFolder() => Process.Start("explorer.exe", Paths.Modifications); - private bool _usingCustomFont => File.Exists(Paths.CustomFont); - private readonly Dictionary FontHeaders = new() { { "ttf", new byte[4] { 0x00, 0x01, 0x00, 0x00 } }, @@ -22,16 +22,15 @@ namespace Bloxstrap.UI.ViewModels.Settings private void ManageCustomFont() { - if (_usingCustomFont) + if (!String.IsNullOrEmpty(TextFontTask.NewState)) { - Filesystem.AssertReadOnly(Paths.CustomFont); - File.Delete(Paths.CustomFont); + TextFontTask.NewState = ""; } else { var dialog = new OpenFileDialog { - Filter = $"{Resources.Strings.Menu_FontFiles}|*.ttf;*.otf;*.ttc" + Filter = $"{Strings.Menu_FontFiles}|*.ttf;*.otf;*.ttc" }; if (dialog.ShowDialog() != true) @@ -41,13 +40,11 @@ namespace Bloxstrap.UI.ViewModels.Settings if (!FontHeaders.ContainsKey(type) || !File.ReadAllBytes(dialog.FileName).Take(4).SequenceEqual(FontHeaders[type])) { - Frontend.ShowMessageBox(Resources.Strings.Menu_Mods_Misc_CustomFont_Invalid, MessageBoxImage.Error); + Frontend.ShowMessageBox(Strings.Menu_Mods_Misc_CustomFont_Invalid, MessageBoxImage.Error); return; } - - Directory.CreateDirectory(Path.GetDirectoryName(Paths.CustomFont)!); - File.Copy(dialog.FileName, Paths.CustomFont); - Filesystem.AssertReadOnly(Paths.CustomFont); + + TextFontTask.NewState = dialog.FileName; } OnPropertyChanged(nameof(ChooseCustomFontVisibility)); @@ -56,45 +53,49 @@ namespace Bloxstrap.UI.ViewModels.Settings public ICommand OpenModsFolderCommand => new RelayCommand(OpenModsFolder); - public bool OldDeathSoundEnabled - { - get => App.Settings.Prop.UseOldDeathSound; - set => App.Settings.Prop.UseOldDeathSound = value; - } + public Visibility ChooseCustomFontVisibility => !String.IsNullOrEmpty(TextFontTask.NewState) ? Visibility.Collapsed : Visibility.Visible; - public bool OldCharacterSoundsEnabled - { - get => App.Settings.Prop.UseOldCharacterSounds; - set => App.Settings.Prop.UseOldCharacterSounds = value; - } - - public IReadOnlyCollection CursorTypes => CursorTypeEx.Selections; - - public Enums.CursorType SelectedCursorType - { - get => App.Settings.Prop.CursorType; - set => App.Settings.Prop.CursorType = value; - } - - public bool OldAvatarBackground - { - get => App.Settings.Prop.UseOldAvatarBackground; - set => App.Settings.Prop.UseOldAvatarBackground = value; - } - - public IReadOnlyCollection EmojiTypes => EmojiTypeEx.Selections; - - public EmojiType SelectedEmojiType - { - get => App.Settings.Prop.EmojiType; - set => App.Settings.Prop.EmojiType = value; - } - - public Visibility ChooseCustomFontVisibility => _usingCustomFont ? Visibility.Collapsed : Visibility.Visible; - public Visibility DeleteCustomFontVisibility => _usingCustomFont ? Visibility.Visible : Visibility.Collapsed; + public Visibility DeleteCustomFontVisibility => !String.IsNullOrEmpty(TextFontTask.NewState) ? Visibility.Visible : Visibility.Collapsed; public ICommand ManageCustomFontCommand => new RelayCommand(ManageCustomFont); + public ModPresetTask OldDeathSoundTask { get; } = new("OldDeathSound", @"content\sounds\ouch.ogg", "Sounds.OldDeath.ogg"); + + public ModPresetTask OldAvatarBackgroundTask { get; } = new("OldAvatarBackground", @"ExtraContent\places\Mobile.rbxl", "OldAvatarBackground.rbxl"); + + public ModPresetTask OldCharacterSoundsTask { get; } = new("OldCharacterSounds", new() + { + { @"content\sounds\action_footsteps_plastic.mp3", "Sounds.OldWalk.mp3" }, + { @"content\sounds\action_jump.mp3", "Sounds.OldJump.mp3" }, + { @"content\sounds\action_get_up.mp3", "Sounds.OldGetUp.mp3" }, + { @"content\sounds\action_falling.mp3", "Sounds.Empty.mp3" }, + { @"content\sounds\action_jump_land.mp3", "Sounds.Empty.mp3" }, + { @"content\sounds\action_swim.mp3", "Sounds.Empty.mp3" }, + { @"content\sounds\impact_water.mp3", "Sounds.Empty.mp3" } + }); + + public EmojiModPresetTask EmojiFontTask { get; } = new(); + + public EnumModPresetTask CursorTypeTask { get; } = new("CursorType", new() + { + { + Enums.CursorType.From2006, new() + { + { @"content\textures\Cursors\KeyboardMouse\ArrowCursor.png", "Cursor.From2006.ArrowCursor.png" }, + { @"content\textures\Cursors\KeyboardMouse\ArrowFarCursor.png", "Cursor.From2006.ArrowFarCursor.png" } + } + }, + { + Enums.CursorType.From2013, new() + { + { @"content\textures\Cursors\KeyboardMouse\ArrowCursor.png", "Cursor.From2013.ArrowCursor.png" }, + { @"content\textures\Cursors\KeyboardMouse\ArrowFarCursor.png", "Cursor.From2013.ArrowFarCursor.png" } + } + } + }); + + public FontModPresetTask TextFontTask { get; } = new(); + public bool DisableFullscreenOptimizations { get => App.Settings.Prop.DisableFullscreenOptimizations; diff --git a/Bloxstrap/UI/ViewModels/Settings/ShortcutsViewModel.cs b/Bloxstrap/UI/ViewModels/Settings/ShortcutsViewModel.cs index 6384144..b79d8c8 100644 --- a/Bloxstrap/UI/ViewModels/Settings/ShortcutsViewModel.cs +++ b/Bloxstrap/UI/ViewModels/Settings/ShortcutsViewModel.cs @@ -5,50 +5,12 @@ namespace Bloxstrap.UI.ViewModels.Settings { public class ShortcutsViewModel : NotifyPropertyChangedViewModel { - private ShortcutTask _desktopIconTask = new(Path.Combine(Paths.Desktop, "Bloxstrap.lnk")) - { - Name = "DesktopIcon" - }; + public ShortcutTask DesktopIconTask { get; } = new("Desktop", Paths.Desktop, "Bloxstrap.lnk"); - private ShortcutTask _startMenuIconTask = new(Path.Combine(Paths.WindowsStartMenu, "Bloxstrap.lnk")) - { - Name = "StartMenuIcon" - }; + public ShortcutTask StartMenuIconTask { get; } = new("StartMenu", Paths.WindowsStartMenu, "Bloxstrap.lnk"); - private ShortcutTask _playerIconTask = new(Path.Combine(Paths.Desktop, $"{Strings.LaunchMenu_LaunchRoblox}.lnk")) - { - Name = "RobloxPlayerIcon", - ExeFlags = "-player" - }; + public ShortcutTask PlayerIconTask { get; } = new("RobloxPlayer", Paths.Desktop, $"{Strings.LaunchMenu_LaunchRoblox}.lnk", "-player"); - private ShortcutTask _settingsIconTask = new(Path.Combine(Paths.Desktop, $"{Strings.Menu_Title}.lnk")) - { - Name = "SettingsIcon", - ExeFlags = "-settings" - }; - - public bool DesktopIcon - { - get => _desktopIconTask.NewState; - set => _desktopIconTask.NewState = value; - } - - public bool StartMenuIcon - { - get => _startMenuIconTask.NewState; - set => _startMenuIconTask.NewState = value; - } - - public bool PlayerIcon - { - get => _playerIconTask.NewState; - set => _playerIconTask.NewState = value; - } - - public bool SettingsIcon - { - get => _settingsIconTask.NewState; - set => _settingsIconTask.NewState = value; - } + public ShortcutTask SettingsIconTask { get; } = new("Settings", Paths.Desktop, $"{Strings.Menu_Title}.lnk", "-settings"); } } diff --git a/Bloxstrap/Utility/InterProcessLock.cs b/Bloxstrap/Utility/InterProcessLock.cs index 2a080e8..eee06a2 100644 --- a/Bloxstrap/Utility/InterProcessLock.cs +++ b/Bloxstrap/Utility/InterProcessLock.cs @@ -12,6 +12,8 @@ namespace Bloxstrap.Utility public bool IsAcquired { get; private set; } + public InterProcessLock(string name) : this(name, TimeSpan.Zero) { } + public InterProcessLock(string name, TimeSpan timeout) { Mutex = new Mutex(false, "Bloxstrap-" + name); @@ -25,6 +27,8 @@ namespace Bloxstrap.Utility Mutex.ReleaseMutex(); IsAcquired = false; } + + GC.SuppressFinalize(this); } } } diff --git a/Bloxstrap/Utility/MD5Hash.cs b/Bloxstrap/Utility/MD5Hash.cs index cec547d..c282c0a 100644 --- a/Bloxstrap/Utility/MD5Hash.cs +++ b/Bloxstrap/Utility/MD5Hash.cs @@ -25,7 +25,7 @@ namespace Bloxstrap.Utility return FromStream(stream); } - private static string Stringify(byte[] hash) + public static string Stringify(byte[] hash) { return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); }