mirror of
https://github.com/bloxstraplabs/bloxstrap.git
synced 2025-04-21 10:01:27 -07:00
Merge branch 'main' into user-pfp-discord-rpc
This commit is contained in:
commit
11b11e01bf
@ -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<string, ISettingTask> PendingSettingTasks = new();
|
||||
public static readonly Dictionary<string, BaseTask> PendingSettingTasks = new();
|
||||
|
||||
public static readonly JsonManager<Settings> 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();
|
||||
|
63
Bloxstrap/AppData/CommonAppData.cs
Normal file
63
Bloxstrap/AppData/CommonAppData.cs
Normal file
@ -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<string, string> _commonMap { get; } = new Dictionary<string, string>()
|
||||
{
|
||||
{ "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<string, string> PackageDirectoryMap { get; set; }
|
||||
|
||||
public CommonAppData()
|
||||
{
|
||||
if (PackageDirectoryMap is null)
|
||||
{
|
||||
PackageDirectoryMap = _commonMap;
|
||||
return;
|
||||
}
|
||||
|
||||
var merged = new Dictionary<string, string>();
|
||||
|
||||
foreach (var entry in _commonMap)
|
||||
merged[entry.Key] = entry.Value;
|
||||
|
||||
foreach (var entry in PackageDirectoryMap)
|
||||
merged[entry.Key] = entry.Value;
|
||||
|
||||
PackageDirectoryMap = merged;
|
||||
}
|
||||
}
|
||||
}
|
23
Bloxstrap/AppData/IAppData.cs
Normal file
23
Bloxstrap/AppData/IAppData.cs
Normal file
@ -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<string, string> PackageDirectoryMap { get; set; }
|
||||
}
|
||||
}
|
26
Bloxstrap/AppData/RobloxPlayerData.cs
Normal file
26
Bloxstrap/AppData/RobloxPlayerData.cs
Normal file
@ -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<string, string> PackageDirectoryMap { get; set; } = new Dictionary<string, string>()
|
||||
{
|
||||
{ "RobloxApp.zip", @"" }
|
||||
};
|
||||
}
|
||||
}
|
42
Bloxstrap/AppData/RobloxStudioData.cs
Normal file
42
Bloxstrap/AppData/RobloxStudioData.cs
Normal file
@ -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<string, string> PackageDirectoryMap { get; set; } = new Dictionary<string, string>()
|
||||
{
|
||||
{ "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\" }
|
||||
};
|
||||
}
|
||||
}
|
@ -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<string, string> _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)
|
||||
{
|
||||
_launchCommandLine = _launchCommandLine.Replace("LAUNCHTIMEPLACEHOLDER", DateTimeOffset.Now.ToUnixTimeMilliseconds().ToString());
|
||||
|
||||
|
||||
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_]+)");
|
||||
var match = Regex.Match(_launchCommandLine, "gameLocale:([a-z_]+)", RegexOptions.CultureInvariant);
|
||||
|
||||
if (match.Groups.Count == 2)
|
||||
_launchCommandLine = _launchCommandLine.Replace("robloxLocale:en_us", $"robloxLocale:{match.Groups[1].Value}");
|
||||
}
|
||||
_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<string, string>
|
||||
{
|
||||
{ @"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<string, string>
|
||||
{
|
||||
{ @"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<string, string>
|
||||
{
|
||||
{ @"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<byte>() : 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<string, string> 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
|
||||
|
@ -2,9 +2,14 @@
|
||||
{
|
||||
public enum CursorType
|
||||
{
|
||||
[EnumSort(Order = 1)]
|
||||
[EnumName(FromTranslation = "Common.Default")]
|
||||
Default,
|
||||
|
||||
[EnumSort(Order = 3)]
|
||||
From2006,
|
||||
|
||||
[EnumSort(Order = 2)]
|
||||
From2013
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
{
|
||||
public enum LaunchMode
|
||||
{
|
||||
None,
|
||||
Player,
|
||||
Studio,
|
||||
StudioAuth
|
||||
|
@ -1,12 +0,0 @@
|
||||
namespace Bloxstrap.Extensions
|
||||
{
|
||||
static class CursorTypeEx
|
||||
{
|
||||
public static IReadOnlyCollection<CursorType> Selections => new CursorType[]
|
||||
{
|
||||
CursorType.Default,
|
||||
CursorType.From2013,
|
||||
CursorType.From2006
|
||||
};
|
||||
}
|
||||
}
|
@ -2,15 +2,6 @@
|
||||
{
|
||||
static class EmojiTypeEx
|
||||
{
|
||||
public static IReadOnlyCollection<EmojiType> Selections => new EmojiType[]
|
||||
{
|
||||
EmojiType.Default,
|
||||
EmojiType.Catmoji,
|
||||
EmojiType.Windows11,
|
||||
EmojiType.Windows10,
|
||||
EmojiType.Windows8
|
||||
};
|
||||
|
||||
public static IReadOnlyDictionary<EmojiType, string> Filenames => new Dictionary<EmojiType, string>
|
||||
{
|
||||
{ EmojiType.Catmoji, "Catmoji.ttf" },
|
||||
|
@ -1,8 +1,4 @@
|
||||
using Bloxstrap.Enums.FlagPresets;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Graphics.Gdi;
|
||||
|
||||
namespace Bloxstrap
|
||||
{
|
||||
|
@ -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;
|
@ -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(
|
||||
var result = Frontend.ShowMessageBox(
|
||||
Strings.Bootstrapper_Uninstall_RobloxRunning,
|
||||
MessageBoxImage.Information,
|
||||
MessageBoxButton.OKCancel
|
||||
MessageBoxButton.OKCancel,
|
||||
MessageBoxResult.OK
|
||||
);
|
||||
|
||||
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),
|
||||
|
@ -2,9 +2,10 @@
|
||||
|
||||
namespace Bloxstrap
|
||||
{
|
||||
public class JsonManager<T> where T : new()
|
||||
public class JsonManager<T> 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}>";
|
||||
|
@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 LaunchMode RobloxLaunchMode { get; private set; } = LaunchMode.Player;
|
||||
public LaunchFlag PlayerFlag { get; } = new("player");
|
||||
|
||||
public string RobloxLaunchArgs { get; private set; } = "--app";
|
||||
public LaunchFlag StudioFlag { get; } = new("studio");
|
||||
|
||||
public LaunchMode RobloxLaunchMode { get; private set; } = LaunchMode.None;
|
||||
|
||||
public string RobloxLaunchArgs { get; private set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Original launch arguments
|
||||
/// </summary>
|
||||
public string[] Args { get; private set; }
|
||||
|
||||
private Dictionary<string, PropertyInfo>? _flagMap;
|
||||
|
||||
private string? _robloxArg;
|
||||
|
||||
// pizzaboxer wanted this
|
||||
private void ParseLaunchFlagProps()
|
||||
{
|
||||
_flagMap = new Dictionary<string, PropertyInfo>();
|
||||
|
||||
foreach (var prop in typeof(LaunchSettings).GetProperties())
|
||||
{
|
||||
var attr = prop.GetCustomAttribute<LaunchFlagAttribute>();
|
||||
|
||||
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<string, LaunchFlag> _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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
13
Bloxstrap/Models/Attributes/EnumSortAttribute.cs
Normal file
13
Bloxstrap/Models/Attributes/EnumSortAttribute.cs
Normal file
@ -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; }
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
22
Bloxstrap/Models/LaunchFlag.cs
Normal file
22
Bloxstrap/Models/LaunchFlag.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
40
Bloxstrap/Models/ModPresetFileData.cs
Normal file
40
Bloxstrap/Models/ModPresetFileData.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
21
Bloxstrap/Models/SettingTasks/Base/BaseTask.cs
Normal file
21
Bloxstrap/Models/SettingTasks/Base/BaseTask.cs
Normal file
@ -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();
|
||||
}
|
||||
}
|
@ -4,22 +4,17 @@ 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
|
||||
{
|
||||
@ -28,12 +23,9 @@ namespace Bloxstrap.Models.SettingTasks
|
||||
}
|
||||
}
|
||||
|
||||
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) { }
|
||||
}
|
||||
}
|
49
Bloxstrap/Models/SettingTasks/Base/EnumBaseTask.cs
Normal file
49
Bloxstrap/Models/SettingTasks/Base/EnumBaseTask.cs
Normal file
@ -0,0 +1,49 @@
|
||||
namespace Bloxstrap.Models.SettingTasks.Base
|
||||
{
|
||||
public abstract class EnumBaseTask<T> : 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<T> Selections { get; private set; }
|
||||
= Enum.GetValues(typeof(T)).Cast<T>().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) { }
|
||||
}
|
||||
}
|
41
Bloxstrap/Models/SettingTasks/Base/StringBaseTask.cs
Normal file
41
Bloxstrap/Models/SettingTasks/Base/StringBaseTask.cs
Normal file
@ -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) { }
|
||||
}
|
||||
}
|
69
Bloxstrap/Models/SettingTasks/EmojiModPresetTask.cs
Normal file
69
Bloxstrap/Models/SettingTasks/EmojiModPresetTask.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using System.Windows;
|
||||
|
||||
using Bloxstrap.Models.SettingTasks.Base;
|
||||
|
||||
namespace Bloxstrap.Models.SettingTasks
|
||||
{
|
||||
public class EmojiModPresetTask : EnumBaseTask<EmojiType>
|
||||
{
|
||||
private string _filePath => Path.Combine(Paths.Modifications, @"content\fonts\TwemojiMozilla.ttf");
|
||||
|
||||
private IEnumerable<KeyValuePair<EmojiType, string>>? 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
68
Bloxstrap/Models/SettingTasks/EnumModPresetTask.cs
Normal file
68
Bloxstrap/Models/SettingTasks/EnumModPresetTask.cs
Normal file
@ -0,0 +1,68 @@
|
||||
using Bloxstrap.Models.SettingTasks.Base;
|
||||
|
||||
namespace Bloxstrap.Models.SettingTasks
|
||||
{
|
||||
public class EnumModPresetTask<T> : EnumBaseTask<T> where T : struct, Enum
|
||||
{
|
||||
private readonly Dictionary<T, Dictionary<string, ModPresetFileData>> _fileDataMap = new();
|
||||
|
||||
private readonly Dictionary<T, Dictionary<string, string>> _map;
|
||||
|
||||
public EnumModPresetTask(string name, Dictionary<T, Dictionary<string, string>> map) : base("ModPreset", name)
|
||||
{
|
||||
_map = map;
|
||||
|
||||
foreach (var enumPair in _map)
|
||||
{
|
||||
var dataMap = new Dictionary<string, ModPresetFileData>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
43
Bloxstrap/Models/SettingTasks/FontModPresetTask.cs
Normal file
43
Bloxstrap/Models/SettingTasks/FontModPresetTask.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
59
Bloxstrap/Models/SettingTasks/ModPresetTask.cs
Normal file
59
Bloxstrap/Models/SettingTasks/ModPresetTask.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using Bloxstrap.Models.SettingTasks.Base;
|
||||
|
||||
namespace Bloxstrap.Models.SettingTasks
|
||||
{
|
||||
public class ModPresetTask : BoolBaseTask
|
||||
{
|
||||
private Dictionary<string, ModPresetFileData> _fileDataMap = new();
|
||||
|
||||
private Dictionary<string, string> _pathMap;
|
||||
|
||||
public ModPresetTask(string name, string path, string resource) : this(name, new() {{ path, resource }}) { }
|
||||
|
||||
public ModPresetTask(string name, Dictionary<string, string> 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
public string ShortcutPath { get; set; }
|
||||
private string _exeFlags;
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -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<CustomIntegration> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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<string, string> Player
|
||||
{
|
||||
get { return CombineDictionaries(_common, _playerOnly); }
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<string, string> 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<string, string> _common = new Dictionary<string, string>()
|
||||
{
|
||||
{ "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<string, string> _playerOnly = new Dictionary<string, string>()
|
||||
{
|
||||
{ "RobloxApp.zip", @"" }
|
||||
};
|
||||
|
||||
private static IReadOnlyDictionary<string, string> _studioOnly = new Dictionary<string, string>()
|
||||
{
|
||||
{ "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<string, string> CombineDictionaries(IReadOnlyDictionary<string, string> d1, IReadOnlyDictionary<string, string> d2)
|
||||
{
|
||||
Dictionary<string, string> newD = new Dictionary<string, string>();
|
||||
|
||||
foreach (var d in d1)
|
||||
newD[d.Key] = d.Value;
|
||||
|
||||
foreach (var d in d2)
|
||||
newD[d.Key] = d.Value;
|
||||
|
||||
return newD;
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@ -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<string, string>();
|
||||
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");
|
||||
|
@ -7,18 +7,19 @@ namespace Bloxstrap
|
||||
static readonly Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
static readonly string[] resourceNames = assembly.GetManifestResourceNames();
|
||||
|
||||
public static async Task<byte[]> 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())
|
||||
public static async Task<byte[]> Get(string name)
|
||||
{
|
||||
using var stream = GetStream(name);
|
||||
using var memoryStream = new MemoryStream();
|
||||
|
||||
await stream.CopyToAsync(memoryStream);
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
29
Bloxstrap/Resources/Strings.Designer.cs
generated
29
Bloxstrap/Resources/Strings.Designer.cs
generated
@ -729,6 +729,24 @@ namespace Bloxstrap.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Please wait for installation to finish..
|
||||
/// </summary>
|
||||
public static string Dialog_AlreadyRunning_Installer {
|
||||
get {
|
||||
return ResourceManager.GetString("Dialog.AlreadyRunning.Installer", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Please wait for uninstallation to finish..
|
||||
/// </summary>
|
||||
public static string Dialog_AlreadyRunning_Uninstaller {
|
||||
get {
|
||||
return ResourceManager.GetString("Dialog.AlreadyRunning.Uninstaller", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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..
|
||||
/// </summary>
|
||||
@ -2751,6 +2769,17 @@ namespace Bloxstrap.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The emoji mod could not be applied because of a network error during download.
|
||||
///
|
||||
///{0}.
|
||||
/// </summary>
|
||||
public static string Menu_Mods_Presets_EmojiType_Error {
|
||||
get {
|
||||
return ResourceManager.GetString("Menu.Mods.Presets.EmojiType.Error", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Preferred emoji type.
|
||||
/// </summary>
|
||||
|
@ -1125,4 +1125,15 @@ If not, then please report this exception to the maintainers of this fork. Do NO
|
||||
<data name="ContextMenu.ServerInformation.Notification.Title.Reserved" xml:space="preserve">
|
||||
<value>Connected to reserved server</value>
|
||||
</data>
|
||||
<data name="Menu.Mods.Presets.EmojiType.Error" xml:space="preserve">
|
||||
<value>The emoji mod could not be applied because of a network error during download.
|
||||
|
||||
{0}</value>
|
||||
</data>
|
||||
<data name="Dialog.AlreadyRunning.Installer" xml:space="preserve">
|
||||
<value>Please wait for installation to finish.</value>
|
||||
</data>
|
||||
<data name="Dialog.AlreadyRunning.Uninstaller" xml:space="preserve">
|
||||
<value>Please wait for uninstallation to finish.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
@ -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<ClientVersion> GetInfo(string channel, bool extraInformation = false, string binaryType = "WindowsPlayer")
|
||||
public static async Task<ClientVersion> 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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -78,7 +78,6 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
|
||||
public ClassicFluentDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
ApplyTheme();
|
||||
|
||||
_viewModel = new ClassicFluentDialogViewModel(this);
|
||||
DataContext = _viewModel;
|
||||
|
@ -88,7 +88,6 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
|
||||
public FluentDialog(bool aero)
|
||||
{
|
||||
InitializeComponent();
|
||||
ApplyTheme();
|
||||
|
||||
_viewModel = new FluentDialogViewModel(this, aero);
|
||||
DataContext = _viewModel;
|
||||
|
@ -31,7 +31,6 @@ namespace Bloxstrap.UI.Elements.ContextMenu
|
||||
public MenuContainer(ActivityWatcher? activityWatcher, DiscordRichPresence? richPresenceHandler, int? processId)
|
||||
{
|
||||
InitializeComponent();
|
||||
ApplyTheme();
|
||||
|
||||
_activityWatcher = activityWatcher;
|
||||
_richPresenceHandler = richPresenceHandler;
|
||||
|
@ -1,9 +1,10 @@
|
||||
<ui:UiWindow x:Class="Bloxstrap.UI.Elements.ContextMenu.ServerInformation"
|
||||
<base:WpfUiWindow x:Class="Bloxstrap.UI.Elements.ContextMenu.ServerInformation"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.ContextMenu"
|
||||
xmlns:base="clr-namespace:Bloxstrap.UI.Elements.Base"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:models="clr-namespace:Bloxstrap.UI.ViewModels.ContextMenu"
|
||||
xmlns:resources="clr-namespace:Bloxstrap.Resources"
|
||||
@ -56,4 +57,4 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ui:UiWindow>
|
||||
</base:WpfUiWindow>
|
||||
|
@ -1,10 +1,11 @@
|
||||
<ui:UiWindow x:Class="Bloxstrap.UI.Elements.Dialogs.AddFastFlagDialog"
|
||||
<base:WpfUiWindow x:Class="Bloxstrap.UI.Elements.Dialogs.AddFastFlagDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Dialogs"
|
||||
xmlns:base="clr-namespace:Bloxstrap.UI.Elements.Base"
|
||||
xmlns:resources="clr-namespace:Bloxstrap.Resources"
|
||||
xmlns:converters="clr-namespace:Bloxstrap.UI.Converters"
|
||||
mc:Ignorable="d"
|
||||
@ -101,4 +102,4 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ui:UiWindow>
|
||||
</base:WpfUiWindow>
|
||||
|
@ -1,10 +1,11 @@
|
||||
<ui:UiWindow x:Class="Bloxstrap.UI.Elements.Dialogs.FluentMessageBox"
|
||||
<base:WpfUiWindow x:Class="Bloxstrap.UI.Elements.Dialogs.FluentMessageBox"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Dialogs"
|
||||
xmlns:base="clr-namespace:Bloxstrap.UI.Elements.Base"
|
||||
mc:Ignorable="d"
|
||||
Title="Bloxstrap"
|
||||
d:DesignWidth="480"
|
||||
@ -43,4 +44,4 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ui:UiWindow>
|
||||
</base:WpfUiWindow>
|
||||
|
@ -26,7 +26,6 @@ namespace Bloxstrap.UI.Elements.Dialogs
|
||||
|
||||
DataContext = viewModel;
|
||||
InitializeComponent();
|
||||
ApplyTheme();
|
||||
|
||||
viewModel.CloseRequestEvent += (_, _) => Close();
|
||||
}
|
||||
|
@ -36,7 +36,6 @@ namespace Bloxstrap.UI.Elements.Dialogs
|
||||
DataContext = viewModel;
|
||||
|
||||
InitializeComponent();
|
||||
ApplyTheme();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,6 @@ namespace Bloxstrap.UI.Elements.Dialogs
|
||||
DataContext = viewModel;
|
||||
|
||||
InitializeComponent();
|
||||
ApplyTheme();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,6 @@ namespace Bloxstrap.UI.Elements.Installer
|
||||
|
||||
DataContext = _viewModel;
|
||||
InitializeComponent();
|
||||
ApplyTheme();
|
||||
|
||||
App.Logger.WriteLine("MainWindow::MainWindow", "Initializing installer");
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
<base:WpfUiWindow x:Class="Bloxstrap.UI.Elements.Settings.MainWindow"
|
||||
x:Name="ConfigurationWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
@ -91,7 +90,7 @@
|
||||
</ItemsPanelTemplate>
|
||||
</StatusBar.ItemsPanel>
|
||||
<StatusBarItem Grid.Column="1" Padding="0,0,4,0">
|
||||
<ui:Button Content="{x:Static resources:Strings.Menu_Save}" Appearance="Primary" Command="{Binding SaveSettingsCommand, Mode=OneWay}" IsEnabled="{Binding ConfirmButtonEnabled, Mode=OneWay}" />
|
||||
<ui:Button Content="{x:Static resources:Strings.Menu_Save}" Appearance="Primary" Command="{Binding SaveSettingsCommand, Mode=OneWay}" />
|
||||
</StatusBarItem>
|
||||
<StatusBarItem Grid.Column="2" Padding="4,0,0,0">
|
||||
<ui:Button Content="{x:Static resources:Strings.Common_Close}" IsCancel="True" />
|
||||
|
@ -18,7 +18,6 @@ namespace Bloxstrap.UI.Elements.Settings
|
||||
DataContext = viewModel;
|
||||
|
||||
InitializeComponent();
|
||||
ApplyTheme();
|
||||
|
||||
App.Logger.WriteLine("MainWindow::MainWindow", "Initializing menu");
|
||||
|
||||
|
@ -599,7 +599,7 @@
|
||||
<controls:MarkdownTextBlock MarkdownText="[MaximumADHD](https://github.com/MaximumADHD)" />
|
||||
<controls:MarkdownTextBlock MarkdownText="[Multako](https://www.roblox.com/users/2485612194/profile)" />
|
||||
<controls:MarkdownTextBlock MarkdownText="[axstin](https://github.com/axstin)" />
|
||||
<controls:MarkdownTextBlock MarkdownText="[taskmanager](https://github.com/Manataraix)" />
|
||||
<controls:MarkdownTextBlock MarkdownText="[taskmanager](https://github.com/Mantaraix)" />
|
||||
<controls:MarkdownTextBlock MarkdownText="[apprehensions](https://github.com/apprehensions)" />
|
||||
</StackPanel>
|
||||
</controls:Expander>
|
||||
|
@ -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 @@
|
||||
<controls:OptionControl
|
||||
Header="{x:Static resources:Strings.Menu_Mods_Presets_OldDeathSound_Title}"
|
||||
Description="{x:Static resources:Strings.Menu_Mods_Presets_OldDeathSound_Description}">
|
||||
<ui:ToggleSwitch IsChecked="{Binding OldDeathSoundEnabled, Mode=TwoWay}" />
|
||||
<ui:ToggleSwitch IsChecked="{Binding OldDeathSoundTask.NewState, Mode=TwoWay}" />
|
||||
</controls:OptionControl>
|
||||
|
||||
<controls:OptionControl
|
||||
Header="{x:Static resources:Strings.Menu_Mods_Presets_MouseCursor_Title}"
|
||||
Description="{x:Static resources:Strings.Menu_Mods_Presets_MouseCursor_Description}">
|
||||
<ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" ItemsSource="{Binding CursorTypes, Mode=OneTime}" Text="{Binding SelectedCursorType, Mode=TwoWay}">
|
||||
<ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" ItemsSource="{Binding CursorTypeTask.Selections, Mode=OneTime}" Text="{Binding CursorTypeTask.NewState, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=., Converter={StaticResource EnumNameConverter}}" />
|
||||
@ -60,19 +62,19 @@
|
||||
<controls:OptionControl
|
||||
Header="{x:Static resources:Strings.Menu_Mods_Presets_OldAvatarEditor_Title}"
|
||||
Description="{x:Static resources:Strings.Menu_Mods_Presets_OldAvatarEditor_Description}">
|
||||
<ui:ToggleSwitch IsChecked="{Binding OldAvatarBackground, Mode=TwoWay}" />
|
||||
<ui:ToggleSwitch IsChecked="{Binding OldAvatarBackgroundTask.NewState, Mode=TwoWay}" />
|
||||
</controls:OptionControl>
|
||||
|
||||
<controls:OptionControl
|
||||
Header="{x:Static resources:Strings.Menu_Mods_Presets_OldCharacterSounds_Title}"
|
||||
Description="{x:Static resources:Strings.Menu_Mods_Presets_OldCharacterSounds_Description}">
|
||||
<ui:ToggleSwitch IsChecked="{Binding OldCharacterSoundsEnabled, Mode=TwoWay}" />
|
||||
<ui:ToggleSwitch IsChecked="{Binding OldCharacterSoundsTask.NewState, Mode=TwoWay}" />
|
||||
</controls:OptionControl>
|
||||
|
||||
<controls:OptionControl
|
||||
Header="{x:Static resources:Strings.Menu_Mods_Presets_EmojiType_Title}"
|
||||
Description="{x:Static resources:Strings.Menu_Mods_Presets_EmojiType_Description}">
|
||||
<ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="220" ItemsSource="{Binding EmojiTypes, Mode=OneTime}" Text="{Binding SelectedEmojiType, Mode=TwoWay}">
|
||||
<ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="220" ItemsSource="{Binding EmojiFontTask.Selections, Mode=OneTime}" Text="{Binding EmojiFontTask.NewState, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=., Converter={StaticResource EnumNameConverter}}" />
|
||||
|
@ -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 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<controls:OptionControl Grid.Column="0" Margin="0,0,4,0" Header="{x:Static resources:Strings.Common_Shortcuts_Desktop}">
|
||||
<ui:ToggleSwitch IsChecked="{Binding DesktopIcon, Mode=TwoWay}" />
|
||||
<ui:ToggleSwitch IsChecked="{Binding DesktopIconTask.NewState, Mode=TwoWay}" />
|
||||
</controls:OptionControl>
|
||||
|
||||
<controls:OptionControl Grid.Column="1" Margin="4,0,0,0" Header="{x:Static resources:Strings.Common_Shortcuts_StartMenu}">
|
||||
<ui:ToggleSwitch IsChecked="{Binding StartMenuIcon, Mode=TwoWay}" />
|
||||
<ui:ToggleSwitch IsChecked="{Binding StartMenuIconTask.NewState, Mode=TwoWay}" />
|
||||
</controls:OptionControl>
|
||||
</Grid>
|
||||
|
||||
@ -44,11 +45,11 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<controls:OptionControl Grid.Column="0" Margin="0,0,4,0" Header="{x:Static resources:Strings.LaunchMenu_LaunchRoblox}">
|
||||
<ui:ToggleSwitch IsChecked="{Binding PlayerIcon, Mode=TwoWay}" />
|
||||
<ui:ToggleSwitch IsChecked="{Binding PlayerIconTask.NewState, Mode=TwoWay}" />
|
||||
</controls:OptionControl>
|
||||
|
||||
<controls:OptionControl Grid.Column="1" Margin="4,0,0,0" Header="{x:Static resources:Strings.Menu_Title}">
|
||||
<ui:ToggleSwitch IsChecked="{Binding SettingsIcon, Mode=TwoWay}" />
|
||||
<ui:ToggleSwitch IsChecked="{Binding SettingsIconTask.NewState, Mode=TwoWay}" />
|
||||
</controls:OptionControl>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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<string, byte[]> 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<Enums.CursorType> 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<EmojiType> 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<Enums.CursorType> 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;
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user