diff --git a/Bloxstrap/App.xaml.cs b/Bloxstrap/App.xaml.cs index 1423e82..a1d3d83 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(); diff --git a/Bloxstrap/Bootstrapper.cs b/Bloxstrap/Bootstrapper.cs index 906f3c9..86bbfd7 100644 --- a/Bloxstrap/Bootstrapper.cs +++ b/Bloxstrap/Bootstrapper.cs @@ -864,72 +864,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 @@ -1043,54 +977,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}"; 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/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..fa0c8a2 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" }, @@ -34,7 +25,7 @@ if (emojiType == EmojiType.Default) return ""; - return $"https://github.com/bloxstraplabs/rbxcustom-fontemojis/releases/download/my-phone-is-78-percent/{Filenames[emojiType]}"; + return $"https://github.com/bloxstraplabs/rbxcustom-fontemoji/releases/download/my-phone-is-78-percent/{Filenames[emojiType]}"; } } } 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/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/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/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 2b7ed2e..584f88f 100644 --- a/Bloxstrap/Models/Settings.cs +++ b/Bloxstrap/Models/Settings.cs @@ -27,12 +27,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/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 1313ad5..1292ce4 100644 --- a/Bloxstrap/Resources/Strings.Designer.cs +++ b/Bloxstrap/Resources/Strings.Designer.cs @@ -2733,6 +2733,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 bd11ab2..a200efc 100644 --- a/Bloxstrap/Resources/Strings.resx +++ b/Bloxstrap/Resources/Strings.resx @@ -1119,4 +1119,9 @@ 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} + \ No newline at end of file diff --git a/Bloxstrap/UI/Elements/Settings/MainWindow.xaml b/Bloxstrap/UI/Elements/Settings/MainWindow.xaml index b1cb96f..57b5ecb 100644 --- a/Bloxstrap/UI/Elements/Settings/MainWindow.xaml +++ b/Bloxstrap/UI/Elements/Settings/MainWindow.xaml @@ -91,7 +91,7 @@ - + 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/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/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(); }