mirror of
https://github.com/bloxstraplabs/bloxstrap.git
synced 2025-04-21 10:01:27 -07:00
commit
dca6fc46bb
@ -2,7 +2,8 @@
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
|
||||||
using Microsoft.Win32;
|
using Windows.Win32;
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
|
||||||
namespace Bloxstrap
|
namespace Bloxstrap
|
||||||
{
|
{
|
||||||
@ -17,10 +18,13 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
// used only for communicating between app and menu - use Directories.Base for anything else
|
// used only for communicating between app and menu - use Directories.Base for anything else
|
||||||
public static string BaseDirectory = null!;
|
public static string BaseDirectory = null!;
|
||||||
|
public static string? CustomFontLocation;
|
||||||
|
|
||||||
public static bool ShouldSaveConfigs { get; set; } = false;
|
public static bool ShouldSaveConfigs { get; set; } = false;
|
||||||
|
|
||||||
public static bool IsSetupComplete { get; set; } = true;
|
public static bool IsSetupComplete { get; set; } = true;
|
||||||
public static bool IsFirstRun { get; private set; } = true;
|
public static bool IsFirstRun { get; set; } = true;
|
||||||
|
|
||||||
public static bool IsQuiet { get; private set; } = false;
|
public static bool IsQuiet { get; private set; } = false;
|
||||||
public static bool IsUninstall { get; private set; } = false;
|
public static bool IsUninstall { get; private set; } = false;
|
||||||
public static bool IsNoLaunch { get; private set; } = false;
|
public static bool IsNoLaunch { get; private set; } = false;
|
||||||
@ -39,7 +43,11 @@ namespace Bloxstrap
|
|||||||
public static readonly JsonManager<State> State = new();
|
public static readonly JsonManager<State> State = new();
|
||||||
public static readonly FastFlagManager FastFlags = new();
|
public static readonly FastFlagManager FastFlags = new();
|
||||||
|
|
||||||
public static readonly HttpClient HttpClient = new(new HttpClientLoggingHandler(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.All }));
|
public static readonly HttpClient HttpClient = new(
|
||||||
|
new HttpClientLoggingHandler(
|
||||||
|
new HttpClientHandler { AutomaticDecompression = DecompressionMethods.All }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
public static void Terminate(ErrorCode exitCode = ErrorCode.ERROR_SUCCESS)
|
public static void Terminate(ErrorCode exitCode = ErrorCode.ERROR_SUCCESS)
|
||||||
{
|
{
|
||||||
@ -51,7 +59,7 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
int exitCodeNum = (int)exitCode;
|
int exitCodeNum = (int)exitCode;
|
||||||
|
|
||||||
Logger.WriteLine($"[App::Terminate] Terminating with exit code {exitCodeNum} ({exitCode})");
|
Logger.WriteLine("App::Terminate", $"Terminating with exit code {exitCodeNum} ({exitCode})");
|
||||||
|
|
||||||
Settings.Save();
|
Settings.Save();
|
||||||
State.Save();
|
State.Save();
|
||||||
@ -64,14 +72,16 @@ namespace Bloxstrap
|
|||||||
{
|
{
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
|
|
||||||
Logger.WriteLine("[App::OnStartup] An exception occurred when running the main thread");
|
Logger.WriteLine("App::GlobalExceptionHandler", "An exception occurred");
|
||||||
Logger.WriteLine($"[App::OnStartup] {e.Exception}");
|
|
||||||
|
|
||||||
FinalizeExceptionHandling(e.Exception);
|
FinalizeExceptionHandling(e.Exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FinalizeExceptionHandling(Exception exception)
|
public static void FinalizeExceptionHandling(Exception exception, bool log = true)
|
||||||
{
|
{
|
||||||
|
if (log)
|
||||||
|
Logger.WriteException("App::FinalizeExceptionHandling", exception);
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
throw exception;
|
throw exception;
|
||||||
#else
|
#else
|
||||||
@ -84,14 +94,18 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
protected override void OnStartup(StartupEventArgs e)
|
protected override void OnStartup(StartupEventArgs e)
|
||||||
{
|
{
|
||||||
|
const string LOG_IDENT = "App::OnStartup";
|
||||||
|
|
||||||
base.OnStartup(e);
|
base.OnStartup(e);
|
||||||
|
|
||||||
Logger.WriteLine($"[App::OnStartup] Starting {ProjectName} v{Version}");
|
Logger.WriteLine(LOG_IDENT, $"Starting {ProjectName} v{Version}");
|
||||||
|
|
||||||
if (String.IsNullOrEmpty(BuildMetadata.CommitHash))
|
if (String.IsNullOrEmpty(BuildMetadata.CommitHash))
|
||||||
Logger.WriteLine($"[App::OnStartup] Compiled {BuildMetadata.Timestamp.ToFriendlyString()} from {BuildMetadata.Machine}");
|
Logger.WriteLine(LOG_IDENT, $"Compiled {BuildMetadata.Timestamp.ToFriendlyString()} from {BuildMetadata.Machine}");
|
||||||
else
|
else
|
||||||
Logger.WriteLine($"[App::OnStartup] Compiled {BuildMetadata.Timestamp.ToFriendlyString()} from commit {BuildMetadata.CommitHash} ({BuildMetadata.CommitRef})");
|
Logger.WriteLine(LOG_IDENT, $"Compiled {BuildMetadata.Timestamp.ToFriendlyString()} from commit {BuildMetadata.CommitHash} ({BuildMetadata.CommitRef})");
|
||||||
|
|
||||||
|
Logger.WriteLine(LOG_IDENT, $"Loaded from {Paths.Process}");
|
||||||
|
|
||||||
// To customize application configuration such as set high DPI settings or default font,
|
// To customize application configuration such as set high DPI settings or default font,
|
||||||
// see https://aka.ms/applicationconfiguration.
|
// see https://aka.ms/applicationconfiguration.
|
||||||
@ -99,79 +113,75 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
LaunchArgs = e.Args;
|
LaunchArgs = e.Args;
|
||||||
|
|
||||||
HttpClient.Timeout = TimeSpan.FromMinutes(5);
|
HttpClient.Timeout = TimeSpan.FromSeconds(30);
|
||||||
HttpClient.DefaultRequestHeaders.Add("User-Agent", ProjectRepository);
|
HttpClient.DefaultRequestHeaders.Add("User-Agent", ProjectRepository);
|
||||||
|
|
||||||
if (LaunchArgs.Length > 0)
|
if (LaunchArgs.Length > 0)
|
||||||
{
|
{
|
||||||
if (Array.IndexOf(LaunchArgs, "-preferences") != -1 || Array.IndexOf(LaunchArgs, "-menu") != -1)
|
if (Array.IndexOf(LaunchArgs, "-preferences") != -1 || Array.IndexOf(LaunchArgs, "-menu") != -1)
|
||||||
{
|
{
|
||||||
Logger.WriteLine("[App::OnStartup] Started with IsMenuLaunch flag");
|
Logger.WriteLine(LOG_IDENT, "Started with IsMenuLaunch flag");
|
||||||
IsMenuLaunch = true;
|
IsMenuLaunch = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.IndexOf(LaunchArgs, "-quiet") != -1)
|
if (Array.IndexOf(LaunchArgs, "-quiet") != -1)
|
||||||
{
|
{
|
||||||
Logger.WriteLine("[App::OnStartup] Started with IsQuiet flag");
|
Logger.WriteLine(LOG_IDENT, "Started with IsQuiet flag");
|
||||||
IsQuiet = true;
|
IsQuiet = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.IndexOf(LaunchArgs, "-uninstall") != -1)
|
if (Array.IndexOf(LaunchArgs, "-uninstall") != -1)
|
||||||
{
|
{
|
||||||
Logger.WriteLine("[App::OnStartup] Started with IsUninstall flag");
|
Logger.WriteLine(LOG_IDENT, "Started with IsUninstall flag");
|
||||||
IsUninstall = true;
|
IsUninstall = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.IndexOf(LaunchArgs, "-nolaunch") != -1)
|
if (Array.IndexOf(LaunchArgs, "-nolaunch") != -1)
|
||||||
{
|
{
|
||||||
Logger.WriteLine("[App::OnStartup] Started with IsNoLaunch flag");
|
Logger.WriteLine(LOG_IDENT, "Started with IsNoLaunch flag");
|
||||||
IsNoLaunch = true;
|
IsNoLaunch = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.IndexOf(LaunchArgs, "-upgrade") != -1)
|
if (Array.IndexOf(LaunchArgs, "-upgrade") != -1)
|
||||||
{
|
{
|
||||||
Logger.WriteLine("[App::OnStartup] Bloxstrap started with IsUpgrade flag");
|
Logger.WriteLine(LOG_IDENT, "Bloxstrap started with IsUpgrade flag");
|
||||||
IsUpgrade = true;
|
IsUpgrade = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if installed
|
if (!IsMenuLaunch)
|
||||||
using (RegistryKey? registryKey = Registry.CurrentUser.OpenSubKey($@"Software\{ProjectName}"))
|
|
||||||
{
|
{
|
||||||
string? installLocation = null;
|
Logger.WriteLine(LOG_IDENT, "Performing connectivity check...");
|
||||||
|
|
||||||
if (registryKey is not null)
|
try
|
||||||
installLocation = (string?)registryKey.GetValue("InstallLocation");
|
|
||||||
|
|
||||||
if (registryKey is null || installLocation is null)
|
|
||||||
{
|
{
|
||||||
Logger.WriteLine("[App::OnStartup] Running first-time install");
|
HttpClient.GetAsync("https://detectportal.firefox.com").Wait();
|
||||||
|
Logger.WriteLine(LOG_IDENT, "Connectivity check finished");
|
||||||
BaseDirectory = Path.Combine(Directories.LocalAppData, ProjectName);
|
|
||||||
Logger.Initialize(true);
|
|
||||||
|
|
||||||
if (!IsQuiet)
|
|
||||||
{
|
|
||||||
IsSetupComplete = false;
|
|
||||||
FastFlags.Load();
|
|
||||||
Controls.ShowMenu();
|
|
||||||
}
|
}
|
||||||
}
|
catch (Exception ex)
|
||||||
else
|
|
||||||
{
|
{
|
||||||
IsFirstRun = false;
|
Logger.WriteLine(LOG_IDENT, "Connectivity check failed!");
|
||||||
BaseDirectory = installLocation;
|
Logger.WriteException(LOG_IDENT, ex);
|
||||||
}
|
|
||||||
}
|
if (ex.GetType() == typeof(AggregateException))
|
||||||
|
ex = ex.InnerException!;
|
||||||
|
|
||||||
|
Controls.ShowConnectivityDialog(
|
||||||
|
"the internet",
|
||||||
|
$"Something may be preventing {ProjectName} from connecting to the internet, or you are currently offline. Please check and try again.",
|
||||||
|
ex
|
||||||
|
);
|
||||||
|
|
||||||
// exit if we don't click the install button on installation
|
|
||||||
if (!IsSetupComplete)
|
|
||||||
{
|
|
||||||
Logger.WriteLine("[App::OnStartup] Installation cancelled!");
|
|
||||||
Terminate(ErrorCode.ERROR_CANCELLED);
|
Terminate(ErrorCode.ERROR_CANCELLED);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Directories.Initialize(BaseDirectory);
|
using (var checker = new InstallChecker())
|
||||||
|
{
|
||||||
|
checker.Check();
|
||||||
|
}
|
||||||
|
|
||||||
|
Paths.Initialize(BaseDirectory);
|
||||||
|
|
||||||
// we shouldn't save settings on the first run until the first installation is finished,
|
// we shouldn't save settings on the first run until the first installation is finished,
|
||||||
// just in case the user decides to cancel the install
|
// just in case the user decides to cancel the install
|
||||||
@ -181,7 +191,7 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
if (!Logger.Initialized)
|
if (!Logger.Initialized)
|
||||||
{
|
{
|
||||||
Logger.WriteLine("[App::OnStartup] Possible duplicate launch detected, terminating.");
|
Logger.WriteLine(LOG_IDENT, "Possible duplicate launch detected, terminating.");
|
||||||
Terminate();
|
Terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,7 +205,7 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
if (!IsUninstall && !IsFirstRun)
|
if (!IsUninstall && !IsFirstRun)
|
||||||
Updater.CheckInstalledVersion();
|
InstallChecker.CheckUpgrade();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
string commandLine = "";
|
string commandLine = "";
|
||||||
@ -206,9 +216,9 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
if (menuProcess is not null)
|
if (menuProcess is not null)
|
||||||
{
|
{
|
||||||
IntPtr handle = menuProcess.MainWindowHandle;
|
var handle = menuProcess.MainWindowHandle;
|
||||||
Logger.WriteLine($"[App::OnStartup] Found an already existing menu window with handle {handle}");
|
Logger.WriteLine(LOG_IDENT, $"Found an already existing menu window with handle {handle}");
|
||||||
NativeMethods.SetForegroundWindow(handle);
|
PInvoke.SetForegroundWindow((HWND)handle);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -253,13 +263,13 @@ namespace Bloxstrap
|
|||||||
ShouldSaveConfigs = true;
|
ShouldSaveConfigs = true;
|
||||||
|
|
||||||
// start bootstrapper and show the bootstrapper modal if we're not running silently
|
// start bootstrapper and show the bootstrapper modal if we're not running silently
|
||||||
Logger.WriteLine($"[App::OnStartup] Initializing bootstrapper");
|
Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper");
|
||||||
Bootstrapper bootstrapper = new(commandLine);
|
Bootstrapper bootstrapper = new(commandLine);
|
||||||
IBootstrapperDialog? dialog = null;
|
IBootstrapperDialog? dialog = null;
|
||||||
|
|
||||||
if (!IsQuiet)
|
if (!IsQuiet)
|
||||||
{
|
{
|
||||||
Logger.WriteLine($"[App::OnStartup] Initializing bootstrapper dialog");
|
Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper dialog");
|
||||||
dialog = Settings.Prop.BootstrapperStyle.GetNew();
|
dialog = Settings.Prop.BootstrapperStyle.GetNew();
|
||||||
bootstrapper.Dialog = dialog;
|
bootstrapper.Dialog = dialog;
|
||||||
dialog.Bootstrapper = bootstrapper;
|
dialog.Bootstrapper = bootstrapper;
|
||||||
@ -273,12 +283,12 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
if (Settings.Prop.MultiInstanceLaunching)
|
if (Settings.Prop.MultiInstanceLaunching)
|
||||||
{
|
{
|
||||||
Logger.WriteLine("[App::OnStartup] Creating singleton mutex");
|
Logger.WriteLine(LOG_IDENT, "Creating singleton mutex");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Mutex.OpenExisting("ROBLOX_singletonMutex");
|
Mutex.OpenExisting("ROBLOX_singletonMutex");
|
||||||
Logger.WriteLine("[App::OnStartup] Warning - singleton mutex already exists!");
|
Logger.WriteLine(LOG_IDENT, "Warning - singleton mutex already exists!");
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -287,22 +297,20 @@ namespace Bloxstrap
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Task bootstrapperTask = Task.Run(() => bootstrapper.Run());
|
Task bootstrapperTask = Task.Run(() => bootstrapper.Run()).ContinueWith(t =>
|
||||||
|
|
||||||
bootstrapperTask.ContinueWith(t =>
|
|
||||||
{
|
{
|
||||||
Logger.WriteLine("[App::OnStartup] Bootstrapper task has finished");
|
Logger.WriteLine(LOG_IDENT, "Bootstrapper task has finished");
|
||||||
|
|
||||||
// notifyicon is blocking main thread, must be disposed here
|
// notifyicon is blocking main thread, must be disposed here
|
||||||
NotifyIcon?.Dispose();
|
NotifyIcon?.Dispose();
|
||||||
|
|
||||||
if (t.IsFaulted)
|
if (t.IsFaulted)
|
||||||
Logger.WriteLine("[App::OnStartup] An exception occurred when running the bootstrapper");
|
Logger.WriteLine(LOG_IDENT, "An exception occurred when running the bootstrapper");
|
||||||
|
|
||||||
if (t.Exception is null)
|
if (t.Exception is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Logger.WriteLine($"[App::OnStartup] {t.Exception}");
|
Logger.WriteException(LOG_IDENT, t.Exception);
|
||||||
|
|
||||||
Exception exception = t.Exception;
|
Exception exception = t.Exception;
|
||||||
|
|
||||||
@ -311,7 +319,7 @@ namespace Bloxstrap
|
|||||||
exception = t.Exception.InnerException!;
|
exception = t.Exception.InnerException!;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
FinalizeExceptionHandling(exception);
|
FinalizeExceptionHandling(exception, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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
|
// 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
|
||||||
@ -320,13 +328,13 @@ namespace Bloxstrap
|
|||||||
if (!IsNoLaunch && Settings.Prop.EnableActivityTracking)
|
if (!IsNoLaunch && Settings.Prop.EnableActivityTracking)
|
||||||
NotifyIcon?.InitializeContextMenu();
|
NotifyIcon?.InitializeContextMenu();
|
||||||
|
|
||||||
Logger.WriteLine($"[App::OnStartup] Waiting for bootstrapper task to finish");
|
Logger.WriteLine(LOG_IDENT, "Waiting for bootstrapper task to finish");
|
||||||
|
|
||||||
bootstrapperTask.Wait();
|
bootstrapperTask.Wait();
|
||||||
|
|
||||||
if (singletonMutex is not null)
|
if (singletonMutex is not null)
|
||||||
{
|
{
|
||||||
Logger.WriteLine($"[App::OnStartup] We have singleton mutex ownership! Running in background until all Roblox processes are closed");
|
Logger.WriteLine(LOG_IDENT, "We have singleton mutex ownership! Running in background until all Roblox processes are closed");
|
||||||
|
|
||||||
// we've got ownership of the roblox singleton mutex!
|
// we've got ownership of the roblox singleton mutex!
|
||||||
// if we stop running, everything will screw up once any more roblox instances launched
|
// if we stop running, everything will screw up once any more roblox instances launched
|
||||||
@ -335,7 +343,7 @@ namespace Bloxstrap
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.WriteLine($"[App::OnStartup] Successfully reached end of main thread. Terminating...");
|
Logger.WriteLine(LOG_IDENT, "Successfully reached end of main thread. Terminating...");
|
||||||
|
|
||||||
Terminate();
|
Terminate();
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
<UseWPF>true</UseWPF>
|
<UseWPF>true</UseWPF>
|
||||||
<UseWindowsForms>True</UseWindowsForms>
|
<UseWindowsForms>True</UseWindowsForms>
|
||||||
<ApplicationIcon>Bloxstrap.ico</ApplicationIcon>
|
<ApplicationIcon>Bloxstrap.ico</ApplicationIcon>
|
||||||
<Version>2.4.0</Version>
|
<Version>2.5.0</Version>
|
||||||
<FileVersion>2.4.0.0</FileVersion>
|
<FileVersion>2.5.0.0</FileVersion>
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@ -39,7 +39,11 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
||||||
<PackageReference Include="DiscordRichPresence" Version="1.1.4.20" />
|
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||||
|
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.18-beta">
|
||||||
|
<!--<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>-->
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="securifybv.ShellLink" Version="0.1.0" />
|
<PackageReference Include="securifybv.ShellLink" Version="0.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
19
Bloxstrap/Exceptions/HttpResponseException.cs
Normal file
19
Bloxstrap/Exceptions/HttpResponseException.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Bloxstrap.Exceptions
|
||||||
|
{
|
||||||
|
internal class HttpResponseException : Exception
|
||||||
|
{
|
||||||
|
public HttpResponseMessage ResponseMessage { get; }
|
||||||
|
|
||||||
|
public HttpResponseException(HttpResponseMessage responseMessage)
|
||||||
|
: base($"Could not connect to {responseMessage.RequestMessage!.RequestUri} because it returned HTTP {(int)responseMessage.StatusCode} ({responseMessage.ReasonPhrase})")
|
||||||
|
{
|
||||||
|
ResponseMessage = responseMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bloxstrap.Exceptions
|
|
||||||
{
|
|
||||||
internal class HttpResponseUnsuccessfulException : Exception
|
|
||||||
{
|
|
||||||
public HttpResponseMessage ResponseMessage { get; }
|
|
||||||
|
|
||||||
public HttpResponseUnsuccessfulException(HttpResponseMessage responseMessage) : base()
|
|
||||||
{
|
|
||||||
ResponseMessage = responseMessage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,6 +10,8 @@ namespace Bloxstrap.Extensions
|
|||||||
|
|
||||||
public static Icon GetIcon(this BootstrapperIcon icon)
|
public static Icon GetIcon(this BootstrapperIcon icon)
|
||||||
{
|
{
|
||||||
|
const string LOG_IDENT = "BootstrapperIconEx::GetIcon";
|
||||||
|
|
||||||
// load the custom icon file
|
// load the custom icon file
|
||||||
if (icon == BootstrapperIcon.IconCustom)
|
if (icon == BootstrapperIcon.IconCustom)
|
||||||
{
|
{
|
||||||
@ -21,7 +23,8 @@ namespace Bloxstrap.Extensions
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[BootstrapperIconEx::GetIcon] Failed to load custom icon! {ex}");
|
App.Logger.WriteLine(LOG_IDENT, $"Failed to load custom icon!");
|
||||||
|
App.Logger.WriteException(LOG_IDENT, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return customIcon ?? Properties.Resources.IconBloxstrap;
|
return customIcon ?? Properties.Resources.IconBloxstrap;
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
if (emojiType == EmojiType.Default)
|
if (emojiType == EmojiType.Default)
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
return $"https://github.com/NikSavchenk0/rbxcustom-fontemojis/raw/8a552f4aaaecfa58d6bd9b0540e1ac16e81faadb/{Filenames[emojiType]}";
|
return $"https://github.com/pizzaboxer/rbxcustom-fontemojis/releases/download/my-phone-is-78-percent/{Filenames[emojiType]}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
using System.Windows.Input;
|
using System.Windows.Forms;
|
||||||
using System.Windows.Media.Animation;
|
|
||||||
|
using Windows.Win32;
|
||||||
|
using Windows.Win32.Graphics.Gdi;
|
||||||
|
|
||||||
namespace Bloxstrap
|
namespace Bloxstrap
|
||||||
{
|
{
|
||||||
public class FastFlagManager : JsonManager<Dictionary<string, object>>
|
public class FastFlagManager : JsonManager<Dictionary<string, object>>
|
||||||
{
|
{
|
||||||
public override string FileLocation => Path.Combine(Directories.Modifications, "ClientSettings\\ClientAppSettings.json");
|
public override string FileLocation => Path.Combine(Paths.Modifications, "ClientSettings\\ClientAppSettings.json");
|
||||||
|
|
||||||
// this is the value of the 'FStringPartTexturePackTablePre2022' flag
|
// this is the value of the 'FStringPartTexturePackTablePre2022' flag
|
||||||
public const string OldTexturesFlagValue = "{\"foil\":{\"ids\":[\"rbxassetid://7546645012\",\"rbxassetid://7546645118\"],\"color\":[255,255,255,255]},\"brick\":{\"ids\":[\"rbxassetid://7546650097\",\"rbxassetid://7546645118\"],\"color\":[204,201,200,232]},\"cobblestone\":{\"ids\":[\"rbxassetid://7546652947\",\"rbxassetid://7546645118\"],\"color\":[212,200,187,250]},\"concrete\":{\"ids\":[\"rbxassetid://7546653951\",\"rbxassetid://7546654144\"],\"color\":[208,208,208,255]},\"diamondplate\":{\"ids\":[\"rbxassetid://7547162198\",\"rbxassetid://7546645118\"],\"color\":[170,170,170,255]},\"fabric\":{\"ids\":[\"rbxassetid://7547101130\",\"rbxassetid://7546645118\"],\"color\":[105,104,102,244]},\"glass\":{\"ids\":[\"rbxassetid://7547304948\",\"rbxassetid://7546645118\"],\"color\":[254,254,254,7]},\"granite\":{\"ids\":[\"rbxassetid://7547164710\",\"rbxassetid://7546645118\"],\"color\":[113,113,113,255]},\"grass\":{\"ids\":[\"rbxassetid://7547169285\",\"rbxassetid://7546645118\"],\"color\":[165,165,159,255]},\"ice\":{\"ids\":[\"rbxassetid://7547171356\",\"rbxassetid://7546645118\"],\"color\":[255,255,255,255]},\"marble\":{\"ids\":[\"rbxassetid://7547177270\",\"rbxassetid://7546645118\"],\"color\":[199,199,199,255]},\"metal\":{\"ids\":[\"rbxassetid://7547288171\",\"rbxassetid://7546645118\"],\"color\":[199,199,199,255]},\"pebble\":{\"ids\":[\"rbxassetid://7547291361\",\"rbxassetid://7546645118\"],\"color\":[208,208,208,255]},\"corrodedmetal\":{\"ids\":[\"rbxassetid://7547184629\",\"rbxassetid://7546645118\"],\"color\":[159,119,95,200]},\"sand\":{\"ids\":[\"rbxassetid://7547295153\",\"rbxassetid://7546645118\"],\"color\":[220,220,220,255]},\"slate\":{\"ids\":[\"rbxassetid://7547298114\",\"rbxassetid://7547298323\"],\"color\":[193,193,193,255]},\"wood\":{\"ids\":[\"rbxassetid://7547303225\",\"rbxassetid://7547298786\"],\"color\":[227,227,227,255]},\"woodplanks\":{\"ids\":[\"rbxassetid://7547332968\",\"rbxassetid://7546645118\"],\"color\":[212,209,203,255]},\"asphalt\":{\"ids\":[\"rbxassetid://9873267379\",\"rbxassetid://9438410548\"],\"color\":[123,123,123,234]},\"basalt\":{\"ids\":[\"rbxassetid://9873270487\",\"rbxassetid://9438413638\"],\"color\":[154,154,153,238]},\"crackedlava\":{\"ids\":[\"rbxassetid://9438582231\",\"rbxassetid://9438453972\"],\"color\":[74,78,80,156]},\"glacier\":{\"ids\":[\"rbxassetid://9438851661\",\"rbxassetid://9438453972\"],\"color\":[226,229,229,243]},\"ground\":{\"ids\":[\"rbxassetid://9439044431\",\"rbxassetid://9438453972\"],\"color\":[114,114,112,240]},\"leafygrass\":{\"ids\":[\"rbxassetid://9873288083\",\"rbxassetid://9438453972\"],\"color\":[121,117,113,234]},\"limestone\":{\"ids\":[\"rbxassetid://9873289812\",\"rbxassetid://9438453972\"],\"color\":[235,234,230,250]},\"mud\":{\"ids\":[\"rbxassetid://9873319819\",\"rbxassetid://9438453972\"],\"color\":[130,130,130,252]},\"pavement\":{\"ids\":[\"rbxassetid://9873322398\",\"rbxassetid://9438453972\"],\"color\":[142,142,144,236]},\"rock\":{\"ids\":[\"rbxassetid://9873515198\",\"rbxassetid://9438453972\"],\"color\":[154,154,154,248]},\"salt\":{\"ids\":[\"rbxassetid://9439566986\",\"rbxassetid://9438453972\"],\"color\":[220,220,221,255]},\"sandstone\":{\"ids\":[\"rbxassetid://9873521380\",\"rbxassetid://9438453972\"],\"color\":[174,171,169,246]},\"snow\":{\"ids\":[\"rbxassetid://9439632387\",\"rbxassetid://9438453972\"],\"color\":[218,218,218,255]}}";
|
public const string OldTexturesFlagValue = "{\"foil\":{\"ids\":[\"rbxassetid://7546645012\",\"rbxassetid://7546645118\"],\"color\":[255,255,255,255]},\"brick\":{\"ids\":[\"rbxassetid://7546650097\",\"rbxassetid://7546645118\"],\"color\":[204,201,200,232]},\"cobblestone\":{\"ids\":[\"rbxassetid://7546652947\",\"rbxassetid://7546645118\"],\"color\":[212,200,187,250]},\"concrete\":{\"ids\":[\"rbxassetid://7546653951\",\"rbxassetid://7546654144\"],\"color\":[208,208,208,255]},\"diamondplate\":{\"ids\":[\"rbxassetid://7547162198\",\"rbxassetid://7546645118\"],\"color\":[170,170,170,255]},\"fabric\":{\"ids\":[\"rbxassetid://7547101130\",\"rbxassetid://7546645118\"],\"color\":[105,104,102,244]},\"glass\":{\"ids\":[\"rbxassetid://7547304948\",\"rbxassetid://7546645118\"],\"color\":[254,254,254,7]},\"granite\":{\"ids\":[\"rbxassetid://7547164710\",\"rbxassetid://7546645118\"],\"color\":[113,113,113,255]},\"grass\":{\"ids\":[\"rbxassetid://7547169285\",\"rbxassetid://7546645118\"],\"color\":[165,165,159,255]},\"ice\":{\"ids\":[\"rbxassetid://7547171356\",\"rbxassetid://7546645118\"],\"color\":[255,255,255,255]},\"marble\":{\"ids\":[\"rbxassetid://7547177270\",\"rbxassetid://7546645118\"],\"color\":[199,199,199,255]},\"metal\":{\"ids\":[\"rbxassetid://7547288171\",\"rbxassetid://7546645118\"],\"color\":[199,199,199,255]},\"pebble\":{\"ids\":[\"rbxassetid://7547291361\",\"rbxassetid://7546645118\"],\"color\":[208,208,208,255]},\"corrodedmetal\":{\"ids\":[\"rbxassetid://7547184629\",\"rbxassetid://7546645118\"],\"color\":[159,119,95,200]},\"sand\":{\"ids\":[\"rbxassetid://7547295153\",\"rbxassetid://7546645118\"],\"color\":[220,220,220,255]},\"slate\":{\"ids\":[\"rbxassetid://7547298114\",\"rbxassetid://7547298323\"],\"color\":[193,193,193,255]},\"wood\":{\"ids\":[\"rbxassetid://7547303225\",\"rbxassetid://7547298786\"],\"color\":[227,227,227,255]},\"woodplanks\":{\"ids\":[\"rbxassetid://7547332968\",\"rbxassetid://7546645118\"],\"color\":[212,209,203,255]},\"asphalt\":{\"ids\":[\"rbxassetid://9873267379\",\"rbxassetid://9438410548\"],\"color\":[123,123,123,234]},\"basalt\":{\"ids\":[\"rbxassetid://9873270487\",\"rbxassetid://9438413638\"],\"color\":[154,154,153,238]},\"crackedlava\":{\"ids\":[\"rbxassetid://9438582231\",\"rbxassetid://9438453972\"],\"color\":[74,78,80,156]},\"glacier\":{\"ids\":[\"rbxassetid://9438851661\",\"rbxassetid://9438453972\"],\"color\":[226,229,229,243]},\"ground\":{\"ids\":[\"rbxassetid://9439044431\",\"rbxassetid://9438453972\"],\"color\":[114,114,112,240]},\"leafygrass\":{\"ids\":[\"rbxassetid://9873288083\",\"rbxassetid://9438453972\"],\"color\":[121,117,113,234]},\"limestone\":{\"ids\":[\"rbxassetid://9873289812\",\"rbxassetid://9438453972\"],\"color\":[235,234,230,250]},\"mud\":{\"ids\":[\"rbxassetid://9873319819\",\"rbxassetid://9438453972\"],\"color\":[130,130,130,252]},\"pavement\":{\"ids\":[\"rbxassetid://9873322398\",\"rbxassetid://9438453972\"],\"color\":[142,142,144,236]},\"rock\":{\"ids\":[\"rbxassetid://9873515198\",\"rbxassetid://9438453972\"],\"color\":[154,154,154,248]},\"salt\":{\"ids\":[\"rbxassetid://9439566986\",\"rbxassetid://9438453972\"],\"color\":[220,220,221,255]},\"sandstone\":{\"ids\":[\"rbxassetid://9873521380\",\"rbxassetid://9438453972\"],\"color\":[174,171,169,246]},\"snow\":{\"ids\":[\"rbxassetid://9439632387\",\"rbxassetid://9438453972\"],\"color\":[218,218,218,255]}}";
|
||||||
@ -20,11 +22,10 @@ namespace Bloxstrap
|
|||||||
{ "HTTP.Proxy.Address.3", "DFStringHttpCurlProxyHostAndPortForExternalUrl" },
|
{ "HTTP.Proxy.Address.3", "DFStringHttpCurlProxyHostAndPortForExternalUrl" },
|
||||||
|
|
||||||
{ "Rendering.Framerate", "DFIntTaskSchedulerTargetFps" },
|
{ "Rendering.Framerate", "DFIntTaskSchedulerTargetFps" },
|
||||||
{ "Rendering.Fullscreen", "FFlagHandleAltEnterFullscreenManually" },
|
{ "Rendering.ManualFullscreen", "FFlagHandleAltEnterFullscreenManually" },
|
||||||
{ "Rendering.TexturePack", "FStringPartTexturePackTable2022" },
|
{ "Rendering.TexturePack", "FStringPartTexturePackTable2022" },
|
||||||
|
{ "Rendering.DisableScaling", "DFFlagDisableDPIScale" },
|
||||||
{ "Rendering.DPI.Disable", "DFFlagDisableDPIScale" },
|
{ "Rendering.MSAA", "FIntDebugForceMSAASamples" },
|
||||||
{ "Rendering.DPI.Variable", "DFFlagVariableDPIScale2" },
|
|
||||||
|
|
||||||
{ "Rendering.Mode.D3D11", "FFlagDebugGraphicsPreferD3D11" },
|
{ "Rendering.Mode.D3D11", "FFlagDebugGraphicsPreferD3D11" },
|
||||||
{ "Rendering.Mode.D3D10", "FFlagDebugGraphicsPreferD3D11FL10" },
|
{ "Rendering.Mode.D3D10", "FFlagDebugGraphicsPreferD3D11FL10" },
|
||||||
@ -63,6 +64,15 @@ namespace Bloxstrap
|
|||||||
{ "Future (Phase 3)", "Future" }
|
{ "Future (Phase 3)", "Future" }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static IReadOnlyDictionary<string, string?> MSAAModes => new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
{ "Automatic", null },
|
||||||
|
{ "1x MSAA", "1" },
|
||||||
|
{ "2x MSAA", "2" },
|
||||||
|
{ "4x MSAA", "4" },
|
||||||
|
{ "8x MSAA", "8" }
|
||||||
|
};
|
||||||
|
|
||||||
// this is one hell of a dictionary definition lmao
|
// this is one hell of a dictionary definition lmao
|
||||||
// since these all set the same flags, wouldn't making this use bitwise operators be better?
|
// since these all set the same flags, wouldn't making this use bitwise operators be better?
|
||||||
public static IReadOnlyDictionary<string, Dictionary<string, string?>> IGMenuVersions => new Dictionary<string, Dictionary<string, string?>>
|
public static IReadOnlyDictionary<string, Dictionary<string, string?>> IGMenuVersions => new Dictionary<string, Dictionary<string, string?>>
|
||||||
@ -108,19 +118,21 @@ namespace Bloxstrap
|
|||||||
// to delete a flag, set the value as null
|
// to delete a flag, set the value as null
|
||||||
public void SetValue(string key, object? value)
|
public void SetValue(string key, object? value)
|
||||||
{
|
{
|
||||||
|
const string LOG_IDENT = "FastFlagManager::SetValue";
|
||||||
|
|
||||||
if (value is null)
|
if (value is null)
|
||||||
{
|
{
|
||||||
if (Prop.ContainsKey(key))
|
if (Prop.ContainsKey(key))
|
||||||
App.Logger.WriteLine($"[FastFlagManager::SetValue] Deletion of '{key}' is pending");
|
App.Logger.WriteLine(LOG_IDENT, $"Deletion of '{key}' is pending");
|
||||||
|
|
||||||
Prop.Remove(key);
|
Prop.Remove(key);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (Prop.ContainsKey(key))
|
if (Prop.ContainsKey(key))
|
||||||
App.Logger.WriteLine($"[FastFlagManager::SetValue] Setting of '{key}' from '{Prop[key]}' to '{value}' is pending");
|
App.Logger.WriteLine(LOG_IDENT, $"Changing of '{key}' from '{Prop[key]}' to '{value}' is pending");
|
||||||
else
|
else
|
||||||
App.Logger.WriteLine($"[FastFlagManager::SetValue] Setting of '{key}' to '{value}' is pending");
|
App.Logger.WriteLine(LOG_IDENT, $"Setting of '{key}' to '{value}' is pending");
|
||||||
|
|
||||||
Prop[key] = value.ToString()!;
|
Prop[key] = value.ToString()!;
|
||||||
}
|
}
|
||||||
@ -142,12 +154,6 @@ namespace Bloxstrap
|
|||||||
SetValue(pair.Value, value);
|
SetValue(pair.Value, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetPresetOnce(string key, object? value)
|
|
||||||
{
|
|
||||||
if (GetPreset(key) is null)
|
|
||||||
SetPreset(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetPresetEnum(string prefix, string target, object? value)
|
public void SetPresetEnum(string prefix, string target, object? value)
|
||||||
{
|
{
|
||||||
foreach (var pair in PresetFlags.Where(x => x.Key.StartsWith(prefix)))
|
foreach (var pair in PresetFlags.Where(x => x.Key.StartsWith(prefix)))
|
||||||
@ -175,6 +181,14 @@ namespace Bloxstrap
|
|||||||
return mapping.First().Key;
|
return mapping.First().Key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CheckManualFullscreenPreset()
|
||||||
|
{
|
||||||
|
if (GetPreset("Rendering.Mode.Vulkan") == "True" || GetPreset("Rendering.Mode.OpenGL") == "True")
|
||||||
|
SetPreset("Rendering.ManualFullscreen", null);
|
||||||
|
else
|
||||||
|
SetPreset("Rendering.ManualFullscreen", "False");
|
||||||
|
}
|
||||||
|
|
||||||
public override void Save()
|
public override void Save()
|
||||||
{
|
{
|
||||||
// convert all flag values to strings before saving
|
// convert all flag values to strings before saving
|
||||||
@ -189,11 +203,24 @@ namespace Bloxstrap
|
|||||||
{
|
{
|
||||||
base.Load();
|
base.Load();
|
||||||
|
|
||||||
SetPresetOnce("Rendering.Framerate", 9999);
|
CheckManualFullscreenPreset();
|
||||||
SetPresetOnce("Rendering.Fullscreen", "False");
|
|
||||||
|
|
||||||
SetPresetOnce("Rendering.DPI.Disable", "True");
|
if (GetPreset("Rendering.Framerate") is not null)
|
||||||
SetPresetOnce("Rendering.DPI.Variable", "False");
|
return;
|
||||||
|
|
||||||
|
// set it to be the framerate of the primary display by default
|
||||||
|
|
||||||
|
var screen = Screen.AllScreens.Where(x => x.Primary).Single();
|
||||||
|
var devmode = new DEVMODEW();
|
||||||
|
|
||||||
|
PInvoke.EnumDisplaySettings(screen.DeviceName, ENUM_DISPLAY_SETTINGS_MODE.ENUM_CURRENT_SETTINGS, ref devmode);
|
||||||
|
|
||||||
|
uint framerate = devmode.dmDisplayFrequency;
|
||||||
|
|
||||||
|
if (framerate <= 100)
|
||||||
|
framerate *= 2;
|
||||||
|
|
||||||
|
SetPreset("Rendering.Framerate", framerate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,10 @@ global using System.Threading;
|
|||||||
global using System.Threading.Tasks;
|
global using System.Threading.Tasks;
|
||||||
|
|
||||||
global using Bloxstrap.Enums;
|
global using Bloxstrap.Enums;
|
||||||
|
global using Bloxstrap.Exceptions;
|
||||||
global using Bloxstrap.Extensions;
|
global using Bloxstrap.Extensions;
|
||||||
global using Bloxstrap.Models;
|
global using Bloxstrap.Models;
|
||||||
|
global using Bloxstrap.Models.BloxstrapRPC;
|
||||||
global using Bloxstrap.Models.Attributes;
|
global using Bloxstrap.Models.Attributes;
|
||||||
global using Bloxstrap.Models.RobloxApi;
|
global using Bloxstrap.Models.RobloxApi;
|
||||||
global using Bloxstrap.UI;
|
global using Bloxstrap.UI;
|
||||||
|
@ -9,13 +9,13 @@
|
|||||||
|
|
||||||
protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken)
|
protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[HttpClientLoggingHandler::HttpRequestMessage] {request.Method} {request.RequestUri}");
|
App.Logger.WriteLine("HttpClientLoggingHandler::ProcessRequest", $"{request.Method} {request.RequestUri}");
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken)
|
protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[HttpClientLoggingHandler::HttpResponseMessage] {(int)response.StatusCode} {response.ReasonPhrase} {response.RequestMessage!.RequestUri}");
|
App.Logger.WriteLine("HttpClientLoggingHandler::ProcessResponse", $"{(int)response.StatusCode} {response.ReasonPhrase} {response.RequestMessage!.RequestUri}");
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
232
Bloxstrap/InstallChecker.cs
Normal file
232
Bloxstrap/InstallChecker.cs
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
|
namespace Bloxstrap
|
||||||
|
{
|
||||||
|
internal class InstallChecker : IDisposable
|
||||||
|
{
|
||||||
|
private RegistryKey? _registryKey;
|
||||||
|
private string? _installLocation;
|
||||||
|
|
||||||
|
internal InstallChecker()
|
||||||
|
{
|
||||||
|
_registryKey = Registry.CurrentUser.OpenSubKey($"Software\\{App.ProjectName}", true);
|
||||||
|
|
||||||
|
if (_registryKey is not null)
|
||||||
|
_installLocation = (string?)_registryKey.GetValue("InstallLocation");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Check()
|
||||||
|
{
|
||||||
|
const string LOG_IDENT = "InstallChecker::Check";
|
||||||
|
|
||||||
|
if (_registryKey is null || _installLocation is null)
|
||||||
|
{
|
||||||
|
if (!File.Exists("Settings.json") || !File.Exists("State.json"))
|
||||||
|
{
|
||||||
|
FirstTimeRun();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_installLocation = Path.GetDirectoryName(Paths.Process)!;
|
||||||
|
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, $"Registry key is likely malformed. Setting install location as '{_installLocation}'");
|
||||||
|
|
||||||
|
if (_registryKey is null)
|
||||||
|
_registryKey = Registry.CurrentUser.CreateSubKey($"Software\\{App.ProjectName}");
|
||||||
|
|
||||||
|
_registryKey.SetValue("InstallLocation", _installLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if drive that bloxstrap was installed to was removed from system, or had its drive letter changed
|
||||||
|
|
||||||
|
if (!Directory.Exists(_installLocation))
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, "Could not find install location. Checking if drive has changed...");
|
||||||
|
|
||||||
|
bool driveExists = false;
|
||||||
|
string driveName = _installLocation[..3];
|
||||||
|
string? newDriveName = null;
|
||||||
|
|
||||||
|
foreach (var drive in DriveInfo.GetDrives())
|
||||||
|
{
|
||||||
|
if (drive.Name == driveName)
|
||||||
|
driveExists = true;
|
||||||
|
else if (Directory.Exists(_installLocation.Replace(driveName, drive.Name)))
|
||||||
|
newDriveName = drive.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newDriveName is not null)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, $"Drive has changed from {driveName} to {newDriveName}");
|
||||||
|
|
||||||
|
Controls.ShowMessageBox(
|
||||||
|
$"{App.ProjectName} has detected a drive letter change and has reconfigured its install location from the {driveName} drive to the {newDriveName} drive.\n" +
|
||||||
|
"\n" +
|
||||||
|
$"While {App.ProjectName} will continue to work, it's recommended that you change the drive leter back to its original value as other installed applications can experience similar issues.",
|
||||||
|
MessageBoxImage.Warning,
|
||||||
|
MessageBoxButton.OK
|
||||||
|
);
|
||||||
|
|
||||||
|
_installLocation = _installLocation.Replace(driveName, newDriveName);
|
||||||
|
_registryKey.SetValue("InstallLocation", _installLocation);
|
||||||
|
}
|
||||||
|
else if (!driveExists)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, $"Drive {driveName} does not exist anymore, and has likely been removed");
|
||||||
|
|
||||||
|
var result = Controls.ShowMessageBox(
|
||||||
|
$"{App.ProjectName} was originally installed to the {driveName} drive, but it appears to no longer be present. Would you like to continue and carry out a fresh install?",
|
||||||
|
MessageBoxImage.Warning,
|
||||||
|
MessageBoxButton.OKCancel
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result != MessageBoxResult.OK)
|
||||||
|
App.Terminate();
|
||||||
|
|
||||||
|
FirstTimeRun();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, "Drive has not changed, folder was likely moved or deleted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App.BaseDirectory = _installLocation;
|
||||||
|
App.IsFirstRun = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_registryKey?.Dispose();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FirstTimeRun()
|
||||||
|
{
|
||||||
|
const string LOG_IDENT = "InstallChecker::FirstTimeRun";
|
||||||
|
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, "Running first-time install");
|
||||||
|
|
||||||
|
App.BaseDirectory = Path.Combine(Paths.LocalAppData, App.ProjectName);
|
||||||
|
App.Logger.Initialize(true);
|
||||||
|
|
||||||
|
if (App.IsQuiet)
|
||||||
|
return;
|
||||||
|
|
||||||
|
App.IsSetupComplete = false;
|
||||||
|
|
||||||
|
App.FastFlags.Load();
|
||||||
|
Controls.ShowMenu();
|
||||||
|
|
||||||
|
// exit if we don't click the install button on installation
|
||||||
|
if (App.IsSetupComplete)
|
||||||
|
return;
|
||||||
|
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, "Installation cancelled!");
|
||||||
|
App.Terminate(ErrorCode.ERROR_CANCELLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void CheckUpgrade()
|
||||||
|
{
|
||||||
|
const string LOG_IDENT = "InstallChecker::CheckUpgrade";
|
||||||
|
|
||||||
|
if (!File.Exists(Paths.Application) || Paths.Process == Paths.Application)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 2.0.0 downloads updates to <BaseFolder>/Updates so lol
|
||||||
|
bool isAutoUpgrade = Paths.Process.StartsWith(Path.Combine(Paths.Base, "Updates")) || Paths.Process.StartsWith(Path.Combine(Paths.LocalAppData, "Temp"));
|
||||||
|
|
||||||
|
FileVersionInfo existingVersionInfo = FileVersionInfo.GetVersionInfo(Paths.Application);
|
||||||
|
FileVersionInfo currentVersionInfo = FileVersionInfo.GetVersionInfo(Paths.Process);
|
||||||
|
|
||||||
|
if (MD5Hash.FromFile(Paths.Process) == MD5Hash.FromFile(Paths.Application))
|
||||||
|
return;
|
||||||
|
|
||||||
|
MessageBoxResult result;
|
||||||
|
|
||||||
|
// silently upgrade version if the command line flag is set or if we're launching from an auto update
|
||||||
|
if (App.IsUpgrade || isAutoUpgrade)
|
||||||
|
{
|
||||||
|
result = MessageBoxResult.Yes;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = Controls.ShowMessageBox(
|
||||||
|
$"The version of {App.ProjectName} you've launched is different to the version you currently have installed.\nWould you like to upgrade your currently installed version?",
|
||||||
|
MessageBoxImage.Question,
|
||||||
|
MessageBoxButton.YesNo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != MessageBoxResult.Yes)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Filesystem.AssertReadOnly(Paths.Application);
|
||||||
|
|
||||||
|
// yes, this is EXTREMELY hacky, but the updater process that launched the
|
||||||
|
// new version may still be open and so we have to wait for it to close
|
||||||
|
int attempts = 0;
|
||||||
|
while (attempts < 10)
|
||||||
|
{
|
||||||
|
attempts++;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(Paths.Application);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
if (attempts == 1)
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, "Waiting for write permissions to update version");
|
||||||
|
|
||||||
|
Thread.Sleep(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attempts == 10)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, "Failed to update! (Could not get write permissions after 5 seconds)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File.Copy(Paths.Process, Paths.Application);
|
||||||
|
|
||||||
|
Bootstrapper.Register();
|
||||||
|
|
||||||
|
// update migrations
|
||||||
|
|
||||||
|
if (App.BuildMetadata.CommitRef.StartsWith("tag") && existingVersionInfo.ProductVersion == "2.4.0")
|
||||||
|
{
|
||||||
|
App.FastFlags.SetValue("DFFlagDisableDPIScale", null);
|
||||||
|
App.FastFlags.SetValue("DFFlagVariableDPIScale2", null);
|
||||||
|
App.FastFlags.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAutoUpgrade)
|
||||||
|
{
|
||||||
|
App.NotifyIcon?.ShowAlert(
|
||||||
|
$"{App.ProjectName} has been upgraded to v{currentVersionInfo.ProductVersion}",
|
||||||
|
"See what's new in this version",
|
||||||
|
30,
|
||||||
|
(_, _) => Utilities.ShellExecute($"https://github.com/{App.ProjectRepository}/releases/tag/v{currentVersionInfo.ProductVersion}")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (!App.IsQuiet)
|
||||||
|
{
|
||||||
|
Controls.ShowMessageBox(
|
||||||
|
$"{App.ProjectName} has been upgraded to v{currentVersionInfo.ProductVersion}",
|
||||||
|
MessageBoxImage.Information,
|
||||||
|
MessageBoxButton.OK
|
||||||
|
);
|
||||||
|
|
||||||
|
Controls.ShowMenu();
|
||||||
|
|
||||||
|
App.Terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
namespace Bloxstrap
|
namespace Bloxstrap.Integrations
|
||||||
{
|
{
|
||||||
public class RobloxActivity : IDisposable
|
public class ActivityWatcher : IDisposable
|
||||||
{
|
{
|
||||||
// i'm thinking the functionality for parsing roblox logs could be broadened for more features than just rich presence,
|
// i'm thinking the functionality for parsing roblox logs could be broadened for more features than just rich presence,
|
||||||
// like checking the ping and region of the current connected server. maybe that's something to add?
|
// like checking the ping and region of the current connected server. maybe that's something to add?
|
||||||
@ -11,7 +11,7 @@
|
|||||||
private const string GameJoinedEntry = "[FLog::Network] serverId:";
|
private const string GameJoinedEntry = "[FLog::Network] serverId:";
|
||||||
private const string GameDisconnectedEntry = "[FLog::Network] Time to disconnect replication data:";
|
private const string GameDisconnectedEntry = "[FLog::Network] Time to disconnect replication data:";
|
||||||
private const string GameTeleportingEntry = "[FLog::SingleSurfaceApp] initiateTeleport";
|
private const string GameTeleportingEntry = "[FLog::SingleSurfaceApp] initiateTeleport";
|
||||||
private const string GameMessageEntry = "[FLog::Output] [SendBloxstrapMessage]";
|
private const string GameMessageEntry = "[FLog::Output] [BloxstrapRPC]";
|
||||||
|
|
||||||
private const string GameJoiningEntryPattern = @"! Joining game '([0-9a-f\-]{36})' place ([0-9]+) at ([0-9\.]+)";
|
private const string GameJoiningEntryPattern = @"! Joining game '([0-9a-f\-]{36})' place ([0-9]+) at ([0-9\.]+)";
|
||||||
private const string GameJoiningUDMUXPattern = @"UDMUX Address = ([0-9\.]+), Port = [0-9]+ \| RCC Server Address = ([0-9\.]+), Port = [0-9]+";
|
private const string GameJoiningUDMUXPattern = @"UDMUX Address = ([0-9\.]+), Port = [0-9]+ \| RCC Server Address = ([0-9\.]+), Port = [0-9]+";
|
||||||
@ -24,9 +24,10 @@
|
|||||||
public event EventHandler<string>? OnLogEntry;
|
public event EventHandler<string>? OnLogEntry;
|
||||||
public event EventHandler? OnGameJoin;
|
public event EventHandler? OnGameJoin;
|
||||||
public event EventHandler? OnGameLeave;
|
public event EventHandler? OnGameLeave;
|
||||||
public event EventHandler<GameMessage>? OnGameMessage;
|
public event EventHandler<Message>? OnRPCMessage;
|
||||||
|
|
||||||
private Dictionary<string, string> GeolcationCache = new();
|
private readonly Dictionary<string, string> GeolocationCache = new();
|
||||||
|
private DateTime LastRPCRequest;
|
||||||
|
|
||||||
public string LogLocation = null!;
|
public string LogLocation = null!;
|
||||||
|
|
||||||
@ -44,6 +45,8 @@
|
|||||||
|
|
||||||
public async void StartWatcher()
|
public async void StartWatcher()
|
||||||
{
|
{
|
||||||
|
const string LOG_IDENT = "ActivityWatcher::StartWatcher";
|
||||||
|
|
||||||
// okay, here's the process:
|
// okay, here's the process:
|
||||||
//
|
//
|
||||||
// - tail the latest log file from %localappdata%\roblox\logs
|
// - tail the latest log file from %localappdata%\roblox\logs
|
||||||
@ -60,7 +63,7 @@
|
|||||||
if (App.Settings.Prop.OhHeyYouFoundMe)
|
if (App.Settings.Prop.OhHeyYouFoundMe)
|
||||||
delay = 250;
|
delay = 250;
|
||||||
|
|
||||||
string logDirectory = Path.Combine(Directories.LocalAppData, "Roblox\\logs");
|
string logDirectory = Path.Combine(Paths.LocalAppData, "Roblox\\logs");
|
||||||
|
|
||||||
if (!Directory.Exists(logDirectory))
|
if (!Directory.Exists(logDirectory))
|
||||||
return;
|
return;
|
||||||
@ -71,7 +74,7 @@
|
|||||||
// if roblox doesn't start quickly enough, we can wind up fetching the previous log file
|
// if roblox doesn't start quickly enough, we can wind up fetching the previous log file
|
||||||
// good rule of thumb is to find a log file that was created in the last 15 seconds or so
|
// good rule of thumb is to find a log file that was created in the last 15 seconds or so
|
||||||
|
|
||||||
App.Logger.WriteLine("[RobloxActivity::StartWatcher] Opening Roblox log file...");
|
App.Logger.WriteLine(LOG_IDENT, "Opening Roblox log file...");
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@ -84,13 +87,13 @@
|
|||||||
if (logFileInfo.CreationTime.AddSeconds(15) > DateTime.Now)
|
if (logFileInfo.CreationTime.AddSeconds(15) > DateTime.Now)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
App.Logger.WriteLine($"[RobloxActivity::StartWatcher] Could not find recent enough log file, waiting... (newest is {logFileInfo.Name})");
|
App.Logger.WriteLine(LOG_IDENT, $"Could not find recent enough log file, waiting... (newest is {logFileInfo.Name})");
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
LogLocation = logFileInfo.FullName;
|
LogLocation = logFileInfo.FullName;
|
||||||
FileStream logFileStream = logFileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
FileStream logFileStream = logFileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||||
App.Logger.WriteLine($"[RobloxActivity::StartWatcher] Opened {LogLocation}");
|
App.Logger.WriteLine(LOG_IDENT, $"Opened {LogLocation}");
|
||||||
|
|
||||||
AutoResetEvent logUpdatedEvent = new(false);
|
AutoResetEvent logUpdatedEvent = new(false);
|
||||||
FileSystemWatcher logWatcher = new()
|
FileSystemWatcher logWatcher = new()
|
||||||
@ -116,6 +119,8 @@
|
|||||||
|
|
||||||
private void ExamineLogEntry(string entry)
|
private void ExamineLogEntry(string entry)
|
||||||
{
|
{
|
||||||
|
const string LOG_IDENT = "ActivityWatcher::ExamineLogEntry";
|
||||||
|
|
||||||
OnLogEntry?.Invoke(this, entry);
|
OnLogEntry?.Invoke(this, entry);
|
||||||
|
|
||||||
_logEntriesRead += 1;
|
_logEntriesRead += 1;
|
||||||
@ -123,9 +128,9 @@
|
|||||||
// debug stats to ensure that the log reader is working correctly
|
// debug stats to ensure that the log reader is working correctly
|
||||||
// if more than 1000 log entries have been read, only log per 100 to save on spam
|
// if more than 1000 log entries have been read, only log per 100 to save on spam
|
||||||
if (_logEntriesRead <= 1000 && _logEntriesRead % 50 == 0)
|
if (_logEntriesRead <= 1000 && _logEntriesRead % 50 == 0)
|
||||||
App.Logger.WriteLine($"[RobloxActivity::ExamineLogEntry] Read {_logEntriesRead} log entries");
|
App.Logger.WriteLine(LOG_IDENT, $"Read {_logEntriesRead} log entries");
|
||||||
else if (_logEntriesRead % 100 == 0)
|
else if (_logEntriesRead % 100 == 0)
|
||||||
App.Logger.WriteLine($"[RobloxActivity::ExamineLogEntry] Read {_logEntriesRead} log entries");
|
App.Logger.WriteLine(LOG_IDENT, $"Read {_logEntriesRead} log entries");
|
||||||
|
|
||||||
if (!ActivityInGame && ActivityPlaceId == 0)
|
if (!ActivityInGame && ActivityPlaceId == 0)
|
||||||
{
|
{
|
||||||
@ -140,8 +145,8 @@
|
|||||||
|
|
||||||
if (match.Groups.Count != 4)
|
if (match.Groups.Count != 4)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[RobloxActivity::ExamineLogEntry] Failed to assert format for game join entry");
|
App.Logger.WriteLine(LOG_IDENT, $"Failed to assert format for game join entry");
|
||||||
App.Logger.WriteLine(entry);
|
App.Logger.WriteLine(LOG_IDENT, entry);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +167,7 @@
|
|||||||
_reservedTeleportMarker = false;
|
_reservedTeleportMarker = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
App.Logger.WriteLine($"[RobloxActivity::ExamineLogEntry] Joining Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
|
App.Logger.WriteLine(LOG_IDENT, $"Joining Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!ActivityInGame && ActivityPlaceId != 0)
|
else if (!ActivityInGame && ActivityPlaceId != 0)
|
||||||
@ -173,15 +178,15 @@
|
|||||||
|
|
||||||
if (match.Groups.Count != 3 || match.Groups[2].Value != ActivityMachineAddress)
|
if (match.Groups.Count != 3 || match.Groups[2].Value != ActivityMachineAddress)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[RobloxActivity::ExamineLogEntry] Failed to assert format for game join UDMUX entry");
|
App.Logger.WriteLine(LOG_IDENT, $"Failed to assert format for game join UDMUX entry");
|
||||||
App.Logger.WriteLine(entry);
|
App.Logger.WriteLine(LOG_IDENT, entry);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ActivityMachineAddress = match.Groups[1].Value;
|
ActivityMachineAddress = match.Groups[1].Value;
|
||||||
ActivityMachineUDMUX = true;
|
ActivityMachineUDMUX = true;
|
||||||
|
|
||||||
App.Logger.WriteLine($"[RobloxActivity::ExamineLogEntry] Server is UDMUX protected ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
|
App.Logger.WriteLine(LOG_IDENT, $"Server is UDMUX protected ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
|
||||||
}
|
}
|
||||||
else if (entry.Contains(GameJoinedEntry))
|
else if (entry.Contains(GameJoinedEntry))
|
||||||
{
|
{
|
||||||
@ -189,12 +194,12 @@
|
|||||||
|
|
||||||
if (match.Groups.Count != 2 || match.Groups[1].Value != ActivityMachineAddress)
|
if (match.Groups.Count != 2 || match.Groups[1].Value != ActivityMachineAddress)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[RobloxActivity::ExamineLogEntry] Failed to assert format for game joined entry");
|
App.Logger.WriteLine(LOG_IDENT, $"Failed to assert format for game joined entry");
|
||||||
App.Logger.WriteLine(entry);
|
App.Logger.WriteLine(LOG_IDENT, entry);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
App.Logger.WriteLine($"[RobloxActivity::ExamineLogEntry] Joined Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
|
App.Logger.WriteLine(LOG_IDENT, $"Joined Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
|
||||||
|
|
||||||
ActivityInGame = true;
|
ActivityInGame = true;
|
||||||
OnGameJoin?.Invoke(this, new EventArgs());
|
OnGameJoin?.Invoke(this, new EventArgs());
|
||||||
@ -204,7 +209,7 @@
|
|||||||
{
|
{
|
||||||
if (entry.Contains(GameDisconnectedEntry))
|
if (entry.Contains(GameDisconnectedEntry))
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[RobloxActivity::ExamineLogEntry] Disconnected from Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
|
App.Logger.WriteLine(LOG_IDENT, $"Disconnected from Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
|
||||||
|
|
||||||
ActivityInGame = false;
|
ActivityInGame = false;
|
||||||
ActivityPlaceId = 0;
|
ActivityPlaceId = 0;
|
||||||
@ -218,7 +223,7 @@
|
|||||||
}
|
}
|
||||||
else if (entry.Contains(GameTeleportingEntry))
|
else if (entry.Contains(GameTeleportingEntry))
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[RobloxActivity::ExamineLogEntry] Initiating teleport to server ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
|
App.Logger.WriteLine(LOG_IDENT, $"Initiating teleport to server ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
|
||||||
_teleportMarker = true;
|
_teleportMarker = true;
|
||||||
}
|
}
|
||||||
else if (_teleportMarker && entry.Contains(GameJoiningReservedServerEntry))
|
else if (_teleportMarker && entry.Contains(GameJoiningReservedServerEntry))
|
||||||
@ -229,60 +234,83 @@
|
|||||||
else if (entry.Contains(GameMessageEntry))
|
else if (entry.Contains(GameMessageEntry))
|
||||||
{
|
{
|
||||||
string messagePlain = entry.Substring(entry.IndexOf(GameMessageEntry) + GameMessageEntry.Length + 1);
|
string messagePlain = entry.Substring(entry.IndexOf(GameMessageEntry) + GameMessageEntry.Length + 1);
|
||||||
GameMessage? message;
|
Message? message;
|
||||||
|
|
||||||
App.Logger.WriteLine($"[RobloxActivity::ExamineLogEntry] Received message: '{messagePlain}'");
|
App.Logger.WriteLine(LOG_IDENT, $"Received message: '{messagePlain}'");
|
||||||
|
|
||||||
|
if ((DateTime.Now - LastRPCRequest).TotalSeconds <= 1)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, "Dropping message as ratelimit has been hit");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
message = JsonSerializer.Deserialize<GameMessage>(messagePlain);
|
message = JsonSerializer.Deserialize<Message>(messagePlain);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[Utilities::ExamineLogEntry] Failed to parse message! (JSON deserialization threw an exception)");
|
App.Logger.WriteLine(LOG_IDENT, "Failed to parse message! (JSON deserialization threw an exception)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message is null)
|
if (message is null)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[Utilities::ExamineLogEntry] Failed to parse message! (JSON deserialization returned null)");
|
App.Logger.WriteLine(LOG_IDENT, "Failed to parse message! (JSON deserialization returned null)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (String.IsNullOrEmpty(message.Command))
|
if (string.IsNullOrEmpty(message.Command))
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[Utilities::ExamineLogEntry] Failed to parse message! (Command is empty)");
|
App.Logger.WriteLine(LOG_IDENT, "Failed to parse message! (Command is empty)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
OnGameMessage?.Invoke(this, message);
|
OnRPCMessage?.Invoke(this, message);
|
||||||
|
|
||||||
|
LastRPCRequest = DateTime.Now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetServerLocation()
|
public async Task<string> GetServerLocation()
|
||||||
{
|
{
|
||||||
if (GeolcationCache.ContainsKey(ActivityMachineAddress))
|
const string LOG_IDENT = "ActivityWatcher::GetServerLocation";
|
||||||
return GeolcationCache[ActivityMachineAddress];
|
|
||||||
|
|
||||||
string location = "";
|
if (GeolocationCache.ContainsKey(ActivityMachineAddress))
|
||||||
|
return GeolocationCache[ActivityMachineAddress];
|
||||||
|
|
||||||
string locationCity = await App.HttpClient.GetStringAsync($"https://ipinfo.io/{ActivityMachineAddress}/city");
|
string location, locationCity, locationRegion, locationCountry = "";
|
||||||
string locationRegion = await App.HttpClient.GetStringAsync($"https://ipinfo.io/{ActivityMachineAddress}/region");
|
|
||||||
string locationCountry = await App.HttpClient.GetStringAsync($"https://ipinfo.io/{ActivityMachineAddress}/country");
|
try
|
||||||
|
{
|
||||||
|
locationCity = await App.HttpClient.GetStringAsync($"https://ipinfo.io/{ActivityMachineAddress}/city");
|
||||||
|
locationRegion = await App.HttpClient.GetStringAsync($"https://ipinfo.io/{ActivityMachineAddress}/region");
|
||||||
|
locationCountry = await App.HttpClient.GetStringAsync($"https://ipinfo.io/{ActivityMachineAddress}/country");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, $"Failed to get server location for {ActivityMachineAddress}");
|
||||||
|
App.Logger.WriteException(LOG_IDENT, ex);
|
||||||
|
|
||||||
|
return "N/A (lookup failed)";
|
||||||
|
}
|
||||||
|
|
||||||
locationCity = locationCity.ReplaceLineEndings("");
|
locationCity = locationCity.ReplaceLineEndings("");
|
||||||
locationRegion = locationRegion.ReplaceLineEndings("");
|
locationRegion = locationRegion.ReplaceLineEndings("");
|
||||||
locationCountry = locationCountry.ReplaceLineEndings("");
|
locationCountry = locationCountry.ReplaceLineEndings("");
|
||||||
|
|
||||||
if (String.IsNullOrEmpty(locationCountry))
|
if (string.IsNullOrEmpty(locationCountry))
|
||||||
location = "N/A";
|
location = "N/A";
|
||||||
else if (locationCity == locationRegion)
|
else if (locationCity == locationRegion)
|
||||||
location = $"{locationRegion}, {locationCountry}";
|
location = $"{locationRegion}, {locationCountry}";
|
||||||
else
|
else
|
||||||
location = $"{locationCity}, {locationRegion}, {locationCountry}";
|
location = $"{locationCity}, {locationRegion}, {locationCountry}";
|
||||||
|
|
||||||
GeolcationCache[ActivityMachineAddress] = location;
|
if (!ActivityInGame)
|
||||||
|
return "N/A (left game)";
|
||||||
|
|
||||||
|
GeolocationCache[ActivityMachineAddress] = location;
|
||||||
|
|
||||||
return location;
|
return location;
|
||||||
}
|
}
|
@ -5,94 +5,158 @@ namespace Bloxstrap.Integrations
|
|||||||
public class DiscordRichPresence : IDisposable
|
public class DiscordRichPresence : IDisposable
|
||||||
{
|
{
|
||||||
private readonly DiscordRpcClient _rpcClient = new("1005469189907173486");
|
private readonly DiscordRpcClient _rpcClient = new("1005469189907173486");
|
||||||
private readonly RobloxActivity _activityWatcher;
|
private readonly ActivityWatcher _activityWatcher;
|
||||||
|
|
||||||
|
private DiscordRPC.RichPresence? _currentPresence;
|
||||||
|
private DiscordRPC.RichPresence? _currentPresenceCopy;
|
||||||
|
|
||||||
private RichPresence? _currentPresence;
|
|
||||||
private bool _visible = true;
|
private bool _visible = true;
|
||||||
private string? _initialStatus;
|
|
||||||
private long _currentUniverseId;
|
private long _currentUniverseId;
|
||||||
private DateTime? _timeStartedUniverse;
|
private DateTime? _timeStartedUniverse;
|
||||||
|
|
||||||
public DiscordRichPresence(RobloxActivity activityWatcher)
|
public DiscordRichPresence(ActivityWatcher activityWatcher)
|
||||||
{
|
{
|
||||||
|
const string LOG_IDENT = "DiscordRichPresence::DiscordRichPresence";
|
||||||
|
|
||||||
_activityWatcher = activityWatcher;
|
_activityWatcher = activityWatcher;
|
||||||
|
|
||||||
_activityWatcher.OnGameJoin += (_, _) => Task.Run(() => SetCurrentGame());
|
_activityWatcher.OnGameJoin += (_, _) => Task.Run(() => SetCurrentGame());
|
||||||
_activityWatcher.OnGameLeave += (_, _) => Task.Run(() => SetCurrentGame());
|
_activityWatcher.OnGameLeave += (_, _) => Task.Run(() => SetCurrentGame());
|
||||||
_activityWatcher.OnGameMessage += (_, message) => OnGameMessage(message);
|
_activityWatcher.OnRPCMessage += (_, message) => ProcessRPCMessage(message);
|
||||||
|
|
||||||
_rpcClient.OnReady += (_, e) =>
|
_rpcClient.OnReady += (_, e) =>
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::DiscordRichPresence] Received ready from user {e.User.Username} ({e.User.ID})");
|
App.Logger.WriteLine(LOG_IDENT, $"Received ready from user {e.User} ({e.User.ID})");
|
||||||
|
|
||||||
_rpcClient.OnPresenceUpdate += (_, e) =>
|
_rpcClient.OnPresenceUpdate += (_, e) =>
|
||||||
App.Logger.WriteLine("[DiscordRichPresence::DiscordRichPresence] Presence updated");
|
App.Logger.WriteLine(LOG_IDENT, "Presence updated");
|
||||||
|
|
||||||
_rpcClient.OnError += (_, e) =>
|
_rpcClient.OnError += (_, e) =>
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::DiscordRichPresence] An RPC error occurred - {e.Message}");
|
App.Logger.WriteLine(LOG_IDENT, $"An RPC error occurred - {e.Message}");
|
||||||
|
|
||||||
_rpcClient.OnConnectionEstablished += (_, e) =>
|
_rpcClient.OnConnectionEstablished += (_, e) =>
|
||||||
App.Logger.WriteLine("[DiscordRichPresence::DiscordRichPresence] Established connection with Discord RPC");
|
App.Logger.WriteLine(LOG_IDENT, "Established connection with Discord RPC");
|
||||||
|
|
||||||
//spams log as it tries to connect every ~15 sec when discord is closed so not now
|
//spams log as it tries to connect every ~15 sec when discord is closed so not now
|
||||||
//_rpcClient.OnConnectionFailed += (_, e) =>
|
//_rpcClient.OnConnectionFailed += (_, e) =>
|
||||||
// App.Logger.WriteLine("[DiscordRichPresence::DiscordRichPresence] Failed to establish connection with Discord RPC");
|
// App.Logger.WriteLine(LOG_IDENT, "Failed to establish connection with Discord RPC");
|
||||||
|
|
||||||
_rpcClient.OnClose += (_, e) =>
|
_rpcClient.OnClose += (_, e) =>
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::DiscordRichPresence] Lost connection to Discord RPC - {e.Reason} ({e.Code})");
|
App.Logger.WriteLine(LOG_IDENT, $"Lost connection to Discord RPC - {e.Reason} ({e.Code})");
|
||||||
|
|
||||||
_rpcClient.Initialize();
|
_rpcClient.Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnGameMessage(GameMessage message)
|
public void ProcessRPCMessage(Message message)
|
||||||
{
|
{
|
||||||
if (message.Command == "SetPresenceStatus")
|
const string LOG_IDENT = "DiscordRichPresence::ProcessRPCMessage";
|
||||||
SetStatus(message.Data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetStatus(string status)
|
if (message.Command != "SetRichPresence")
|
||||||
{
|
return;
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetStatus] Setting status to '{status}'");
|
|
||||||
|
|
||||||
if (_currentPresence is null)
|
if (_currentPresence is null || _currentPresenceCopy is null)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetStatus] Presence is not set, aborting");
|
App.Logger.WriteLine(LOG_IDENT, "Presence is not set, aborting");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.Length > 128)
|
Models.BloxstrapRPC.RichPresence? presenceData;
|
||||||
|
|
||||||
|
// a lot of repeated code here, could this somehow be cleaned up?
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetStatus] Status cannot be longer than 128 characters, aborting");
|
presenceData = message.Data.Deserialize<Models.BloxstrapRPC.RichPresence>();
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, "Failed to parse message! (JSON deserialization threw an exception)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_initialStatus is null)
|
if (presenceData is null)
|
||||||
_initialStatus = _currentPresence.State;
|
|
||||||
|
|
||||||
string finalStatus;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(status))
|
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetStatus] Status is empty, reverting to initial status");
|
App.Logger.WriteLine(LOG_IDENT, "Failed to parse message! (JSON deserialization returned null)");
|
||||||
finalStatus = _initialStatus;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (presenceData.Details is not null)
|
||||||
|
{
|
||||||
|
if (presenceData.Details.Length > 128)
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, $"Details cannot be longer than 128 characters");
|
||||||
|
else if (presenceData.Details == "<reset>")
|
||||||
|
_currentPresence.Details = _currentPresenceCopy.Details;
|
||||||
|
else
|
||||||
|
_currentPresence.Details = presenceData.Details;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (presenceData.State is not null)
|
||||||
|
{
|
||||||
|
if (presenceData.State.Length > 128)
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, $"State cannot be longer than 128 characters");
|
||||||
|
else if (presenceData.State == "<reset>")
|
||||||
|
_currentPresence.State = _currentPresenceCopy.State;
|
||||||
|
else
|
||||||
|
_currentPresence.State = presenceData.State;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (presenceData.TimestampStart == 0)
|
||||||
|
_currentPresence.Timestamps.Start = null;
|
||||||
|
else if (presenceData.TimestampStart is not null)
|
||||||
|
_currentPresence.Timestamps.StartUnixMilliseconds = presenceData.TimestampStart * 1000;
|
||||||
|
|
||||||
|
if (presenceData.TimestampEnd == 0)
|
||||||
|
_currentPresence.Timestamps.End = null;
|
||||||
|
else if (presenceData.TimestampEnd is not null)
|
||||||
|
_currentPresence.Timestamps.EndUnixMilliseconds = presenceData.TimestampEnd * 1000;
|
||||||
|
|
||||||
|
if (presenceData.SmallImage is not null)
|
||||||
|
{
|
||||||
|
if (presenceData.SmallImage.Clear)
|
||||||
|
{
|
||||||
|
_currentPresence.Assets.SmallImageKey = "";
|
||||||
|
}
|
||||||
|
else if (presenceData.SmallImage.Reset)
|
||||||
|
{
|
||||||
|
_currentPresence.Assets.SmallImageText = _currentPresenceCopy.Assets.SmallImageText;
|
||||||
|
_currentPresence.Assets.SmallImageKey = _currentPresenceCopy.Assets.SmallImageKey;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
finalStatus = status;
|
if (presenceData.SmallImage.AssetId is not null)
|
||||||
|
_currentPresence.Assets.SmallImageKey = $"https://assetdelivery.roblox.com/v1/asset/?id={presenceData.SmallImage.AssetId}";
|
||||||
|
|
||||||
|
if (presenceData.SmallImage.HoverText is not null)
|
||||||
|
_currentPresence.Assets.SmallImageText = presenceData.SmallImage.HoverText;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_currentPresence.State == finalStatus)
|
if (presenceData.LargeImage is not null)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetStatus] Status is unchanged, aborting");
|
if (presenceData.LargeImage.Clear)
|
||||||
return;
|
{
|
||||||
|
_currentPresence.Assets.LargeImageKey = "";
|
||||||
|
}
|
||||||
|
else if (presenceData.LargeImage.Reset)
|
||||||
|
{
|
||||||
|
_currentPresence.Assets.LargeImageText = _currentPresenceCopy.Assets.LargeImageText;
|
||||||
|
_currentPresence.Assets.LargeImageKey = _currentPresenceCopy.Assets.LargeImageKey;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (presenceData.LargeImage.AssetId is not null)
|
||||||
|
_currentPresence.Assets.LargeImageKey = $"https://assetdelivery.roblox.com/v1/asset/?id={presenceData.LargeImage.AssetId}";
|
||||||
|
|
||||||
|
if (presenceData.LargeImage.HoverText is not null)
|
||||||
|
_currentPresence.Assets.LargeImageText = presenceData.LargeImage.HoverText;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentPresence.State = finalStatus;
|
|
||||||
UpdatePresence();
|
UpdatePresence();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetVisibility(bool visible)
|
public void SetVisibility(bool visible)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetVisibility] Setting presence visibility ({visible})");
|
App.Logger.WriteLine("DiscordRichPresence::SetVisibility", $"Setting presence visibility ({visible})");
|
||||||
|
|
||||||
_visible = visible;
|
_visible = visible;
|
||||||
|
|
||||||
@ -104,11 +168,12 @@ namespace Bloxstrap.Integrations
|
|||||||
|
|
||||||
public async Task<bool> SetCurrentGame()
|
public async Task<bool> SetCurrentGame()
|
||||||
{
|
{
|
||||||
|
const string LOG_IDENT = "DiscordRichPresence::SetCurrentGame";
|
||||||
|
|
||||||
if (!_activityWatcher.ActivityInGame)
|
if (!_activityWatcher.ActivityInGame)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Not in game, clearing presence");
|
App.Logger.WriteLine(LOG_IDENT, "Not in game, clearing presence");
|
||||||
_currentPresence = null;
|
_currentPresence = _currentPresenceCopy = null;
|
||||||
_initialStatus = null;
|
|
||||||
UpdatePresence();
|
UpdatePresence();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -116,17 +181,17 @@ namespace Bloxstrap.Integrations
|
|||||||
string icon = "roblox";
|
string icon = "roblox";
|
||||||
long placeId = _activityWatcher.ActivityPlaceId;
|
long placeId = _activityWatcher.ActivityPlaceId;
|
||||||
|
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Setting presence for Place ID {placeId}");
|
App.Logger.WriteLine(LOG_IDENT, $"Setting presence for Place ID {placeId}");
|
||||||
|
|
||||||
var universeIdResponse = await Http.GetJson<UniverseIdResponse>($"https://apis.roblox.com/universes/v1/places/{placeId}/universe");
|
var universeIdResponse = await Http.GetJson<UniverseIdResponse>($"https://apis.roblox.com/universes/v1/places/{placeId}/universe");
|
||||||
if (universeIdResponse is null)
|
if (universeIdResponse is null)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Could not get Universe ID!");
|
App.Logger.WriteLine(LOG_IDENT, "Could not get Universe ID!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
long universeId = universeIdResponse.UniverseId;
|
long universeId = universeIdResponse.UniverseId;
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Got Universe ID as {universeId}");
|
App.Logger.WriteLine(LOG_IDENT, $"Got Universe ID as {universeId}");
|
||||||
|
|
||||||
// preserve time spent playing if we're teleporting between places in the same universe
|
// preserve time spent playing if we're teleporting between places in the same universe
|
||||||
if (_timeStartedUniverse is null || !_activityWatcher.ActivityIsTeleport || universeId != _currentUniverseId)
|
if (_timeStartedUniverse is null || !_activityWatcher.ActivityIsTeleport || universeId != _currentUniverseId)
|
||||||
@ -137,22 +202,22 @@ namespace Bloxstrap.Integrations
|
|||||||
var gameDetailResponse = await Http.GetJson<ApiArrayResponse<GameDetailResponse>>($"https://games.roblox.com/v1/games?universeIds={universeId}");
|
var gameDetailResponse = await Http.GetJson<ApiArrayResponse<GameDetailResponse>>($"https://games.roblox.com/v1/games?universeIds={universeId}");
|
||||||
if (gameDetailResponse is null || !gameDetailResponse.Data.Any())
|
if (gameDetailResponse is null || !gameDetailResponse.Data.Any())
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Could not get Universe info!");
|
App.Logger.WriteLine(LOG_IDENT, "Could not get Universe info!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameDetailResponse universeDetails = gameDetailResponse.Data.ToArray()[0];
|
GameDetailResponse universeDetails = gameDetailResponse.Data.ToArray()[0];
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Got Universe details");
|
App.Logger.WriteLine(LOG_IDENT, "Got Universe details");
|
||||||
|
|
||||||
var universeThumbnailResponse = await Http.GetJson<ApiArrayResponse<ThumbnailResponse>>($"https://thumbnails.roblox.com/v1/games/icons?universeIds={universeId}&returnPolicy=PlaceHolder&size=512x512&format=Png&isCircular=false");
|
var universeThumbnailResponse = await Http.GetJson<ApiArrayResponse<ThumbnailResponse>>($"https://thumbnails.roblox.com/v1/games/icons?universeIds={universeId}&returnPolicy=PlaceHolder&size=512x512&format=Png&isCircular=false");
|
||||||
if (universeThumbnailResponse is null || !universeThumbnailResponse.Data.Any())
|
if (universeThumbnailResponse is null || !universeThumbnailResponse.Data.Any())
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Could not get Universe thumbnail info!");
|
App.Logger.WriteLine(LOG_IDENT, "Could not get Universe thumbnail info!");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
icon = universeThumbnailResponse.Data.ToArray()[0].ImageUrl;
|
icon = universeThumbnailResponse.Data.ToArray()[0].ImageUrl;
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Got Universe thumbnail as {icon}");
|
App.Logger.WriteLine(LOG_IDENT, $"Got Universe thumbnail as {icon}");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Button> buttons = new();
|
List<Button> buttons = new();
|
||||||
@ -174,7 +239,7 @@ namespace Bloxstrap.Integrations
|
|||||||
|
|
||||||
if (!_activityWatcher.ActivityInGame || placeId != _activityWatcher.ActivityPlaceId)
|
if (!_activityWatcher.ActivityInGame || placeId != _activityWatcher.ActivityPlaceId)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Aborting presence set because game activity has changed");
|
App.Logger.WriteLine(LOG_IDENT, "Aborting presence set because game activity has changed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,7 +250,7 @@ namespace Bloxstrap.Integrations
|
|||||||
_ => $"by {universeDetails.Creator.Name}" + (universeDetails.Creator.HasVerifiedBadge ? " ☑️" : ""),
|
_ => $"by {universeDetails.Creator.Name}" + (universeDetails.Creator.HasVerifiedBadge ? " ☑️" : ""),
|
||||||
};
|
};
|
||||||
|
|
||||||
_currentPresence = new RichPresence
|
_currentPresence = new DiscordRPC.RichPresence
|
||||||
{
|
{
|
||||||
Details = $"Playing {universeDetails.Name}",
|
Details = $"Playing {universeDetails.Name}",
|
||||||
State = status,
|
State = status,
|
||||||
@ -200,6 +265,9 @@ namespace Bloxstrap.Integrations
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// this is used for configuration from BloxstrapRPC
|
||||||
|
_currentPresenceCopy = _currentPresence.Clone();
|
||||||
|
|
||||||
UpdatePresence();
|
UpdatePresence();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -207,14 +275,16 @@ namespace Bloxstrap.Integrations
|
|||||||
|
|
||||||
public void UpdatePresence()
|
public void UpdatePresence()
|
||||||
{
|
{
|
||||||
|
const string LOG_IDENT = "DiscordRichPresence::UpdatePresence";
|
||||||
|
|
||||||
if (_currentPresence is null)
|
if (_currentPresence is null)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::UpdatePresence] Presence is empty, clearing");
|
App.Logger.WriteLine(LOG_IDENT, $"Presence is empty, clearing");
|
||||||
_rpcClient.ClearPresence();
|
_rpcClient.ClearPresence();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
App.Logger.WriteLine($"[DiscordRichPresence::UpdatePresence] Updating presence");
|
App.Logger.WriteLine(LOG_IDENT, $"Updating presence");
|
||||||
|
|
||||||
if (_visible)
|
if (_visible)
|
||||||
_rpcClient.SetPresence(_currentPresence);
|
_rpcClient.SetPresence(_currentPresence);
|
||||||
@ -222,7 +292,7 @@ namespace Bloxstrap.Integrations
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine("[DiscordRichPresence::Dispose] Cleaning up Discord RPC and Presence");
|
App.Logger.WriteLine("DiscordRichPresence::Dispose", "Cleaning up Discord RPC and Presence");
|
||||||
_rpcClient.ClearPresence();
|
_rpcClient.ClearPresence();
|
||||||
_rpcClient.Dispose();
|
_rpcClient.Dispose();
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
namespace Bloxstrap
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Bloxstrap
|
||||||
{
|
{
|
||||||
public class JsonManager<T> where T : new()
|
public class JsonManager<T> where T : new()
|
||||||
{
|
{
|
||||||
public T Prop { get; set; } = new();
|
public T Prop { get; set; } = new();
|
||||||
public virtual string FileLocation => Path.Combine(Directories.Base, $"{typeof(T).Name}.json");
|
public virtual string FileLocation => Path.Combine(Paths.Base, $"{typeof(T).Name}.json");
|
||||||
|
|
||||||
|
private string LOG_IDENT_CLASS => $"JsonManager<{typeof(T).Name}>";
|
||||||
|
|
||||||
public virtual void Load()
|
public virtual void Load()
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Load] Loading from {FileLocation}...");
|
string LOG_IDENT = $"{LOG_IDENT_CLASS}::Load";
|
||||||
|
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, $"Loading from {FileLocation}...");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -18,28 +24,31 @@
|
|||||||
|
|
||||||
Prop = settings;
|
Prop = settings;
|
||||||
|
|
||||||
App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Load] Loaded successfully!");
|
App.Logger.WriteLine(LOG_IDENT, "Loaded successfully!");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Load] Failed to load! ({ex.Message})");
|
App.Logger.WriteLine(LOG_IDENT, "Failed to load!");
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, $"{ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Save()
|
public virtual void Save()
|
||||||
{
|
{
|
||||||
|
string LOG_IDENT = $"{LOG_IDENT_CLASS}::Save";
|
||||||
|
|
||||||
if (!App.ShouldSaveConfigs)
|
if (!App.ShouldSaveConfigs)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Save] Save request ignored");
|
App.Logger.WriteLine(LOG_IDENT, "Save request ignored");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Save] Saving to {FileLocation}...");
|
App.Logger.WriteLine(LOG_IDENT, $"Saving to {FileLocation}...");
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(FileLocation)!);
|
Directory.CreateDirectory(Path.GetDirectoryName(FileLocation)!);
|
||||||
File.WriteAllText(FileLocation, JsonSerializer.Serialize(Prop, new JsonSerializerOptions { WriteIndented = true }));
|
File.WriteAllText(FileLocation, JsonSerializer.Serialize(Prop, new JsonSerializerOptions { WriteIndented = true }));
|
||||||
|
|
||||||
App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Save] Save complete!");
|
App.Logger.WriteLine(LOG_IDENT, "Save complete!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
namespace Bloxstrap
|
namespace Bloxstrap
|
||||||
{
|
{
|
||||||
// https://stackoverflow.com/a/53873141/11852173
|
// https://stackoverflow.com/a/53873141/11852173
|
||||||
// TODO - this kind of sucks
|
|
||||||
// the main problem is just that this doesn't finish writing log entries before exiting the program
|
|
||||||
// this can be solved by making writetolog completely synchronous, but while it doesn't affect performance, its's not ideal
|
|
||||||
// also, writing and flushing for every single line that's written may not be great
|
|
||||||
|
|
||||||
public class Logger
|
public class Logger
|
||||||
{
|
{
|
||||||
@ -17,16 +13,18 @@
|
|||||||
|
|
||||||
public void Initialize(bool useTempDir = false)
|
public void Initialize(bool useTempDir = false)
|
||||||
{
|
{
|
||||||
string directory = useTempDir ? Path.Combine(Directories.LocalAppData, "Temp") : Path.Combine(Directories.Base, "Logs");
|
const string LOG_IDENT = "Logger::Initialize";
|
||||||
|
|
||||||
|
string directory = useTempDir ? Path.Combine(Paths.LocalAppData, "Temp") : Path.Combine(Paths.Base, "Logs");
|
||||||
string timestamp = DateTime.UtcNow.ToString("yyyyMMdd'T'HHmmss'Z'");
|
string timestamp = DateTime.UtcNow.ToString("yyyyMMdd'T'HHmmss'Z'");
|
||||||
string filename = $"{App.ProjectName}_{timestamp}.log";
|
string filename = $"{App.ProjectName}_{timestamp}.log";
|
||||||
string location = Path.Combine(directory, filename);
|
string location = Path.Combine(directory, filename);
|
||||||
|
|
||||||
WriteLine($"[Logger::Initialize] Initializing at {location}");
|
WriteLine(LOG_IDENT, $"Initializing at {location}");
|
||||||
|
|
||||||
if (Initialized)
|
if (Initialized)
|
||||||
{
|
{
|
||||||
WriteLine("[Logger::Initialize] Failed to initialize because logger is already initialized");
|
WriteLine(LOG_IDENT, "Failed to initialize because logger is already initialized");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,45 +32,63 @@
|
|||||||
|
|
||||||
if (File.Exists(location))
|
if (File.Exists(location))
|
||||||
{
|
{
|
||||||
WriteLine("[Logger::Initialize] Failed to initialize because log file already exists");
|
WriteLine(LOG_IDENT, "Failed to initialize because log file already exists");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
_filestream = File.Open(location, FileMode.Create, FileAccess.Write, FileShare.Read);
|
_filestream = File.Open(location, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
WriteLine(LOG_IDENT, "Failed to initialize because log file already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Initialized = true;
|
Initialized = true;
|
||||||
|
|
||||||
if (Backlog.Count > 0)
|
if (Backlog.Count > 0)
|
||||||
WriteToLog(string.Join("\r\n", Backlog));
|
WriteToLog(string.Join("\r\n", Backlog));
|
||||||
|
|
||||||
WriteLine($"[Logger::Initialize] Finished initializing!");
|
WriteLine(LOG_IDENT, "Finished initializing!");
|
||||||
|
|
||||||
FileLocation = location;
|
FileLocation = location;
|
||||||
|
|
||||||
// clean up any logs older than a week
|
// clean up any logs older than a week
|
||||||
if (Directories.Initialized && Directory.Exists(Directories.Logs))
|
if (Paths.Initialized && Directory.Exists(Paths.Logs))
|
||||||
{
|
{
|
||||||
foreach (FileInfo log in new DirectoryInfo(Directories.Logs).GetFiles())
|
foreach (FileInfo log in new DirectoryInfo(Paths.Logs).GetFiles())
|
||||||
{
|
{
|
||||||
if (log.LastWriteTimeUtc.AddDays(7) > DateTime.UtcNow)
|
if (log.LastWriteTimeUtc.AddDays(7) > DateTime.UtcNow)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
App.Logger.WriteLine($"[Logger::Initialize] Cleaning up old log file '{log.Name}'");
|
WriteLine(LOG_IDENT, $"Cleaning up old log file '{log.Name}'");
|
||||||
log.Delete();
|
log.Delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteLine(string message)
|
private void WriteLine(string message)
|
||||||
{
|
{
|
||||||
string timestamp = DateTime.UtcNow.ToString("s") + "Z";
|
string timestamp = DateTime.UtcNow.ToString("s") + "Z";
|
||||||
string outcon = $"{timestamp} {message}";
|
string outcon = $"{timestamp} {message}";
|
||||||
string outlog = outcon.Replace(Directories.UserProfile, "%UserProfile%");
|
string outlog = outcon.Replace(Paths.UserProfile, "%UserProfile%");
|
||||||
|
|
||||||
Debug.WriteLine(outcon);
|
Debug.WriteLine(outcon);
|
||||||
WriteToLog(outlog);
|
WriteToLog(outlog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void WriteLine(string identifier, string message) => WriteLine($"[{identifier}] {message}");
|
||||||
|
|
||||||
|
public void WriteException(string identifier, Exception ex)
|
||||||
|
{
|
||||||
|
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
|
||||||
|
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
|
||||||
|
|
||||||
|
WriteLine($"[{identifier}] {ex}");
|
||||||
|
}
|
||||||
|
|
||||||
private async void WriteToLog(string message)
|
private async void WriteToLog(string message)
|
||||||
{
|
{
|
||||||
if (!Initialized)
|
if (!Initialized)
|
||||||
@ -84,8 +100,9 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _semaphore.WaitAsync();
|
await _semaphore.WaitAsync();
|
||||||
await _filestream!.WriteAsync(Encoding.Unicode.GetBytes($"{message}\r\n"));
|
await _filestream!.WriteAsync(Encoding.UTF8.GetBytes($"{message}\r\n"));
|
||||||
await _filestream.FlushAsync();
|
|
||||||
|
_ = _filestream.FlushAsync();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
10
Bloxstrap/Models/BloxstrapRPC/Message.cs
Normal file
10
Bloxstrap/Models/BloxstrapRPC/Message.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace Bloxstrap.Models.BloxstrapRPC;
|
||||||
|
|
||||||
|
public class Message
|
||||||
|
{
|
||||||
|
[JsonPropertyName("command")]
|
||||||
|
public string Command { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonPropertyName("data")]
|
||||||
|
public JsonElement Data { get; set; }
|
||||||
|
}
|
23
Bloxstrap/Models/BloxstrapRPC/RichPresence.cs
Normal file
23
Bloxstrap/Models/BloxstrapRPC/RichPresence.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
namespace Bloxstrap.Models.BloxstrapRPC
|
||||||
|
{
|
||||||
|
class RichPresence
|
||||||
|
{
|
||||||
|
[JsonPropertyName("details")]
|
||||||
|
public string? Details { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("state")]
|
||||||
|
public string? State { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("timeStart")]
|
||||||
|
public ulong? TimestampStart { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("timeEnd")]
|
||||||
|
public ulong? TimestampEnd { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("smallImage")]
|
||||||
|
public RichPresenceImage? SmallImage { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("largeImage")]
|
||||||
|
public RichPresenceImage? LargeImage { get; set; }
|
||||||
|
}
|
||||||
|
}
|
17
Bloxstrap/Models/BloxstrapRPC/RichPresenceImage.cs
Normal file
17
Bloxstrap/Models/BloxstrapRPC/RichPresenceImage.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
namespace Bloxstrap.Models.BloxstrapRPC
|
||||||
|
{
|
||||||
|
class RichPresenceImage
|
||||||
|
{
|
||||||
|
[JsonPropertyName("assetId")]
|
||||||
|
public ulong? AssetId { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("hoverText")]
|
||||||
|
public string? HoverText { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("clear")]
|
||||||
|
public bool Clear { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonPropertyName("reset")]
|
||||||
|
public bool Reset { get; set; } = false;
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +0,0 @@
|
|||||||
namespace Bloxstrap.Models
|
|
||||||
{
|
|
||||||
public class GameMessage
|
|
||||||
{
|
|
||||||
[JsonPropertyName("command")]
|
|
||||||
public string Command { get; set; } = null!;
|
|
||||||
|
|
||||||
[JsonPropertyName("data")]
|
|
||||||
public string Data { get; set; } = null!;
|
|
||||||
}
|
|
||||||
}
|
|
5
Bloxstrap/NativeMethods.txt
Normal file
5
Bloxstrap/NativeMethods.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
SetForegroundWindow
|
||||||
|
FlashWindow
|
||||||
|
GetWindowLong
|
||||||
|
SetWindowLong
|
||||||
|
EnumDisplaySettings
|
@ -1,6 +1,6 @@
|
|||||||
namespace Bloxstrap
|
namespace Bloxstrap
|
||||||
{
|
{
|
||||||
static class Directories
|
static class Paths
|
||||||
{
|
{
|
||||||
// note that these are directories that aren't tethered to the basedirectory
|
// note that these are directories that aren't tethered to the basedirectory
|
||||||
// so these can safely be called before initialization
|
// so these can safely be called before initialization
|
||||||
@ -8,7 +8,9 @@
|
|||||||
public static string LocalAppData => Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
public static string LocalAppData => Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||||
public static string Desktop => Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
|
public static string Desktop => Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
|
||||||
public static string StartMenu => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), "Programs", App.ProjectName);
|
public static string StartMenu => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), "Programs", App.ProjectName);
|
||||||
public static string MyPictures => Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
|
public static string System => Environment.GetFolderPath(Environment.SpecialFolder.System);
|
||||||
|
|
||||||
|
public static string Process => Environment.ProcessPath!;
|
||||||
|
|
||||||
public static string Base { get; private set; } = "";
|
public static string Base { get; private set; } = "";
|
||||||
public static string Downloads { get; private set; } = "";
|
public static string Downloads { get; private set; } = "";
|
||||||
@ -19,6 +21,8 @@
|
|||||||
|
|
||||||
public static string Application { get; private set; } = "";
|
public static string Application { get; private set; } = "";
|
||||||
|
|
||||||
|
public static string CustomFont => Path.Combine(Modifications, "content\\fonts\\CustomFont.ttf");
|
||||||
|
|
||||||
public static bool Initialized => !String.IsNullOrEmpty(Base);
|
public static bool Initialized => !String.IsNullOrEmpty(Base);
|
||||||
|
|
||||||
public static void Initialize(string baseDirectory)
|
public static void Initialize(string baseDirectory)
|
@ -94,7 +94,7 @@ namespace Bloxstrap
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
App.Logger.WriteLine($"[Protocol::ParseUri] Changed Roblox build channel from {App.Settings.Prop.Channel} to {channel}");
|
App.Logger.WriteLine("Protocol::ParseUri", $"Changed Roblox channel from {App.Settings.Prop.Channel} to {channel}");
|
||||||
App.Settings.Prop.Channel = channel;
|
App.Settings.Prop.Channel = channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +137,7 @@ namespace Bloxstrap
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[Protocol::Unregister] Failed to unregister {key}: {ex}");
|
App.Logger.WriteLine("Protocol::Unregister", $"Failed to unregister {key}: {ex}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
@ -1,6 +1,4 @@
|
|||||||
using Bloxstrap.Exceptions;
|
namespace Bloxstrap
|
||||||
|
|
||||||
namespace Bloxstrap
|
|
||||||
{
|
{
|
||||||
public static class RobloxDeployment
|
public static class RobloxDeployment
|
||||||
{
|
{
|
||||||
@ -23,24 +21,26 @@ namespace Bloxstrap
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
const string LOG_IDENT = "DeployManager::DefaultBaseUrl.Set";
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_baseUrl))
|
if (string.IsNullOrEmpty(_baseUrl))
|
||||||
{
|
{
|
||||||
// check for a working accessible deployment domain
|
// check for a working accessible deployment domain
|
||||||
foreach (string attemptedUrl in BaseUrls)
|
foreach (string attemptedUrl in BaseUrls)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[DeployManager::DefaultBaseUrl.Set] Testing connection to '{attemptedUrl}'...");
|
App.Logger.WriteLine(LOG_IDENT, $"Testing connection to '{attemptedUrl}'...");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
App.HttpClient.GetAsync($"{attemptedUrl}/version").Wait();
|
App.HttpClient.GetAsync($"{attemptedUrl}/version").Wait();
|
||||||
App.Logger.WriteLine($"[DeployManager::DefaultBaseUrl.Set] Connection successful!");
|
App.Logger.WriteLine(LOG_IDENT, "Connection successful!");
|
||||||
_baseUrl = attemptedUrl;
|
_baseUrl = attemptedUrl;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[DeployManager::DefaultBaseUrl.Set] Connection failed!");
|
App.Logger.WriteLine(LOG_IDENT, "Connection failed!");
|
||||||
App.Logger.WriteLine($"[DeployManager::DefaultBaseUrl.Set] {ex}");
|
App.Logger.WriteException(LOG_IDENT, ex);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,18 +83,33 @@ namespace Bloxstrap
|
|||||||
|
|
||||||
public static async Task<ClientVersion> GetInfo(string channel, bool extraInformation = false)
|
public static async Task<ClientVersion> GetInfo(string channel, bool extraInformation = false)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[RobloxDeployment::GetInfo] Getting deploy info for channel {channel} (extraInformation={extraInformation})");
|
const string LOG_IDENT = "RobloxDeployment::GetInfo";
|
||||||
|
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, $"Getting deploy info for channel {channel} (extraInformation={extraInformation})");
|
||||||
|
|
||||||
ClientVersion clientVersion;
|
ClientVersion clientVersion;
|
||||||
|
|
||||||
if (ClientVersionCache.ContainsKey(channel))
|
if (ClientVersionCache.ContainsKey(channel))
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[RobloxDeployment::GetInfo] Deploy information is cached");
|
App.Logger.WriteLine(LOG_IDENT, "Deploy information is cached");
|
||||||
clientVersion = ClientVersionCache[channel];
|
clientVersion = ClientVersionCache[channel];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HttpResponseMessage deployInfoResponse = await App.HttpClient.GetAsync($"https://clientsettingscdn.roblox.com/v2/client-version/WindowsPlayer/channel/{channel}");
|
string path = $"/v2/client-version/WindowsPlayer/channel/{channel}";
|
||||||
|
HttpResponseMessage deployInfoResponse;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
deployInfoResponse = await App.HttpClient.GetAsync("https://clientsettingscdn.roblox.com" + path);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, "Failed to contact clientsettingscdn! Falling back to clientsettings...");
|
||||||
|
App.Logger.WriteException(LOG_IDENT, ex);
|
||||||
|
|
||||||
|
deployInfoResponse = await App.HttpClient.GetAsync("https://clientsettings.roblox.com" + path);
|
||||||
|
}
|
||||||
|
|
||||||
string rawResponse = await deployInfoResponse.Content.ReadAsStringAsync();
|
string rawResponse = await deployInfoResponse.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
@ -105,36 +120,18 @@ namespace Bloxstrap
|
|||||||
// 500 = Error while fetching version information.
|
// 500 = Error while fetching version information.
|
||||||
// either way, we throw
|
// either way, we throw
|
||||||
|
|
||||||
App.Logger.WriteLine(
|
App.Logger.WriteLine(LOG_IDENT,
|
||||||
"[RobloxDeployment::GetInfo] Failed to fetch deploy info!\r\n" +
|
"Failed to fetch deploy info!\r\n" +
|
||||||
$"\tStatus code: {deployInfoResponse.StatusCode}\r\n" +
|
$"\tStatus code: {deployInfoResponse.StatusCode}\r\n" +
|
||||||
$"\tResponse: {rawResponse}"
|
$"\tResponse: {rawResponse}"
|
||||||
);
|
);
|
||||||
|
|
||||||
throw new HttpResponseUnsuccessfulException(deployInfoResponse);
|
throw new HttpResponseException(deployInfoResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
clientVersion = JsonSerializer.Deserialize<ClientVersion>(rawResponse)!;
|
clientVersion = JsonSerializer.Deserialize<ClientVersion>(rawResponse)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// for preferences
|
|
||||||
if (extraInformation && clientVersion.Timestamp is null)
|
|
||||||
{
|
|
||||||
App.Logger.WriteLine("[RobloxDeployment::GetInfo] 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($"[RobloxDeployment::GetInfo] {manifestUrl} - Last-Modified: {lastModified}");
|
|
||||||
clientVersion.Timestamp = DateTime.Parse(lastModified).ToLocalTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if channel is behind LIVE
|
// check if channel is behind LIVE
|
||||||
if (channel != DefaultChannel)
|
if (channel != DefaultChannel)
|
||||||
{
|
{
|
||||||
@ -143,6 +140,23 @@ namespace Bloxstrap
|
|||||||
if (Utilities.CompareVersions(clientVersion.Version, defaultClientVersion.Version) == -1)
|
if (Utilities.CompareVersions(clientVersion.Version, defaultClientVersion.Version) == -1)
|
||||||
clientVersion.IsBehindDefaultChannel = true;
|
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[channel] = clientVersion;
|
ClientVersionCache[channel] = clientVersion;
|
||||||
|
@ -39,6 +39,14 @@ namespace Bloxstrap.UI
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void ShowConnectivityDialog(string targetName, string description, Exception exception)
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
new ConnectivityDialog(targetName, description, exception).ShowDialog();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static IBootstrapperDialog GetBootstrapperDialog(BootstrapperStyle style)
|
public static IBootstrapperDialog GetBootstrapperDialog(BootstrapperStyle style)
|
||||||
{
|
{
|
||||||
return style switch
|
return style switch
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
ResizeMode="NoResize"
|
ResizeMode="NoResize"
|
||||||
Background="{ui:ThemeResource ApplicationBackgroundBrush}"
|
Background="{ui:ThemeResource ApplicationBackgroundBrush}"
|
||||||
ExtendsContentIntoTitleBar="True"
|
ExtendsContentIntoTitleBar="True"
|
||||||
|
WindowBackdropType="Mica"
|
||||||
WindowStartupLocation="CenterScreen">
|
WindowStartupLocation="CenterScreen">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
@ -37,7 +38,10 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Border Grid.Row="2" Padding="15" Background="{ui:ThemeResource SolidBackgroundFillColorSecondaryBrush}">
|
<Border Grid.Row="2" Padding="15">
|
||||||
|
<Border.Background>
|
||||||
|
<SolidColorBrush Color="{ui:ThemeResource SolidBackgroundFillColorSecondary}" Opacity="{Binding FooterOpacity, Mode=OneTime}" />
|
||||||
|
</Border.Background>
|
||||||
<Button Margin="0" Content="Cancel" Width="120" HorizontalAlignment="Right" IsEnabled="{Binding CancelEnabled, Mode=OneWay}" Command="{Binding CancelInstallCommand}" />
|
<Button Margin="0" Content="Cancel" Width="120" HorizontalAlignment="Right" IsEnabled="{Binding CancelEnabled, Mode=OneWay}" Command="{Binding CancelInstallCommand}" />
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -66,7 +66,7 @@ namespace Bloxstrap.UI.Elements.Bootstrapper
|
|||||||
|
|
||||||
public FluentDialog()
|
public FluentDialog()
|
||||||
{
|
{
|
||||||
_viewModel = new BootstrapperDialogViewModel(this);
|
_viewModel = new FluentDialogViewModel(this);
|
||||||
DataContext = _viewModel;
|
DataContext = _viewModel;
|
||||||
Title = App.Settings.Prop.BootstrapperTitle;
|
Title = App.Settings.Prop.BootstrapperTitle;
|
||||||
Icon = App.Settings.Prop.BootstrapperIcon.GetIcon().GetImageSource();
|
Icon = App.Settings.Prop.BootstrapperIcon.GetIcon().GetImageSource();
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
using Bloxstrap.Integrations;
|
||||||
using Bloxstrap.UI.ViewModels.ContextMenu;
|
using Bloxstrap.UI.ViewModels.ContextMenu;
|
||||||
|
|
||||||
namespace Bloxstrap.UI.Elements.ContextMenu
|
namespace Bloxstrap.UI.Elements.ContextMenu
|
||||||
@ -12,7 +13,7 @@ namespace Bloxstrap.UI.Elements.ContextMenu
|
|||||||
{
|
{
|
||||||
private bool _autoscroll = true;
|
private bool _autoscroll = true;
|
||||||
|
|
||||||
public LogTracer(RobloxActivity activityWatcher)
|
public LogTracer(ActivityWatcher activityWatcher)
|
||||||
{
|
{
|
||||||
DataContext = new LogTracerViewModel(this, activityWatcher);
|
DataContext = new LogTracerViewModel(this, activityWatcher);
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
@ -21,11 +21,44 @@
|
|||||||
Closed="Window_Closed">
|
Closed="Window_Closed">
|
||||||
<ui:UiWindow.ContextMenu>
|
<ui:UiWindow.ContextMenu>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<MenuItem x:Name="VersionMenuItem" IsEnabled="False" />
|
<MenuItem IsEnabled="False">
|
||||||
|
<MenuItem.Header>
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="20" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Image Grid.Column="0" Source="pack://application:,,,/Bloxstrap.ico" />
|
||||||
|
<TextBlock x:Name="VersionTextBlock" Foreground="{DynamicResource TextFillColorSecondaryBrush}" Grid.Column="1" VerticalAlignment="Center" Margin="6,0,0,0" Text="Bloxstrap v2.4.1" />
|
||||||
|
</Grid>
|
||||||
|
</MenuItem.Header>
|
||||||
|
</MenuItem>
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem x:Name="RichPresenceMenuItem" Header="Discord Rich Presence" IsCheckable="True" IsChecked="True" Visibility="Collapsed" Click="RichPresenceMenuItem_Click" />
|
<MenuItem x:Name="RichPresenceMenuItem" Header="Discord Rich Presence" IsCheckable="True" IsChecked="True" Visibility="Collapsed" Click="RichPresenceMenuItem_Click" />
|
||||||
<MenuItem x:Name="InviteDeeplinkMenuItem" Header="Copy invite deeplink" Visibility="Collapsed" Click="InviteDeeplinkMenuItem_Click" />
|
<MenuItem x:Name="InviteDeeplinkMenuItem" Visibility="Collapsed" Click="InviteDeeplinkMenuItem_Click">
|
||||||
<MenuItem x:Name="ServerDetailsMenuItem" Header="See server details" Visibility="Collapsed" Click="ServerDetailsMenuItem_Click" />
|
<MenuItem.Header>
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="24" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<ui:SymbolIcon Grid.Column="0" Symbol="ClipboardLink24"/>
|
||||||
|
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="4,0,0,0" Text="Copy invite deeplink" />
|
||||||
|
</Grid>
|
||||||
|
</MenuItem.Header>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem x:Name="ServerDetailsMenuItem" Visibility="Collapsed" Click="ServerDetailsMenuItem_Click">
|
||||||
|
<MenuItem.Header>
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="24" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<ui:SymbolIcon Grid.Column="0" Symbol="Info28"/>
|
||||||
|
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="4,0,0,0" Text="See server details" />
|
||||||
|
</Grid>
|
||||||
|
</MenuItem.Header>
|
||||||
|
</MenuItem>
|
||||||
<MenuItem x:Name="LogTracerMenuItem" Header="Open log tracer" Visibility="Collapsed" Click="LogTracerMenuItem_Click" />
|
<MenuItem x:Name="LogTracerMenuItem" Header="Open log tracer" Visibility="Collapsed" Click="LogTracerMenuItem_Click" />
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</ui:UiWindow.ContextMenu>
|
</ui:UiWindow.ContextMenu>
|
||||||
|
@ -1,17 +1,10 @@
|
|||||||
using System;
|
using System.Windows;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Data;
|
|
||||||
using System.Windows.Documents;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using System.Windows.Interop;
|
using System.Windows.Interop;
|
||||||
using System.Windows.Media;
|
|
||||||
using System.Windows.Media.Imaging;
|
using Windows.Win32;
|
||||||
using System.Windows.Shapes;
|
using Windows.Win32.Foundation;
|
||||||
|
using Windows.Win32.UI.WindowsAndMessaging;
|
||||||
|
|
||||||
using Bloxstrap.Integrations;
|
using Bloxstrap.Integrations;
|
||||||
|
|
||||||
@ -24,13 +17,13 @@ namespace Bloxstrap.UI.Elements.ContextMenu
|
|||||||
{
|
{
|
||||||
// i wouldve gladly done this as mvvm but turns out that data binding just does not work with menuitems for some reason so idk this sucks
|
// i wouldve gladly done this as mvvm but turns out that data binding just does not work with menuitems for some reason so idk this sucks
|
||||||
|
|
||||||
private readonly RobloxActivity? _activityWatcher;
|
private readonly ActivityWatcher? _activityWatcher;
|
||||||
private readonly DiscordRichPresence? _richPresenceHandler;
|
private readonly DiscordRichPresence? _richPresenceHandler;
|
||||||
|
|
||||||
private LogTracer? _logTracerWindow;
|
private LogTracer? _logTracerWindow;
|
||||||
private ServerInformation? _serverInformationWindow;
|
private ServerInformation? _serverInformationWindow;
|
||||||
|
|
||||||
public MenuContainer(RobloxActivity? activityWatcher, DiscordRichPresence? richPresenceHandler)
|
public MenuContainer(ActivityWatcher? activityWatcher, DiscordRichPresence? richPresenceHandler)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
@ -49,7 +42,7 @@ namespace Bloxstrap.UI.Elements.ContextMenu
|
|||||||
if (_richPresenceHandler is not null)
|
if (_richPresenceHandler is not null)
|
||||||
RichPresenceMenuItem.Visibility = Visibility.Visible;
|
RichPresenceMenuItem.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
VersionMenuItem.Header = $"{App.ProjectName} v{App.Version}";
|
VersionTextBlock.Text = $"{App.ProjectName} v{App.Version}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowServerInformationWindow()
|
public void ShowServerInformationWindow()
|
||||||
@ -92,13 +85,14 @@ namespace Bloxstrap.UI.Elements.ContextMenu
|
|||||||
// this is done to register the context menu wrapper as a tool window so it doesnt appear in the alt+tab switcher
|
// this is done to register the context menu wrapper as a tool window so it doesnt appear in the alt+tab switcher
|
||||||
// https://stackoverflow.com/a/551847/11852173
|
// https://stackoverflow.com/a/551847/11852173
|
||||||
|
|
||||||
var wndHelper = new WindowInteropHelper(this);
|
HWND hWnd = (HWND)new WindowInteropHelper(this).Handle;
|
||||||
long exStyle = NativeMethods.GetWindowLongPtr(wndHelper.Handle, NativeMethods.GWL_EXSTYLE).ToInt64();
|
|
||||||
exStyle |= NativeMethods.WS_EX_TOOLWINDOW;
|
int exStyle = PInvoke.GetWindowLong(hWnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE);
|
||||||
NativeMethods.SetWindowLongPtr(wndHelper.Handle, NativeMethods.GWL_EXSTYLE, (IntPtr)exStyle);
|
exStyle |= 0x00000080; //NativeMethods.WS_EX_TOOLWINDOW;
|
||||||
|
PInvoke.SetWindowLong(hWnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, exStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Window_Closed(object sender, EventArgs e) => App.Logger.WriteLine("[MenuContainer::Window_Closed] Context menu container closed");
|
private void Window_Closed(object sender, EventArgs e) => App.Logger.WriteLine("MenuContainer::Window_Closed", "Context menu container closed");
|
||||||
|
|
||||||
private void RichPresenceMenuItem_Click(object sender, RoutedEventArgs e) => _richPresenceHandler?.SetVisibility(((MenuItem)sender).IsChecked);
|
private void RichPresenceMenuItem_Click(object sender, RoutedEventArgs e) => _richPresenceHandler?.SetVisibility(((MenuItem)sender).IsChecked);
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ using System.Windows.Media;
|
|||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
using Bloxstrap.Integrations;
|
||||||
using Bloxstrap.UI.ViewModels.ContextMenu;
|
using Bloxstrap.UI.ViewModels.ContextMenu;
|
||||||
|
|
||||||
namespace Bloxstrap.UI.Elements.ContextMenu
|
namespace Bloxstrap.UI.Elements.ContextMenu
|
||||||
@ -21,7 +22,7 @@ namespace Bloxstrap.UI.Elements.ContextMenu
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class ServerInformation
|
public partial class ServerInformation
|
||||||
{
|
{
|
||||||
public ServerInformation(RobloxActivity activityWatcher)
|
public ServerInformation(ActivityWatcher activityWatcher)
|
||||||
{
|
{
|
||||||
DataContext = new ServerInformationViewModel(this, activityWatcher);
|
DataContext = new ServerInformationViewModel(this, activityWatcher);
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
44
Bloxstrap/UI/Elements/Dialogs/ConnectivityDialog.xaml
Normal file
44
Bloxstrap/UI/Elements/Dialogs/ConnectivityDialog.xaml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<ui:UiWindow x:Class="Bloxstrap.UI.Elements.Dialogs.ConnectivityDialog"
|
||||||
|
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"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Width="480"
|
||||||
|
MinHeight="0"
|
||||||
|
SizeToContent="Height"
|
||||||
|
Background="{ui:ThemeResource ApplicationBackgroundBrush}"
|
||||||
|
ExtendsContentIntoTitleBar="True"
|
||||||
|
WindowStartupLocation="CenterScreen">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<ui:TitleBar Grid.Row="0" Grid.ColumnSpan="2" Padding="8" x:Name="RootTitleBar" ShowMinimize="False" ShowMaximize="False" CanMaximize="False" KeyboardNavigation.TabNavigation="None" Title="Connectivity error" />
|
||||||
|
|
||||||
|
<Grid Grid.Row="1" Margin="16">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Image Grid.Column="0" Width="32" Height="32" Margin="0,0,15,0" VerticalAlignment="Top" RenderOptions.BitmapScalingMode="HighQuality" Source="pack://application:,,,/Resources/MessageBox/Error.png" />
|
||||||
|
<StackPanel Grid.Column="1">
|
||||||
|
<TextBlock x:Name="TitleTextBlock" Text="? is unable to connect to ?" FontSize="18" Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||||
|
<TextBlock x:Name="DescriptionTextBlock" Text="?" Margin="0,16,0,0" TextWrapping="Wrap" Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||||
|
<TextBlock Text="More information:" Margin="0,16,0,0" TextWrapping="Wrap" Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||||
|
<RichTextBox x:Name="ErrorRichTextBox" Padding="8" Margin="0,8,0,0" Block.LineHeight="2" FontFamily="Courier New" IsReadOnly="True" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Border Grid.Row="2" Padding="15" Background="{ui:ThemeResource SolidBackgroundFillColorSecondaryBrush}">
|
||||||
|
<StackPanel Orientation="Horizontal" FlowDirection="LeftToRight" HorizontalAlignment="Right">
|
||||||
|
<Button x:Name="CloseButton" MinWidth="100" Content="Close" Margin="12,0,0,0" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</ui:UiWindow>
|
45
Bloxstrap/UI/Elements/Dialogs/ConnectivityDialog.xaml.cs
Normal file
45
Bloxstrap/UI/Elements/Dialogs/ConnectivityDialog.xaml.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using System.Media;
|
||||||
|
using System.Windows.Interop;
|
||||||
|
|
||||||
|
using Windows.Win32;
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
|
||||||
|
namespace Bloxstrap.UI.Elements.Dialogs
|
||||||
|
{
|
||||||
|
// hmm... do i use MVVM for this?
|
||||||
|
// this is entirely static, so i think im fine without it, and this way is just so much more efficient
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for ExceptionDialog.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class ConnectivityDialog
|
||||||
|
{
|
||||||
|
public ConnectivityDialog(string targetName, string description, Exception exception)
|
||||||
|
{
|
||||||
|
Exception? innerException = exception.InnerException;
|
||||||
|
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
TitleTextBlock.Text = $"{App.ProjectName} is unable to connect to {targetName}";
|
||||||
|
DescriptionTextBlock.Text = description;
|
||||||
|
|
||||||
|
ErrorRichTextBox.Selection.Text = $"{exception.GetType()}: {exception.Message}";
|
||||||
|
|
||||||
|
if (innerException is not null)
|
||||||
|
ErrorRichTextBox.Selection.Text += $"\n\n===== Inner Exception =====\n{innerException.GetType()}: {innerException.Message}";
|
||||||
|
|
||||||
|
CloseButton.Click += delegate
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
};
|
||||||
|
|
||||||
|
SystemSounds.Hand.Play();
|
||||||
|
|
||||||
|
Loaded += delegate
|
||||||
|
{
|
||||||
|
var hWnd = new WindowInteropHelper(this).Handle;
|
||||||
|
PInvoke.FlashWindow((HWND)hWnd, true);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,9 @@
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Interop;
|
using System.Windows.Interop;
|
||||||
|
|
||||||
|
using Windows.Win32;
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
|
||||||
namespace Bloxstrap.UI.Elements.Dialogs
|
namespace Bloxstrap.UI.Elements.Dialogs
|
||||||
{
|
{
|
||||||
// hmm... do i use MVVM for this?
|
// hmm... do i use MVVM for this?
|
||||||
@ -60,7 +63,7 @@ namespace Bloxstrap.UI.Elements.Dialogs
|
|||||||
Loaded += delegate
|
Loaded += delegate
|
||||||
{
|
{
|
||||||
IntPtr hWnd = new WindowInteropHelper(this).Handle;
|
IntPtr hWnd = new WindowInteropHelper(this).Handle;
|
||||||
NativeMethods.FlashWindow(hWnd, true);
|
PInvoke.FlashWindow((HWND)hWnd, true);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,9 @@ using System.Windows.Controls;
|
|||||||
using System.Windows.Interop;
|
using System.Windows.Interop;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
|
using Windows.Win32;
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
|
||||||
using Bloxstrap.UI.Utility;
|
using Bloxstrap.UI.Utility;
|
||||||
|
|
||||||
namespace Bloxstrap.UI.Elements.Dialogs
|
namespace Bloxstrap.UI.Elements.Dialogs
|
||||||
@ -107,8 +110,8 @@ namespace Bloxstrap.UI.Elements.Dialogs
|
|||||||
|
|
||||||
Loaded += delegate
|
Loaded += delegate
|
||||||
{
|
{
|
||||||
IntPtr hWnd = new WindowInteropHelper(this).Handle;
|
var hWnd = new WindowInteropHelper(this).Handle;
|
||||||
NativeMethods.FlashWindow(hWnd, true);
|
PInvoke.FlashWindow((HWND)hWnd, true);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ namespace Bloxstrap.UI.Elements.Menu
|
|||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine("[MainWindow::MainWindow] Initializing menu");
|
App.Logger.WriteLine("MainWindow::MainWindow", "Initializing menu");
|
||||||
|
|
||||||
DataContext = new MainWindowViewModel(this, _dialogService);
|
DataContext = new MainWindowViewModel(this, _dialogService);
|
||||||
SetTheme();
|
SetTheme();
|
||||||
|
@ -4,10 +4,14 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||||
|
xmlns:local="clr-namespace:Bloxstrap.UI.Elements.Menu.Pages"
|
||||||
|
xmlns:models="clr-namespace:Bloxstrap.UI.ViewModels.Menu"
|
||||||
|
d:DataContext="{d:DesignInstance Type=models:BehaviourViewModel}"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="600" d:DesignWidth="800"
|
d:DesignHeight="600" d:DesignWidth="800"
|
||||||
Title="BehaviourPage"
|
Title="BehaviourPage"
|
||||||
Scrollable="True">
|
Scrollable="True">
|
||||||
|
|
||||||
<StackPanel Margin="0,0,14,14">
|
<StackPanel Margin="0,0,14,14">
|
||||||
<TextBlock Margin="0,0,0,8" Text="Configure what Bloxstrap should do when launching." FontSize="14" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
<TextBlock Margin="0,0,0,8" Text="Configure what Bloxstrap should do when launching." FontSize="14" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||||
|
|
||||||
@ -45,24 +49,27 @@
|
|||||||
<ComboBox Grid.Column="1" Margin="8,0,8,0" Padding="10,5,10,5" Width="200" IsEditable="True" ItemsSource="{Binding Channels, Mode=OneWay}" Text="{Binding SelectedChannel, Mode=TwoWay, Delay=250}" />
|
<ComboBox Grid.Column="1" Margin="8,0,8,0" Padding="10,5,10,5" Width="200" IsEditable="True" ItemsSource="{Binding Channels, Mode=OneWay}" Text="{Binding SelectedChannel, Mode=TwoWay, Delay=250}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</ui:CardExpander.Header>
|
</ui:CardExpander.Header>
|
||||||
|
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<Grid Margin="0,0,4,0">
|
<Grid Margin="0,0,4,0">
|
||||||
<Grid.Style>
|
<Grid.Style>
|
||||||
<Style>
|
<Style TargetType="Grid">
|
||||||
<Setter Property="Grid.Visibility" Value="Visible"/>
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
<DataTrigger Binding="{Binding ChannelDeployInfo}" Value="{x:Null}">
|
<DataTrigger Binding="{Binding ChannelDeployInfo}" Value="{x:Null}">
|
||||||
<Setter Property="Grid.Visibility" Value="Collapsed" />
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
</Style.Triggers>
|
</Style.Triggers>
|
||||||
</Style>
|
</Style>
|
||||||
</Grid.Style>
|
</Grid.Style>
|
||||||
|
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
@ -77,28 +84,64 @@
|
|||||||
<TextBlock Grid.Row="2" Grid.Column="0" Margin="0,0,16,0" VerticalAlignment="Center" Text="Deployed" />
|
<TextBlock Grid.Row="2" Grid.Column="0" Margin="0,0,16,0" VerticalAlignment="Center" Text="Deployed" />
|
||||||
<TextBlock Grid.Row="2" Grid.Column="1" Foreground="{DynamicResource TextFillColorTertiaryBrush}" Text="{Binding ChannelDeployInfo.Timestamp, Mode=OneWay}" />
|
<TextBlock Grid.Row="2" Grid.Column="1" Foreground="{DynamicResource TextFillColorTertiaryBrush}" Text="{Binding ChannelDeployInfo.Timestamp, Mode=OneWay}" />
|
||||||
|
|
||||||
<StackPanel Grid.Row="3" Grid.ColumnSpan="2" Margin="0,16,0,0" Orientation="Horizontal" Visibility="{Binding ChannelWarningVisibility, Mode=OneWay}">
|
<StackPanel Grid.Row="3" Grid.ColumnSpan="2" Margin="0,16,0,0" Orientation="Horizontal">
|
||||||
|
<StackPanel.Style>
|
||||||
|
<Style TargetType="StackPanel">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding ShowChannelWarning, Mode=OneWay}" Value="True">
|
||||||
|
<Setter Property="Visibility" Value="Visible" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</StackPanel.Style>
|
||||||
<Image Grid.Column="0" Width="24" Height="24" RenderOptions.BitmapScalingMode="HighQuality" Source="pack://application:,,,/Resources/MessageBox/Warning.png" />
|
<Image Grid.Column="0" Width="24" Height="24" RenderOptions.BitmapScalingMode="HighQuality" Source="pack://application:,,,/Resources/MessageBox/Warning.png" />
|
||||||
<TextBlock Margin="8,0,0,0" VerticalAlignment="Center" Text="This channel is out of date, and is likely no longer being updated. Please use another channel." />
|
<TextBlock Margin="8,0,0,0" VerticalAlignment="Center" Text="This channel is out of date, and is likely no longer being updated. Please use another channel." />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid Column="0">
|
<Grid Column="0">
|
||||||
<Grid.Style>
|
<Grid.Style>
|
||||||
<Style>
|
<Style TargetType="Grid">
|
||||||
<Setter Property="Grid.Visibility" Value="Collapsed"/>
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
<DataTrigger Binding="{Binding ChannelDeployInfo}" Value="{x:Null}">
|
<DataTrigger Binding="{Binding ChannelDeployInfo}" Value="{x:Null}">
|
||||||
<Setter Property="Grid.Visibility" Value="Visible" />
|
<Setter Property="Visibility" Value="Visible" />
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
</Style.Triggers>
|
</Style.Triggers>
|
||||||
</Style>
|
</Style>
|
||||||
</Grid.Style>
|
</Grid.Style>
|
||||||
|
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<ui:ProgressRing Grid.Column="0" Margin="6" IsIndeterminate="True" Visibility="{Binding LoadingSpinnerVisibility, Mode=OneWay}" />
|
|
||||||
<Image Grid.Column="0" Margin="6" Width="60" Height="60" Visibility="{Binding LoadingErrorVisibility, Mode=OneWay}" RenderOptions.BitmapScalingMode="HighQuality" Source="pack://application:,,,/Resources/MessageBox/Error.png" />
|
<ui:ProgressRing Grid.Column="0" Margin="6" IsIndeterminate="True">
|
||||||
|
<ui:ProgressRing.Style>
|
||||||
|
<Style TargetType="ui:ProgressRing" BasedOn="{StaticResource {x:Type ui:ProgressRing}}">
|
||||||
|
<Setter Property="Visibility" Value="Visible" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding ShowLoadingError, Mode=OneWay}" Value="True">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</ui:ProgressRing.Style>
|
||||||
|
</ui:ProgressRing>
|
||||||
|
|
||||||
|
<Image Grid.Column="0" Margin="6" Width="60" Height="60" RenderOptions.BitmapScalingMode="HighQuality" Source="pack://application:,,,/Resources/MessageBox/Error.png">
|
||||||
|
<Image.Style>
|
||||||
|
<Style TargetType="Image">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding ShowLoadingError, Mode=OneWay}" Value="True">
|
||||||
|
<Setter Property="Visibility" Value="Visible" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Image.Style>
|
||||||
|
</Image>
|
||||||
|
|
||||||
<TextBlock Grid.Column="1" Margin="16" VerticalAlignment="Center" Text="{Binding ChannelInfoLoadingText, Mode=OneWay}" TextWrapping="Wrap" />
|
<TextBlock Grid.Column="1" Margin="16" VerticalAlignment="Center" Text="{Binding ChannelInfoLoadingText, Mode=OneWay}" TextWrapping="Wrap" />
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -114,5 +157,24 @@
|
|||||||
</ui:CardControl.Header>
|
</ui:CardControl.Header>
|
||||||
<ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" ItemsSource="{Binding ChannelChangeModes.Keys, Mode=OneTime}" Text="{Binding SelectedChannelChangeMode, Mode=TwoWay}" />
|
<ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" ItemsSource="{Binding ChannelChangeModes.Keys, Mode=OneTime}" Text="{Binding SelectedChannelChangeMode, Mode=TwoWay}" />
|
||||||
</ui:CardControl>
|
</ui:CardControl>
|
||||||
|
|
||||||
|
<ui:CardControl Margin="0,8,0,0">
|
||||||
|
<ui:CardControl.Style>
|
||||||
|
<Style TargetType="ui:CardControl" BasedOn="{StaticResource {x:Type ui:CardControl}}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding ForceRobloxReinstallation, Mode=OneTime}" Value="True">
|
||||||
|
<Setter Property="IsEnabled" Value="False" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</ui:CardControl.Style>
|
||||||
|
<ui:CardControl.Header>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock FontSize="14" Text="Force Roblox reinstallation" />
|
||||||
|
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Roblox will be installed fresh on next launch." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||||
|
</StackPanel>
|
||||||
|
</ui:CardControl.Header>
|
||||||
|
<ui:ToggleSwitch IsChecked="{Binding ForceRobloxReinstallation, Mode=TwoWay}" />
|
||||||
|
</ui:CardControl>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ui:UiPage>
|
</ui:UiPage>
|
||||||
|
@ -1,4 +1,18 @@
|
|||||||
using Bloxstrap.UI.ViewModels.Menu;
|
using Bloxstrap.UI.ViewModels.Menu;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Navigation;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
namespace Bloxstrap.UI.Elements.Menu.Pages
|
namespace Bloxstrap.UI.Elements.Menu.Pages
|
||||||
{
|
{
|
||||||
@ -12,5 +26,10 @@ namespace Bloxstrap.UI.Elements.Menu.Pages
|
|||||||
DataContext = new BehaviourViewModel();
|
DataContext = new BehaviourViewModel();
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ToggleSwitch_Checked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
Loaded="Page_Loaded">
|
Loaded="Page_Loaded">
|
||||||
<Grid Margin="0,0,14,14">
|
<Grid Margin="0,0,14,14">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
@ -33,9 +34,12 @@
|
|||||||
</ui:Button.Style>
|
</ui:Button.Style>
|
||||||
</ui:Button>
|
</ui:Button>
|
||||||
<ToggleButton x:Name="TogglePresetsButton" Content="Show preset flags" Click="ToggleButton_Click" Margin="12,0,0,0" />
|
<ToggleButton x:Name="TogglePresetsButton" Content="Show preset flags" Click="ToggleButton_Click" Margin="12,0,0,0" />
|
||||||
|
<ui:Button Icon="ArrowImport24" Content="Import JSON" Margin="12,0,0,0" Click="ImportJSONButton_Click" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<DataGrid Name="DataGrid" Grid.Row="2" HeadersVisibility="Column" GridLinesVisibility="Horizontal" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" CellEditEnding="DataGrid_CellEditEnding">
|
<ui:TextBox x:Name="SearchTextBox" Grid.Row="2" Margin="0,0,0,16" Icon="Search32" PlaceholderText="Search" TextChanged="SearchTextBox_TextChanged" />
|
||||||
|
|
||||||
|
<DataGrid Name="DataGrid" Grid.Row="3" HeadersVisibility="Column" GridLinesVisibility="Horizontal" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" CellEditEnding="DataGrid_CellEditEnding">
|
||||||
<DataGrid.Style>
|
<DataGrid.Style>
|
||||||
<Style TargetType="DataGrid" BasedOn="{StaticResource {x:Type DataGrid}}">
|
<Style TargetType="DataGrid" BasedOn="{StaticResource {x:Type DataGrid}}">
|
||||||
<Setter Property="Background" Value="Transparent" />
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
@ -3,6 +3,8 @@ using System.Windows;
|
|||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Controls.Primitives;
|
using System.Windows.Controls.Primitives;
|
||||||
|
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
using Wpf.Ui.Mvvm.Contracts;
|
using Wpf.Ui.Mvvm.Contracts;
|
||||||
|
|
||||||
using Bloxstrap.UI.Elements.Dialogs;
|
using Bloxstrap.UI.Elements.Dialogs;
|
||||||
@ -19,6 +21,7 @@ namespace Bloxstrap.UI.Elements.Menu.Pages
|
|||||||
|
|
||||||
private readonly ObservableCollection<FastFlag> _fastFlagList = new();
|
private readonly ObservableCollection<FastFlag> _fastFlagList = new();
|
||||||
private bool _showPresets = false;
|
private bool _showPresets = false;
|
||||||
|
private string _searchFilter = "";
|
||||||
|
|
||||||
public FastFlagEditorPage()
|
public FastFlagEditorPage()
|
||||||
{
|
{
|
||||||
@ -33,11 +36,14 @@ namespace Bloxstrap.UI.Elements.Menu.Pages
|
|||||||
|
|
||||||
var presetFlags = FastFlagManager.PresetFlags.Values;
|
var presetFlags = FastFlagManager.PresetFlags.Values;
|
||||||
|
|
||||||
foreach (var pair in App.FastFlags.Prop)
|
foreach (var pair in App.FastFlags.Prop.OrderBy(x => x.Key))
|
||||||
{
|
{
|
||||||
if (!_showPresets && presetFlags.Contains(pair.Key))
|
if (!_showPresets && presetFlags.Contains(pair.Key))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (!pair.Key.ToLower().Contains(_searchFilter.ToLower()))
|
||||||
|
continue;
|
||||||
|
|
||||||
var entry = new FastFlag
|
var entry = new FastFlag
|
||||||
{
|
{
|
||||||
// Enabled = true,
|
// Enabled = true,
|
||||||
@ -69,6 +75,15 @@ namespace Bloxstrap.UI.Elements.Menu.Pages
|
|||||||
DataGrid.ScrollIntoView(newSelectedEntry);
|
DataGrid.ScrollIntoView(newSelectedEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ClearSearch(bool refresh = true)
|
||||||
|
{
|
||||||
|
SearchTextBox.Text = "";
|
||||||
|
_searchFilter = "";
|
||||||
|
|
||||||
|
if (refresh)
|
||||||
|
ReloadList();
|
||||||
|
}
|
||||||
|
|
||||||
// refresh list on page load to synchronize with preset page
|
// refresh list on page load to synchronize with preset page
|
||||||
private void Page_Loaded(object sender, RoutedEventArgs e) => ReloadList();
|
private void Page_Loaded(object sender, RoutedEventArgs e) => ReloadList();
|
||||||
|
|
||||||
@ -139,6 +154,9 @@ namespace Bloxstrap.UI.Elements.Menu.Pages
|
|||||||
Value = dialog.FlagValueTextBox.Text
|
Value = dialog.FlagValueTextBox.Text
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!name.Contains(_searchFilter))
|
||||||
|
ClearSearch();
|
||||||
|
|
||||||
_fastFlagList.Add(entry);
|
_fastFlagList.Add(entry);
|
||||||
|
|
||||||
App.FastFlags.SetValue(entry.Name, entry.Value);
|
App.FastFlags.SetValue(entry.Name, entry.Value);
|
||||||
@ -147,13 +165,24 @@ namespace Bloxstrap.UI.Elements.Menu.Pages
|
|||||||
{
|
{
|
||||||
Controls.ShowMessageBox("An entry for this FastFlag already exists.", MessageBoxImage.Information);
|
Controls.ShowMessageBox("An entry for this FastFlag already exists.", MessageBoxImage.Information);
|
||||||
|
|
||||||
if (!_showPresets && FastFlagManager.PresetFlags.Values.Contains(dialog.FlagNameTextBox.Text))
|
bool refresh = false;
|
||||||
|
|
||||||
|
if (!_showPresets && FastFlagManager.PresetFlags.Values.Contains(name))
|
||||||
{
|
{
|
||||||
_showPresets = true;
|
|
||||||
TogglePresetsButton.IsChecked = true;
|
TogglePresetsButton.IsChecked = true;
|
||||||
ReloadList();
|
_showPresets = true;
|
||||||
|
refresh = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!name.Contains(_searchFilter))
|
||||||
|
{
|
||||||
|
ClearSearch(false);
|
||||||
|
refresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refresh)
|
||||||
|
ReloadList();
|
||||||
|
|
||||||
entry = _fastFlagList.Where(x => x.Name == name).FirstOrDefault();
|
entry = _fastFlagList.Where(x => x.Name == name).FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,5 +212,70 @@ namespace Bloxstrap.UI.Elements.Menu.Pages
|
|||||||
_showPresets = button.IsChecked ?? false;
|
_showPresets = button.IsChecked ?? false;
|
||||||
ReloadList();
|
ReloadList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ImportJSONButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var dialog = new OpenFileDialog
|
||||||
|
{
|
||||||
|
Filter = "JSON files|*.json|All files|*.*"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() != true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var list = JsonSerializer.Deserialize<Dictionary<string, object>>(File.ReadAllText(dialog.FileName));
|
||||||
|
|
||||||
|
if (list is null)
|
||||||
|
throw new Exception("JSON deserialization returned null");
|
||||||
|
|
||||||
|
var conflictingFlags = App.FastFlags.Prop.Where(x => list.ContainsKey(x.Key)).Select(x => x.Key);
|
||||||
|
bool overwriteConflicting = false;
|
||||||
|
|
||||||
|
if (conflictingFlags.Any())
|
||||||
|
{
|
||||||
|
var result = Controls.ShowMessageBox(
|
||||||
|
"Some of the flags you are attempting to import already have set values. Would you like to overwrite their current values with the ones defined in the import?\n" +
|
||||||
|
"\n" +
|
||||||
|
"Conflicting flags:\n" +
|
||||||
|
String.Join(", ", conflictingFlags),
|
||||||
|
MessageBoxImage.Question,
|
||||||
|
MessageBoxButton.YesNo
|
||||||
|
);
|
||||||
|
|
||||||
|
overwriteConflicting = result == MessageBoxResult.Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var pair in list)
|
||||||
|
{
|
||||||
|
if (App.FastFlags.Prop.ContainsKey(pair.Key) && !overwriteConflicting)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
App.FastFlags.SetValue(pair.Key, pair.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClearSearch();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Controls.ShowMessageBox(
|
||||||
|
"The file you've selected does not appear to be valid JSON. Please double check the file contents and try again.\n" +
|
||||||
|
"\n" +
|
||||||
|
"More information:\n" +
|
||||||
|
$"{ex.Message}",
|
||||||
|
MessageBoxImage.Error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not TextBox textbox)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_searchFilter = textbox.Text;
|
||||||
|
ReloadList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,11 +67,9 @@
|
|||||||
|
|
||||||
<TextBlock Text="Presets" FontSize="16" FontWeight="Medium" Margin="0,16,0,0" />
|
<TextBlock Text="Presets" FontSize="16" FontWeight="Medium" Margin="0,16,0,0" />
|
||||||
<TextBlock Foreground="{DynamicResource TextFillColorSecondaryBrush}">
|
<TextBlock Foreground="{DynamicResource TextFillColorSecondaryBrush}">
|
||||||
FastFlags for Direct3D
|
FastFlag preset for Direct3D
|
||||||
<Hyperlink Foreground="{DynamicResource TextFillColorPrimaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/pizzaboxer/bloxstrap/wiki/A-guide-to-FastFlags#exclusive-fullscreen">exclusive fullscreen</Hyperlink>
|
<Hyperlink Foreground="{DynamicResource TextFillColorPrimaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/pizzaboxer/bloxstrap/wiki/A-guide-to-FastFlags#exclusive-fullscreen">exclusive fullscreen</Hyperlink>
|
||||||
(Alt+Enter) and
|
using Alt+Enter is already enabled by default.
|
||||||
<Hyperlink Foreground="{DynamicResource TextFillColorPrimaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/pizzaboxer/bloxstrap/wiki/A-guide-to-FastFlags#dpi-scaling-fixes">DPI scaling fixes</Hyperlink>
|
|
||||||
are already enabled by default.
|
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<ui:CardControl Margin="0,8,0,0">
|
<ui:CardControl Margin="0,8,0,0">
|
||||||
<ui:CardControl.Header>
|
<ui:CardControl.Header>
|
||||||
@ -168,11 +166,40 @@
|
|||||||
<ui:CardControl.Header>
|
<ui:CardControl.Header>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock FontSize="14" Text="Rendering mode" />
|
<TextBlock FontSize="14" Text="Rendering mode" />
|
||||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Choose what graphics renderer Roblox should use." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Choose what renderer Roblox should use. VR requires Direct3D/Automatic." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ui:CardControl.Header>
|
</ui:CardControl.Header>
|
||||||
<ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" ItemsSource="{Binding RenderingModes.Keys, Mode=OneTime}" Text="{Binding SelectedRenderingMode, Mode=TwoWay}" />
|
<ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" ItemsSource="{Binding RenderingModes.Keys, Mode=OneTime}" Text="{Binding SelectedRenderingMode, Mode=TwoWay}" />
|
||||||
</ui:CardControl>
|
</ui:CardControl>
|
||||||
|
<ui:CardControl Margin="0,8,0,0">
|
||||||
|
<ui:CardControl.Header>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock FontSize="14" Text="Antialiasing quality" />
|
||||||
|
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Forces the amount of MSAA samples that are taken." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||||
|
</StackPanel>
|
||||||
|
</ui:CardControl.Header>
|
||||||
|
<ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" ItemsSource="{Binding MSAAModes.Keys, Mode=OneTime}" Text="{Binding SelectedMSAAMode, Mode=TwoWay}" />
|
||||||
|
</ui:CardControl>
|
||||||
|
<ui:CardControl Margin="0,8,0,0">
|
||||||
|
<ui:CardControl.Header>
|
||||||
|
<StackPanel>
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Grid.Column="0" FontSize="14" Text="Preserve rendering quality with display scaling" />
|
||||||
|
<TextBlock Grid.Column="1" Margin="4,0,0,0">
|
||||||
|
<Hyperlink TextDecorations="None" ToolTip="More information on this preset" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/pizzaboxer/bloxstrap/wiki/A-guide-to-FastFlags#dpi-scaling-fixes">
|
||||||
|
<ui:SymbolIcon Symbol="QuestionCircle48" Margin="0,1,0,0" />
|
||||||
|
</Hyperlink>
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Roblox reduces your rendering quality, depending on display scaling. This toggle disables that." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||||
|
</StackPanel>
|
||||||
|
</ui:CardControl.Header>
|
||||||
|
<ui:ToggleSwitch IsChecked="{Binding FixDisplayScaling, Mode=TwoWay}" />
|
||||||
|
</ui:CardControl>
|
||||||
<ui:CardControl Margin="0,8,0,0">
|
<ui:CardControl Margin="0,8,0,0">
|
||||||
<ui:CardControl.Header>
|
<ui:CardControl.Header>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
|
@ -14,14 +14,14 @@ namespace Bloxstrap.UI
|
|||||||
private readonly System.Windows.Forms.NotifyIcon _notifyIcon;
|
private readonly System.Windows.Forms.NotifyIcon _notifyIcon;
|
||||||
private MenuContainer? _menuContainer;
|
private MenuContainer? _menuContainer;
|
||||||
|
|
||||||
private RobloxActivity? _activityWatcher;
|
private ActivityWatcher? _activityWatcher;
|
||||||
private DiscordRichPresence? _richPresenceHandler;
|
private DiscordRichPresence? _richPresenceHandler;
|
||||||
|
|
||||||
EventHandler? _alertClickHandler;
|
EventHandler? _alertClickHandler;
|
||||||
|
|
||||||
public NotifyIconWrapper()
|
public NotifyIconWrapper()
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine("[NotifyIconWrapper::NotifyIconWrapper] Initializing notification area icon");
|
App.Logger.WriteLine("NotifyIconWrapper::NotifyIconWrapper", "Initializing notification area icon");
|
||||||
|
|
||||||
_notifyIcon = new()
|
_notifyIcon = new()
|
||||||
{
|
{
|
||||||
@ -42,7 +42,7 @@ namespace Bloxstrap.UI
|
|||||||
_richPresenceHandler = richPresenceHandler;
|
_richPresenceHandler = richPresenceHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetActivityWatcher(RobloxActivity activityWatcher)
|
public void SetActivityWatcher(ActivityWatcher activityWatcher)
|
||||||
{
|
{
|
||||||
if (_activityWatcher is not null)
|
if (_activityWatcher is not null)
|
||||||
return;
|
return;
|
||||||
@ -57,10 +57,10 @@ namespace Bloxstrap.UI
|
|||||||
#region Context menu
|
#region Context menu
|
||||||
public void InitializeContextMenu()
|
public void InitializeContextMenu()
|
||||||
{
|
{
|
||||||
if (_menuContainer is not null)
|
if (_menuContainer is not null || _disposing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
App.Logger.WriteLine("[NotifyIconWrapper::InitializeContextMenu] Initializing context menu");
|
App.Logger.WriteLine("NotifyIconWrapper::InitializeContextMenu", "Initializing context menu");
|
||||||
|
|
||||||
_menuContainer = new(_activityWatcher, _richPresenceHandler);
|
_menuContainer = new(_activityWatcher, _richPresenceHandler);
|
||||||
_menuContainer.ShowDialog();
|
_menuContainer.ShowDialog();
|
||||||
@ -94,15 +94,17 @@ namespace Bloxstrap.UI
|
|||||||
{
|
{
|
||||||
string id = Guid.NewGuid().ToString()[..8];
|
string id = Guid.NewGuid().ToString()[..8];
|
||||||
|
|
||||||
App.Logger.WriteLine($"[NotifyIconWrapper::ShowAlert] [{id}] Showing alert for {duration} seconds (clickHandler={clickHandler is not null})");
|
string LOG_IDENT = $"NotifyIconWrapper::ShowAlert.{id}";
|
||||||
App.Logger.WriteLine($"[NotifyIconWrapper::ShowAlert] [{id}] {caption}: {message.Replace("\n", "\\n")}");
|
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, $"Showing alert for {duration} seconds (clickHandler={clickHandler is not null})");
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, $"{caption}: {message.Replace("\n", "\\n")}");
|
||||||
|
|
||||||
_notifyIcon.BalloonTipTitle = caption;
|
_notifyIcon.BalloonTipTitle = caption;
|
||||||
_notifyIcon.BalloonTipText = message;
|
_notifyIcon.BalloonTipText = message;
|
||||||
|
|
||||||
if (_alertClickHandler is not null)
|
if (_alertClickHandler is not null)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[NotifyIconWrapper::ShowAlert] [{id}] Previous alert still present, erasing click handler");
|
App.Logger.WriteLine(LOG_IDENT, "Previous alert still present, erasing click handler");
|
||||||
_notifyIcon.BalloonTipClicked -= _alertClickHandler;
|
_notifyIcon.BalloonTipClicked -= _alertClickHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,12 +119,12 @@ namespace Bloxstrap.UI
|
|||||||
|
|
||||||
_notifyIcon.BalloonTipClicked -= clickHandler;
|
_notifyIcon.BalloonTipClicked -= clickHandler;
|
||||||
|
|
||||||
App.Logger.WriteLine($"[NotifyIconWrapper::ShowAlert] [{id}] Duration over, erasing current click handler");
|
App.Logger.WriteLine(LOG_IDENT, "Duration over, erasing current click handler");
|
||||||
|
|
||||||
if (_alertClickHandler == clickHandler)
|
if (_alertClickHandler == clickHandler)
|
||||||
_alertClickHandler = null;
|
_alertClickHandler = null;
|
||||||
else
|
else
|
||||||
App.Logger.WriteLine($"[NotifyIconWrapper::ShowAlert] [{id}] Click handler has been overriden by another alert");
|
App.Logger.WriteLine(LOG_IDENT, "Click handler has been overriden by another alert");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +135,7 @@ namespace Bloxstrap.UI
|
|||||||
|
|
||||||
_disposing = true;
|
_disposing = true;
|
||||||
|
|
||||||
App.Logger.WriteLine($"[NotifyIconWrapper::Dispose] Disposing NotifyIcon");
|
App.Logger.WriteLine("NotifyIconWrapper::Dispose", "Disposing NotifyIcon");
|
||||||
|
|
||||||
_menuContainer?.Dispatcher.Invoke(_menuContainer.Close);
|
_menuContainer?.Dispatcher.Invoke(_menuContainer.Close);
|
||||||
_notifyIcon?.Dispose();
|
_notifyIcon?.Dispose();
|
||||||
|
@ -20,7 +20,7 @@ namespace Bloxstrap.UI.ViewModels.Bootstrapper
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
string playerLocation = Path.Combine(Directories.Versions, App.State.Prop.VersionGuid, "RobloxPlayerBeta.exe");
|
string playerLocation = Path.Combine(Paths.Versions, App.State.Prop.VersionGuid, "RobloxPlayerBeta.exe");
|
||||||
|
|
||||||
if (!File.Exists(playerLocation))
|
if (!File.Exists(playerLocation))
|
||||||
return "";
|
return "";
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
|
namespace Bloxstrap.UI.ViewModels.Bootstrapper
|
||||||
|
{
|
||||||
|
public class FluentDialogViewModel : BootstrapperDialogViewModel
|
||||||
|
{
|
||||||
|
public double FooterOpacity => Environment.OSVersion.Version.Build >= 22000 ? 0.4 : 1;
|
||||||
|
|
||||||
|
public FluentDialogViewModel(IBootstrapperDialog dialog) : base(dialog)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,12 +3,14 @@ using System.Windows.Input;
|
|||||||
|
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
|
||||||
|
using Bloxstrap.Integrations;
|
||||||
|
|
||||||
namespace Bloxstrap.UI.ViewModels.ContextMenu
|
namespace Bloxstrap.UI.ViewModels.ContextMenu
|
||||||
{
|
{
|
||||||
internal class LogTracerViewModel : NotifyPropertyChangedViewModel
|
internal class LogTracerViewModel : NotifyPropertyChangedViewModel
|
||||||
{
|
{
|
||||||
private readonly Window _window;
|
private readonly Window _window;
|
||||||
private readonly RobloxActivity _activityWatcher;
|
private readonly ActivityWatcher _activityWatcher;
|
||||||
private int _lineNumber = 1;
|
private int _lineNumber = 1;
|
||||||
|
|
||||||
public ICommand CloseWindowCommand => new RelayCommand(_window.Close);
|
public ICommand CloseWindowCommand => new RelayCommand(_window.Close);
|
||||||
@ -17,7 +19,7 @@ namespace Bloxstrap.UI.ViewModels.ContextMenu
|
|||||||
public string LogFilename => Path.GetFileName(_activityWatcher.LogLocation);
|
public string LogFilename => Path.GetFileName(_activityWatcher.LogLocation);
|
||||||
public string LogContents { get; private set; } = "";
|
public string LogContents { get; private set; } = "";
|
||||||
|
|
||||||
public LogTracerViewModel(Window window, RobloxActivity activityWatcher)
|
public LogTracerViewModel(Window window, ActivityWatcher activityWatcher)
|
||||||
{
|
{
|
||||||
_window = window;
|
_window = window;
|
||||||
_activityWatcher = activityWatcher;
|
_activityWatcher = activityWatcher;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using Bloxstrap.Integrations;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
|
||||||
namespace Bloxstrap.UI.ViewModels.ContextMenu
|
namespace Bloxstrap.UI.ViewModels.ContextMenu
|
||||||
@ -8,7 +8,7 @@ namespace Bloxstrap.UI.ViewModels.ContextMenu
|
|||||||
internal class ServerInformationViewModel : NotifyPropertyChangedViewModel
|
internal class ServerInformationViewModel : NotifyPropertyChangedViewModel
|
||||||
{
|
{
|
||||||
private readonly Window _window;
|
private readonly Window _window;
|
||||||
private readonly RobloxActivity _activityWatcher;
|
private readonly ActivityWatcher _activityWatcher;
|
||||||
|
|
||||||
public string InstanceId => _activityWatcher.ActivityJobId;
|
public string InstanceId => _activityWatcher.ActivityJobId;
|
||||||
public string ServerType => $"{_activityWatcher.ActivityServerType} server";
|
public string ServerType => $"{_activityWatcher.ActivityServerType} server";
|
||||||
@ -18,7 +18,7 @@ namespace Bloxstrap.UI.ViewModels.ContextMenu
|
|||||||
public ICommand CopyInstanceIdCommand => new RelayCommand(CopyInstanceId);
|
public ICommand CopyInstanceIdCommand => new RelayCommand(CopyInstanceId);
|
||||||
public ICommand CloseWindowCommand => new RelayCommand(_window.Close);
|
public ICommand CloseWindowCommand => new RelayCommand(_window.Close);
|
||||||
|
|
||||||
public ServerInformationViewModel(Window window, RobloxActivity activityWatcher)
|
public ServerInformationViewModel(Window window, ActivityWatcher activityWatcher)
|
||||||
{
|
{
|
||||||
_window = window;
|
_window = window;
|
||||||
_activityWatcher = activityWatcher;
|
_activityWatcher = activityWatcher;
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
using System.Windows;
|
namespace Bloxstrap.UI.ViewModels.Menu
|
||||||
|
|
||||||
using Bloxstrap.Exceptions;
|
|
||||||
|
|
||||||
namespace Bloxstrap.UI.ViewModels.Menu
|
|
||||||
{
|
{
|
||||||
public class BehaviourViewModel : NotifyPropertyChangedViewModel
|
public class BehaviourViewModel : NotifyPropertyChangedViewModel
|
||||||
{
|
{
|
||||||
private bool _manualChannelEntry = !RobloxDeployment.SelectableChannels.Contains(App.Settings.Prop.Channel);
|
private string _oldVersionGuid = "";
|
||||||
|
|
||||||
public BehaviourViewModel()
|
public BehaviourViewModel()
|
||||||
{
|
{
|
||||||
@ -15,21 +11,23 @@ namespace Bloxstrap.UI.ViewModels.Menu
|
|||||||
|
|
||||||
private async Task LoadChannelDeployInfo(string channel)
|
private async Task LoadChannelDeployInfo(string channel)
|
||||||
{
|
{
|
||||||
LoadingSpinnerVisibility = Visibility.Visible;
|
const string LOG_IDENT = "BehaviourViewModel::LoadChannelDeployInfo";
|
||||||
LoadingErrorVisibility = Visibility.Collapsed;
|
|
||||||
ChannelInfoLoadingText = "Fetching latest deploy info, please wait...";
|
|
||||||
ChannelDeployInfo = null;
|
|
||||||
|
|
||||||
OnPropertyChanged(nameof(LoadingSpinnerVisibility));
|
ShowLoadingError = false;
|
||||||
OnPropertyChanged(nameof(LoadingErrorVisibility));
|
OnPropertyChanged(nameof(ShowLoadingError));
|
||||||
|
|
||||||
|
ChannelInfoLoadingText = "Fetching latest deploy info, please wait...";
|
||||||
OnPropertyChanged(nameof(ChannelInfoLoadingText));
|
OnPropertyChanged(nameof(ChannelInfoLoadingText));
|
||||||
|
|
||||||
|
ChannelDeployInfo = null;
|
||||||
OnPropertyChanged(nameof(ChannelDeployInfo));
|
OnPropertyChanged(nameof(ChannelDeployInfo));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ClientVersion info = await RobloxDeployment.GetInfo(channel, true);
|
ClientVersion info = await RobloxDeployment.GetInfo(channel, true);
|
||||||
|
|
||||||
ChannelWarningVisibility = info.IsBehindDefaultChannel ? Visibility.Visible : Visibility.Collapsed;
|
ShowChannelWarning = info.IsBehindDefaultChannel;
|
||||||
|
OnPropertyChanged(nameof(ShowChannelWarning));
|
||||||
|
|
||||||
ChannelDeployInfo = new DeployInfo
|
ChannelDeployInfo = new DeployInfo
|
||||||
{
|
{
|
||||||
@ -38,43 +36,35 @@ namespace Bloxstrap.UI.ViewModels.Menu
|
|||||||
Timestamp = info.Timestamp?.ToFriendlyString()!
|
Timestamp = info.Timestamp?.ToFriendlyString()!
|
||||||
};
|
};
|
||||||
|
|
||||||
OnPropertyChanged(nameof(ChannelWarningVisibility));
|
|
||||||
OnPropertyChanged(nameof(ChannelDeployInfo));
|
OnPropertyChanged(nameof(ChannelDeployInfo));
|
||||||
}
|
}
|
||||||
catch (HttpResponseUnsuccessfulException ex)
|
catch (HttpResponseException ex)
|
||||||
{
|
{
|
||||||
LoadingSpinnerVisibility = Visibility.Collapsed;
|
ShowLoadingError = true;
|
||||||
LoadingErrorVisibility = Visibility.Visible;
|
OnPropertyChanged(nameof(ShowLoadingError));
|
||||||
|
|
||||||
ChannelInfoLoadingText = ex.ResponseMessage.StatusCode switch
|
ChannelInfoLoadingText = ex.ResponseMessage.StatusCode switch
|
||||||
{
|
{
|
||||||
HttpStatusCode.NotFound => "The specified channel name does not exist.",
|
HttpStatusCode.NotFound => "The specified channel name does not exist.",
|
||||||
_ => $"Failed to fetch information! (HTTP {ex.ResponseMessage.StatusCode})",
|
_ => $"Failed to fetch information! (HTTP {(int)ex.ResponseMessage.StatusCode} - {ex.ResponseMessage.ReasonPhrase})",
|
||||||
};
|
};
|
||||||
|
|
||||||
OnPropertyChanged(nameof(LoadingSpinnerVisibility));
|
|
||||||
OnPropertyChanged(nameof(LoadingErrorVisibility));
|
|
||||||
OnPropertyChanged(nameof(ChannelInfoLoadingText));
|
OnPropertyChanged(nameof(ChannelInfoLoadingText));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoadingSpinnerVisibility = Visibility.Collapsed;
|
App.Logger.WriteLine(LOG_IDENT, "An exception occurred while fetching channel information");
|
||||||
LoadingErrorVisibility = Visibility.Visible;
|
App.Logger.WriteException(LOG_IDENT, ex);
|
||||||
|
|
||||||
App.Logger.WriteLine("[BehaviourViewModel::LoadChannelDeployInfo] An exception occurred while fetching channel information");
|
ShowLoadingError = true;
|
||||||
App.Logger.WriteLine($"[BehaviourViewModel::LoadChannelDeployInfo] {ex}");
|
OnPropertyChanged(nameof(ShowLoadingError));
|
||||||
|
|
||||||
ChannelInfoLoadingText = $"Failed to fetch information! ({ex.Message})";
|
ChannelInfoLoadingText = $"Failed to fetch information! ({ex.Message})";
|
||||||
|
|
||||||
OnPropertyChanged(nameof(LoadingSpinnerVisibility));
|
|
||||||
OnPropertyChanged(nameof(LoadingErrorVisibility));
|
|
||||||
OnPropertyChanged(nameof(ChannelInfoLoadingText));
|
OnPropertyChanged(nameof(ChannelInfoLoadingText));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Visibility LoadingSpinnerVisibility { get; private set; } = Visibility.Visible;
|
public bool ShowLoadingError { get; set; } = false;
|
||||||
public Visibility LoadingErrorVisibility { get; private set; } = Visibility.Collapsed;
|
public bool ShowChannelWarning { get; set; } = false;
|
||||||
public Visibility ChannelWarningVisibility { get; private set; } = Visibility.Collapsed;
|
|
||||||
|
|
||||||
public DeployInfo? ChannelDeployInfo { get; private set; } = null;
|
public DeployInfo? ChannelDeployInfo { get; private set; } = null;
|
||||||
public string ChannelInfoLoadingText { get; private set; } = null!;
|
public string ChannelInfoLoadingText { get; private set; } = null!;
|
||||||
@ -108,24 +98,6 @@ namespace Bloxstrap.UI.ViewModels.Menu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ManualChannelEntry
|
|
||||||
{
|
|
||||||
get => _manualChannelEntry;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_manualChannelEntry = value;
|
|
||||||
|
|
||||||
if (!value)
|
|
||||||
{
|
|
||||||
// roblox typically sets channels in all lowercase, so here we find if a case insensitive match exists
|
|
||||||
string? matchingChannel = Channels.Where(x => x.ToLowerInvariant() == SelectedChannel.ToLowerInvariant()).FirstOrDefault();
|
|
||||||
SelectedChannel = string.IsNullOrEmpty(matchingChannel) ? RobloxDeployment.DefaultChannel : matchingChannel;
|
|
||||||
}
|
|
||||||
|
|
||||||
OnPropertyChanged(nameof(SelectedChannel));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo - move to enum attributes?
|
// todo - move to enum attributes?
|
||||||
public IReadOnlyDictionary<string, ChannelChangeMode> ChannelChangeModes => new Dictionary<string, ChannelChangeMode>
|
public IReadOnlyDictionary<string, ChannelChangeMode> ChannelChangeModes => new Dictionary<string, ChannelChangeMode>
|
||||||
{
|
{
|
||||||
@ -139,5 +111,22 @@ namespace Bloxstrap.UI.ViewModels.Menu
|
|||||||
get => ChannelChangeModes.FirstOrDefault(x => x.Value == App.Settings.Prop.ChannelChangeMode).Key;
|
get => ChannelChangeModes.FirstOrDefault(x => x.Value == App.Settings.Prop.ChannelChangeMode).Key;
|
||||||
set => App.Settings.Prop.ChannelChangeMode = ChannelChangeModes[value];
|
set => App.Settings.Prop.ChannelChangeMode = ChannelChangeModes[value];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool ForceRobloxReinstallation
|
||||||
|
{
|
||||||
|
get => String.IsNullOrEmpty(App.State.Prop.VersionGuid);
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
_oldVersionGuid = App.State.Prop.VersionGuid;
|
||||||
|
App.State.Prop.VersionGuid = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
App.State.Prop.VersionGuid = _oldVersionGuid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,17 @@ namespace Bloxstrap.UI.ViewModels.Menu
|
|||||||
public string SelectedRenderingMode
|
public string SelectedRenderingMode
|
||||||
{
|
{
|
||||||
get => App.FastFlags.GetPresetEnum(RenderingModes, "Rendering.Mode", "True");
|
get => App.FastFlags.GetPresetEnum(RenderingModes, "Rendering.Mode", "True");
|
||||||
set => App.FastFlags.SetPresetEnum("Rendering.Mode", RenderingModes[value], "True");
|
set
|
||||||
|
{
|
||||||
|
App.FastFlags.SetPresetEnum("Rendering.Mode", RenderingModes[value], "True");
|
||||||
|
App.FastFlags.CheckManualFullscreenPreset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FixDisplayScaling
|
||||||
|
{
|
||||||
|
get => App.FastFlags.GetPreset("Rendering.DisableScaling") == "True";
|
||||||
|
set => App.FastFlags.SetPreset("Rendering.DisableScaling", value ? "True" : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AlternateGraphicsSelectorEnabled
|
public bool AlternateGraphicsSelectorEnabled
|
||||||
@ -117,6 +127,14 @@ namespace Bloxstrap.UI.ViewModels.Menu
|
|||||||
set => App.FastFlags.SetPresetEnum("Rendering.Lighting", LightingModes[value], "True");
|
set => App.FastFlags.SetPresetEnum("Rendering.Lighting", LightingModes[value], "True");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, string?> MSAAModes => FastFlagManager.MSAAModes;
|
||||||
|
|
||||||
|
public string SelectedMSAAMode
|
||||||
|
{
|
||||||
|
get => MSAAModes.First(x => x.Value == App.FastFlags.GetPreset("Rendering.MSAA")).Key ?? MSAAModes.First().Key;
|
||||||
|
set => App.FastFlags.SetPreset("Rendering.MSAA", MSAAModes[value]);
|
||||||
|
}
|
||||||
|
|
||||||
public bool GuiHidingEnabled
|
public bool GuiHidingEnabled
|
||||||
{
|
{
|
||||||
get => App.FastFlags.GetPreset("UI.Hide") == "32380007";
|
get => App.FastFlags.GetPreset("UI.Hide") == "32380007";
|
||||||
|
@ -31,7 +31,7 @@ namespace Bloxstrap.UI.ViewModels.Menu
|
|||||||
|
|
||||||
private void OpenFolder()
|
private void OpenFolder()
|
||||||
{
|
{
|
||||||
Process.Start("explorer.exe", Directories.Base);
|
Process.Start("explorer.exe", Paths.Base);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string InstallLocation
|
public string InstallLocation
|
||||||
|
@ -42,7 +42,7 @@ namespace Bloxstrap.UI.ViewModels.Menu
|
|||||||
|
|
||||||
bool shouldCheckInstallLocation = App.IsFirstRun || App.BaseDirectory != _originalBaseDirectory;
|
bool shouldCheckInstallLocation = App.IsFirstRun || App.BaseDirectory != _originalBaseDirectory;
|
||||||
|
|
||||||
if (shouldCheckInstallLocation)
|
if (shouldCheckInstallLocation && NavigationVisibility == Visibility.Visible)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -75,7 +75,7 @@ namespace Bloxstrap.UI.ViewModels.Menu
|
|||||||
$"The folder you've chosen to install {App.ProjectName} to already exists and is NOT empty. It is strongly recommended for {App.ProjectName} to be installed to its own independent folder.\n\n" +
|
$"The folder you've chosen to install {App.ProjectName} to already exists and is NOT empty. It is strongly recommended for {App.ProjectName} to be installed to its own independent folder.\n\n" +
|
||||||
"Changing to the following location is suggested:\n" +
|
"Changing to the following location is suggested:\n" +
|
||||||
$"{suggestedChange}\n\n" +
|
$"{suggestedChange}\n\n" +
|
||||||
"Would you like to change your install location to this?\n" +
|
"Would you like to change to the suggested location?\n" +
|
||||||
"Selecting 'No' will ignore this warning and continue installation.",
|
"Selecting 'No' will ignore this warning and continue installation.",
|
||||||
MessageBoxImage.Warning,
|
MessageBoxImage.Warning,
|
||||||
MessageBoxButton.YesNoCancel,
|
MessageBoxButton.YesNoCancel,
|
||||||
@ -96,14 +96,15 @@ namespace Bloxstrap.UI.ViewModels.Menu
|
|||||||
((INavigationWindow)_window).Navigate(typeof(PreInstallPage));
|
((INavigationWindow)_window).Navigate(typeof(PreInstallPage));
|
||||||
|
|
||||||
NavigationVisibility = Visibility.Collapsed;
|
NavigationVisibility = Visibility.Collapsed;
|
||||||
ConfirmButtonEnabled = false;
|
|
||||||
|
|
||||||
OnPropertyChanged(nameof(NavigationVisibility));
|
OnPropertyChanged(nameof(NavigationVisibility));
|
||||||
|
|
||||||
|
ConfirmButtonEnabled = false;
|
||||||
OnPropertyChanged(nameof(ConfirmButtonEnabled));
|
OnPropertyChanged(nameof(ConfirmButtonEnabled));
|
||||||
|
|
||||||
Task.Run(async delegate
|
Task.Run(async delegate
|
||||||
{
|
{
|
||||||
await Task.Delay(3000);
|
await Task.Delay(3000);
|
||||||
|
|
||||||
ConfirmButtonEnabled = true;
|
ConfirmButtonEnabled = true;
|
||||||
OnPropertyChanged(nameof(ConfirmButtonEnabled));
|
OnPropertyChanged(nameof(ConfirmButtonEnabled));
|
||||||
});
|
});
|
||||||
@ -121,7 +122,7 @@ namespace Bloxstrap.UI.ViewModels.Menu
|
|||||||
|
|
||||||
if (shouldCheckInstallLocation)
|
if (shouldCheckInstallLocation)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[MainWindowViewModel::ConfirmSettings] Changing install location from {_originalBaseDirectory} to {App.BaseDirectory}");
|
App.Logger.WriteLine("MainWindowViewModel::ConfirmSettings", $"Changing install location from {_originalBaseDirectory} to {App.BaseDirectory}");
|
||||||
|
|
||||||
Controls.ShowMessageBox(
|
Controls.ShowMessageBox(
|
||||||
$"{App.ProjectName} will install to the new location you've set the next time it runs.",
|
$"{App.ProjectName} will install to the new location you've set the next time it runs.",
|
||||||
@ -131,7 +132,7 @@ namespace Bloxstrap.UI.ViewModels.Menu
|
|||||||
using RegistryKey registryKey = Registry.CurrentUser.CreateSubKey($@"Software\{App.ProjectName}");
|
using RegistryKey registryKey = Registry.CurrentUser.CreateSubKey($@"Software\{App.ProjectName}");
|
||||||
registryKey.SetValue("InstallLocation", App.BaseDirectory);
|
registryKey.SetValue("InstallLocation", App.BaseDirectory);
|
||||||
registryKey.SetValue("OldInstallLocation", _originalBaseDirectory);
|
registryKey.SetValue("OldInstallLocation", _originalBaseDirectory);
|
||||||
Directories.Initialize(App.BaseDirectory);
|
Paths.Initialize(App.BaseDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
CloseWindow();
|
CloseWindow();
|
||||||
|
@ -9,16 +9,18 @@ namespace Bloxstrap.UI.ViewModels.Menu
|
|||||||
{
|
{
|
||||||
public class ModsViewModel : NotifyPropertyChangedViewModel
|
public class ModsViewModel : NotifyPropertyChangedViewModel
|
||||||
{
|
{
|
||||||
private void OpenModsFolder() => Process.Start("explorer.exe", Directories.Modifications);
|
private void OpenModsFolder() => Process.Start("explorer.exe", Paths.Modifications);
|
||||||
|
|
||||||
private string _customFontLocation = Path.Combine(Directories.Modifications, "content\\fonts\\CustomFont.ttf");
|
private bool _usingCustomFont => App.IsFirstRun && App.CustomFontLocation is not null || !App.IsFirstRun && File.Exists(Paths.CustomFont);
|
||||||
private bool _usingCustomFont => File.Exists(_customFontLocation);
|
|
||||||
|
|
||||||
private void ManageCustomFont()
|
private void ManageCustomFont()
|
||||||
{
|
{
|
||||||
if (_usingCustomFont)
|
if (_usingCustomFont)
|
||||||
{
|
{
|
||||||
File.Delete(_customFontLocation);
|
if (App.IsFirstRun)
|
||||||
|
App.CustomFontLocation = null;
|
||||||
|
else
|
||||||
|
File.Delete(Paths.CustomFont);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -30,8 +32,16 @@ namespace Bloxstrap.UI.ViewModels.Menu
|
|||||||
if (dialog.ShowDialog() != true)
|
if (dialog.ShowDialog() != true)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(_customFontLocation)!);
|
if (App.IsFirstRun)
|
||||||
File.Copy(dialog.FileName, _customFontLocation);
|
{
|
||||||
|
App.CustomFontLocation = dialog.FileName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(Paths.CustomFont)!);
|
||||||
|
File.Copy(dialog.FileName, Paths.CustomFont);
|
||||||
|
Filesystem.AssertReadOnly(Paths.CustomFont);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OnPropertyChanged(nameof(ChooseCustomFontVisibility));
|
OnPropertyChanged(nameof(ChooseCustomFontVisibility));
|
||||||
|
@ -1,93 +0,0 @@
|
|||||||
using System.Windows;
|
|
||||||
|
|
||||||
namespace Bloxstrap
|
|
||||||
{
|
|
||||||
public class Updater
|
|
||||||
{
|
|
||||||
public static void CheckInstalledVersion()
|
|
||||||
{
|
|
||||||
if (Environment.ProcessPath is null || !File.Exists(Directories.Application) || Environment.ProcessPath == Directories.Application)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 2.0.0 downloads updates to <BaseFolder>/Updates so lol
|
|
||||||
bool isAutoUpgrade = Environment.ProcessPath.StartsWith(Path.Combine(Directories.Base, "Updates")) || Environment.ProcessPath.StartsWith(Path.Combine(Directories.LocalAppData, "Temp"));
|
|
||||||
|
|
||||||
FileVersionInfo currentVersionInfo = FileVersionInfo.GetVersionInfo(Environment.ProcessPath);
|
|
||||||
|
|
||||||
if (MD5Hash.FromFile(Environment.ProcessPath) == MD5Hash.FromFile(Directories.Application))
|
|
||||||
return;
|
|
||||||
|
|
||||||
MessageBoxResult result;
|
|
||||||
|
|
||||||
// silently upgrade version if the command line flag is set or if we're launching from an auto update
|
|
||||||
if (App.IsUpgrade || isAutoUpgrade)
|
|
||||||
{
|
|
||||||
result = MessageBoxResult.Yes;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = Controls.ShowMessageBox(
|
|
||||||
$"The version of {App.ProjectName} you've launched is different to the version you currently have installed.\nWould you like to upgrade your currently installed version?",
|
|
||||||
MessageBoxImage.Question,
|
|
||||||
MessageBoxButton.YesNo
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result != MessageBoxResult.Yes)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// yes, this is EXTREMELY hacky, but the updater process that launched the
|
|
||||||
// new version may still be open and so we have to wait for it to close
|
|
||||||
int attempts = 0;
|
|
||||||
while (attempts < 10)
|
|
||||||
{
|
|
||||||
attempts++;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
File.Delete(Directories.Application);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
if (attempts == 1)
|
|
||||||
App.Logger.WriteLine("[Updater::CheckInstalledVersion] Waiting for write permissions to update version");
|
|
||||||
|
|
||||||
Thread.Sleep(500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attempts == 10)
|
|
||||||
{
|
|
||||||
App.Logger.WriteLine("[Updater::CheckInstalledVersion] Failed to update! (Could not get write permissions after 5 seconds)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
File.Copy(Environment.ProcessPath, Directories.Application);
|
|
||||||
|
|
||||||
Bootstrapper.Register();
|
|
||||||
|
|
||||||
if (isAutoUpgrade)
|
|
||||||
{
|
|
||||||
App.NotifyIcon?.ShowAlert(
|
|
||||||
$"Bloxstrap has been upgraded to v{currentVersionInfo.ProductVersion}",
|
|
||||||
"See what's new in this version",
|
|
||||||
30,
|
|
||||||
(_, _) => Utilities.ShellExecute($"https://github.com/{App.ProjectRepository}/releases/tag/v{currentVersionInfo.ProductVersion}")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else if (!App.IsQuiet)
|
|
||||||
{
|
|
||||||
Controls.ShowMessageBox(
|
|
||||||
$"{App.ProjectName} has been updated to v{currentVersionInfo.ProductVersion}",
|
|
||||||
MessageBoxImage.Information,
|
|
||||||
MessageBoxButton.OK
|
|
||||||
);
|
|
||||||
|
|
||||||
Controls.ShowMenu();
|
|
||||||
|
|
||||||
App.Terminate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +1,33 @@
|
|||||||
namespace Bloxstrap
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace Bloxstrap
|
||||||
{
|
{
|
||||||
static class Utilities
|
static class Utilities
|
||||||
{
|
{
|
||||||
public static long GetFreeDiskSpace(string path)
|
public static void ShellExecute(string website)
|
||||||
{
|
{
|
||||||
foreach (DriveInfo drive in DriveInfo.GetDrives())
|
try
|
||||||
{
|
{
|
||||||
if (path.StartsWith(drive.Name))
|
Process.Start(new ProcessStartInfo
|
||||||
return drive.AvailableFreeSpace;
|
{
|
||||||
|
FileName = website,
|
||||||
|
UseShellExecute = true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
catch (Win32Exception ex)
|
||||||
|
{
|
||||||
|
// lmfao
|
||||||
|
|
||||||
return -1;
|
if (!ex.Message.Contains("Application not found"))
|
||||||
|
throw;
|
||||||
|
|
||||||
|
Process.Start(new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "rundll32.exe",
|
||||||
|
Arguments = $"shell32,OpenAs_RunDLL {website}"
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ShellExecute(string website) => Process.Start(new ProcessStartInfo { FileName = website, UseShellExecute = true });
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
|
20
Bloxstrap/Utility/AsyncHelpers.cs
Normal file
20
Bloxstrap/Utility/AsyncHelpers.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
namespace Bloxstrap.Utility
|
||||||
|
{
|
||||||
|
public static class AsyncHelpers
|
||||||
|
{
|
||||||
|
public static void ExceptionHandler(Task task, object? state)
|
||||||
|
{
|
||||||
|
const string LOG_IDENT = "AsyncHelpers::ExceptionHandler";
|
||||||
|
|
||||||
|
if (task.Exception is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (state is null)
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, "An exception occurred while running the task");
|
||||||
|
else
|
||||||
|
App.Logger.WriteLine(LOG_IDENT, $"An exception occurred while running the task '{state}'");
|
||||||
|
|
||||||
|
App.FinalizeExceptionHandling(task.Exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
Bloxstrap/Utility/Filesystem.cs
Normal file
34
Bloxstrap/Utility/Filesystem.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace Bloxstrap.Utility
|
||||||
|
{
|
||||||
|
internal static class Filesystem
|
||||||
|
{
|
||||||
|
internal static long GetFreeDiskSpace(string path)
|
||||||
|
{
|
||||||
|
foreach (DriveInfo drive in DriveInfo.GetDrives())
|
||||||
|
{
|
||||||
|
if (path.StartsWith(drive.Name))
|
||||||
|
return drive.AvailableFreeSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void AssertReadOnly(string filePath)
|
||||||
|
{
|
||||||
|
var fileInfo = new FileInfo(filePath);
|
||||||
|
|
||||||
|
if (!fileInfo.IsReadOnly)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fileInfo.IsReadOnly = false;
|
||||||
|
App.Logger.WriteLine("Filesystem::AssertReadOnly", $"The following file was set as read-only: {filePath}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,8 @@
|
|||||||
{
|
{
|
||||||
public static async Task<T?> GetJson<T>(string url)
|
public static async Task<T?> GetJson<T>(string url)
|
||||||
{
|
{
|
||||||
|
string LOG_IDENT = $"Http::GetJson<{typeof(T).Name}>";
|
||||||
|
|
||||||
string json = await App.HttpClient.GetStringAsync(url);
|
string json = await App.HttpClient.GetStringAsync(url);
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -12,8 +14,8 @@
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
App.Logger.WriteLine($"[Http::GetJson<{typeof(T).Name}>] Failed to deserialize JSON for {url}!");
|
App.Logger.WriteLine(LOG_IDENT, $"Failed to deserialize JSON for {url}!");
|
||||||
App.Logger.WriteLine($"[Http::GetJson<{typeof(T).Name}>] {ex}");
|
App.Logger.WriteException(LOG_IDENT, ex);
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Bloxstrap.Utility
|
|
||||||
{
|
|
||||||
static class NativeMethods
|
|
||||||
{
|
|
||||||
[DllImport("user32.dll")]
|
|
||||||
public static extern bool SetForegroundWindow(IntPtr hWnd);
|
|
||||||
|
|
||||||
[DllImport("user32.dll")]
|
|
||||||
public static extern bool FlashWindow(IntPtr hWnd, bool bInvert);
|
|
||||||
|
|
||||||
[DllImport("user32.dll")]
|
|
||||||
public static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);
|
|
||||||
|
|
||||||
[DllImport("user32.dll")]
|
|
||||||
public static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
|
|
||||||
|
|
||||||
// i only bothered to add the constants that im using lol
|
|
||||||
|
|
||||||
public const int GWL_EXSTYLE = -20;
|
|
||||||
|
|
||||||
public const int WS_EX_TOOLWINDOW = 0x00000080;
|
|
||||||
}
|
|
||||||
}
|
|
2
wpfui
2
wpfui
@ -1 +1 @@
|
|||||||
Subproject commit 5f0a87d7d8bc19335ad1c15a93e1c17efc3c2f6e
|
Subproject commit 55d5ca08f9a1d7623f9a7e386e1f4ac9f2d024a7
|
Loading…
Reference in New Issue
Block a user