Merge branch 'version-2.4.0' into dependabot/nuget/DiscordRichPresence-1.1.4.20
15
.github/workflows/ci.yml
vendored
@ -6,28 +6,33 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
configuration: [Debug, Release]
|
||||
platform: [x64]
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '6.x'
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet build --no-restore
|
||||
|
||||
- name: Publish
|
||||
run: dotnet publish -p:PublishSingleFile=true -r win-${{ matrix.platform }} -c ${{ matrix.configuration }} --self-contained false .\Bloxstrap\Bloxstrap.csproj
|
||||
run: dotnet publish -p:PublishSingleFile=true -p:CommitHash=${{ github.sha }} -p:CommitRef=${{ github.ref_type }}/${{ github.ref_name }} -r win-x64 -c ${{ matrix.configuration }} --self-contained false .\Bloxstrap\Bloxstrap.csproj
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Bloxstrap (${{ matrix.configuration }}, ${{ matrix.platform }})
|
||||
name: Bloxstrap (${{ matrix.configuration }}, x64)
|
||||
path: |
|
||||
.\Bloxstrap\bin\${{ matrix.configuration }}\net6.0-windows\win-${{ matrix.platform }}\publish\*
|
||||
.\Bloxstrap\bin\${{ matrix.configuration }}\net6.0-windows\win-x64\publish\*
|
||||
|
||||
release:
|
||||
needs: build
|
||||
@ -40,9 +45,11 @@ jobs:
|
||||
with:
|
||||
name: Bloxstrap (Release, x64)
|
||||
path: x64
|
||||
|
||||
- name: Rename binaries
|
||||
run: |
|
||||
mv x64/Bloxstrap.exe Bloxstrap-${{ github.ref_name }}-x64.exe
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
|
@ -11,6 +11,8 @@
|
||||
<ui:ThemesDictionary Theme="Dark" />
|
||||
<ui:ControlsDictionary />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<FontFamily x:Key="Rubik">pack://application:,,,/Resources/Fonts/#Rubik Light</FontFamily>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
@ -1,24 +1,9 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
|
||||
using Microsoft.Win32;
|
||||
|
||||
using Bloxstrap.Dialogs;
|
||||
using Bloxstrap.Extensions;
|
||||
using Bloxstrap.Models;
|
||||
using Bloxstrap.Singletons;
|
||||
using Bloxstrap.Views;
|
||||
|
||||
namespace Bloxstrap
|
||||
{
|
||||
/// <summary>
|
||||
@ -26,13 +11,13 @@ namespace Bloxstrap
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
public static readonly CultureInfo CultureFormat = CultureInfo.InvariantCulture;
|
||||
|
||||
public const string ProjectName = "Bloxstrap";
|
||||
public const string ProjectRepository = "pizzaboxer/bloxstrap";
|
||||
public const string RobloxAppName = "RobloxPlayerBeta";
|
||||
|
||||
// used only for communicating between app and menu - use Directories.Base for anything else
|
||||
public static string BaseDirectory = null!;
|
||||
|
||||
public static bool ShouldSaveConfigs { get; set; } = false;
|
||||
public static bool IsSetupComplete { get; set; } = true;
|
||||
public static bool IsFirstRun { get; private set; } = true;
|
||||
@ -43,57 +28,35 @@ namespace Bloxstrap
|
||||
public static bool IsMenuLaunch { get; private set; } = false;
|
||||
public static string[] LaunchArgs { get; private set; } = null!;
|
||||
|
||||
public static BuildMetadataAttribute BuildMetadata = Assembly.GetExecutingAssembly().GetCustomAttribute<BuildMetadataAttribute>()!;
|
||||
public static string Version = Assembly.GetExecutingAssembly().GetName().Version!.ToString()[..^2];
|
||||
|
||||
// singletons
|
||||
public static readonly Logger Logger = new();
|
||||
public static readonly HttpClient HttpClient = new(new HttpClientLoggingHandler(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.All }));
|
||||
|
||||
public static readonly JsonManager<Settings> Settings = new();
|
||||
public static readonly JsonManager<State> State = new();
|
||||
public static readonly FastFlagManager FastFlags = new();
|
||||
public static readonly HttpClient HttpClient = new(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.All });
|
||||
|
||||
public static System.Windows.Forms.NotifyIcon Notification { get; private set; } = null!;
|
||||
|
||||
// shorthand
|
||||
public static MessageBoxResult ShowMessageBox(string message, MessageBoxImage icon = MessageBoxImage.None, MessageBoxButton buttons = MessageBoxButton.OK)
|
||||
public static void Terminate(ErrorCode exitCode = ErrorCode.ERROR_SUCCESS)
|
||||
{
|
||||
if (IsQuiet)
|
||||
return MessageBoxResult.None;
|
||||
if (IsFirstRun)
|
||||
{
|
||||
if (exitCode == ErrorCode.ERROR_CANCELLED)
|
||||
exitCode = ErrorCode.ERROR_INSTALL_USEREXIT;
|
||||
}
|
||||
|
||||
return MessageBox.Show(message, ProjectName, buttons, icon);
|
||||
}
|
||||
int exitCodeNum = (int)exitCode;
|
||||
|
||||
Logger.WriteLine($"[App::Terminate] Terminating with exit code {exitCodeNum} ({exitCode})");
|
||||
|
||||
public static void Terminate(int code = Bootstrapper.ERROR_SUCCESS)
|
||||
{
|
||||
Logger.WriteLine($"[App::Terminate] Terminating with exit code {code}");
|
||||
Settings.Save();
|
||||
State.Save();
|
||||
Environment.Exit(code);
|
||||
}
|
||||
Notification.Dispose();
|
||||
|
||||
private void InitLog()
|
||||
{
|
||||
// if we're running for the first time or uninstalling, log to temp folder
|
||||
// else, log to bloxstrap folder
|
||||
|
||||
bool isUsingTempDir = IsFirstRun || IsUninstall;
|
||||
string logdir = isUsingTempDir ? Path.Combine(Directories.LocalAppData, "Temp") : Path.Combine(Directories.Base, "Logs");
|
||||
string timestamp = DateTime.UtcNow.ToString("yyyyMMdd'T'HHmmss'Z'");
|
||||
|
||||
Logger.Initialize(Path.Combine(logdir, $"{ProjectName}_{timestamp}.log"));
|
||||
|
||||
// clean up any logs older than a week
|
||||
if (!isUsingTempDir)
|
||||
{
|
||||
foreach (FileInfo log in new DirectoryInfo(logdir).GetFiles())
|
||||
{
|
||||
if (log.LastWriteTimeUtc.AddDays(7) > DateTime.UtcNow)
|
||||
continue;
|
||||
|
||||
Logger.WriteLine($"[App::InitLog] Cleaning up old log file '{log.Name}'");
|
||||
log.Delete();
|
||||
}
|
||||
}
|
||||
Environment.Exit(exitCodeNum);
|
||||
}
|
||||
|
||||
void GlobalExceptionHandler(object sender, DispatcherUnhandledExceptionEventArgs e)
|
||||
@ -103,10 +66,19 @@ namespace Bloxstrap
|
||||
Logger.WriteLine("[App::OnStartup] An exception occurred when running the main thread");
|
||||
Logger.WriteLine($"[App::OnStartup] {e.Exception}");
|
||||
|
||||
if (!IsQuiet)
|
||||
Settings.Prop.BootstrapperStyle.GetNew().ShowError($"{e.Exception.GetType()}: {e.Exception.Message}");
|
||||
FinalizeExceptionHandling(e.Exception);
|
||||
}
|
||||
|
||||
Terminate(Bootstrapper.ERROR_INSTALL_FAILURE);
|
||||
void FinalizeExceptionHandling(Exception exception)
|
||||
{
|
||||
#if DEBUG
|
||||
throw exception;
|
||||
#else
|
||||
if (!IsQuiet)
|
||||
Controls.ShowExceptionDialog(exception);
|
||||
|
||||
Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
@ -115,6 +87,11 @@ namespace Bloxstrap
|
||||
|
||||
Logger.WriteLine($"[App::OnStartup] Starting {ProjectName} v{Version}");
|
||||
|
||||
if (String.IsNullOrEmpty(BuildMetadata.CommitHash))
|
||||
Logger.WriteLine($"[App::OnStartup] Compiled {BuildMetadata.Timestamp.ToFriendlyString()} from {BuildMetadata.Machine}");
|
||||
else
|
||||
Logger.WriteLine($"[App::OnStartup] Compiled {BuildMetadata.Timestamp.ToFriendlyString()} from commit {BuildMetadata.CommitHash} ({BuildMetadata.CommitRef})");
|
||||
|
||||
// To customize application configuration such as set high DPI settings or default font,
|
||||
// see https://aka.ms/applicationconfiguration.
|
||||
ApplicationConfiguration.Initialize();
|
||||
@ -172,24 +149,29 @@ namespace Bloxstrap
|
||||
// check if installed
|
||||
using (RegistryKey? registryKey = Registry.CurrentUser.OpenSubKey($@"Software\{ProjectName}"))
|
||||
{
|
||||
if (registryKey is null)
|
||||
string? installLocation = null;
|
||||
|
||||
if (registryKey is not null)
|
||||
installLocation = (string?)registryKey.GetValue("InstallLocation");
|
||||
|
||||
if (registryKey is null || installLocation is null)
|
||||
{
|
||||
Logger.WriteLine("[App::OnStartup] Running first-time install");
|
||||
|
||||
BaseDirectory = Path.Combine(Directories.LocalAppData, ProjectName);
|
||||
InitLog();
|
||||
Logger.Initialize(true);
|
||||
|
||||
if (!IsQuiet)
|
||||
{
|
||||
IsSetupComplete = false;
|
||||
FastFlags.Load();
|
||||
new MainWindow().ShowDialog();
|
||||
Controls.ShowMenu();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IsFirstRun = false;
|
||||
BaseDirectory = (string)registryKey.GetValue("InstallLocation")!;
|
||||
BaseDirectory = installLocation;
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,7 +179,7 @@ namespace Bloxstrap
|
||||
if (!IsSetupComplete)
|
||||
{
|
||||
Logger.WriteLine("[App::OnStartup] Installation cancelled!");
|
||||
Environment.Exit(Bootstrapper.ERROR_INSTALL_USEREXIT);
|
||||
Terminate(ErrorCode.ERROR_CANCELLED);
|
||||
}
|
||||
|
||||
Directories.Initialize(BaseDirectory);
|
||||
@ -206,7 +188,14 @@ namespace Bloxstrap
|
||||
// just in case the user decides to cancel the install
|
||||
if (!IsFirstRun)
|
||||
{
|
||||
InitLog();
|
||||
Logger.Initialize(IsUninstall);
|
||||
|
||||
if (!Logger.Initialized)
|
||||
{
|
||||
Logger.WriteLine("[App::OnStartup] Possible duplicate launch detected, terminating.");
|
||||
Terminate();
|
||||
}
|
||||
|
||||
Settings.Load();
|
||||
State.Load();
|
||||
FastFlags.Load();
|
||||
@ -221,24 +210,24 @@ namespace Bloxstrap
|
||||
|
||||
if (IsMenuLaunch)
|
||||
{
|
||||
Mutex mutex;
|
||||
Process? menuProcess = Process.GetProcesses().Where(x => x.MainWindowTitle == $"{ProjectName} Menu").FirstOrDefault();
|
||||
|
||||
try
|
||||
if (menuProcess is not null)
|
||||
{
|
||||
mutex = Mutex.OpenExisting("Bloxstrap_MenuMutex");
|
||||
Logger.WriteLine("[App::OnStartup] Bloxstrap_MenuMutex mutex exists, aborting menu launch...");
|
||||
Terminate();
|
||||
IntPtr handle = menuProcess.MainWindowHandle;
|
||||
Logger.WriteLine($"[App::OnStartup] Found an already existing menu window with handle {handle}");
|
||||
NativeMethods.SetForegroundWindow(handle);
|
||||
}
|
||||
catch
|
||||
else
|
||||
{
|
||||
// no mutex exists, continue to opening preferences menu
|
||||
mutex = new(true, "Bloxstrap_MenuMutex");
|
||||
if (Process.GetProcessesByName(ProjectName).Length > 1 && !IsQuiet)
|
||||
Controls.ShowMessageBox(
|
||||
$"{ProjectName} is currently running, likely as a background Roblox process. Please note that not all your changes will immediately apply until you close all currently open Roblox instances.",
|
||||
MessageBoxImage.Information
|
||||
);
|
||||
|
||||
Controls.ShowMenu();
|
||||
}
|
||||
|
||||
if (Utilities.GetProcessCount(ProjectName) > 1)
|
||||
ShowMessageBox($"{ProjectName} is currently running, likely as a background Roblox process. Please note that not all your changes will immediately apply until you close all currently open Roblox instances.", MessageBoxImage.Information);
|
||||
|
||||
new MainWindow().ShowDialog();
|
||||
}
|
||||
else if (LaunchArgs.Length > 0)
|
||||
{
|
||||
@ -248,6 +237,12 @@ namespace Bloxstrap
|
||||
}
|
||||
else if (LaunchArgs[0].StartsWith("roblox:"))
|
||||
{
|
||||
if (Settings.Prop.UseDisableAppPatch)
|
||||
Controls.ShowMessageBox(
|
||||
"Roblox was launched via a deeplink, however the desktop app is required for deeplink launching to work. Because you've opted to disable the desktop app, it will temporarily be re-enabled for this launch only.",
|
||||
MessageBoxImage.Information
|
||||
);
|
||||
|
||||
commandLine = $"--app --deeplink {LaunchArgs[0]}";
|
||||
}
|
||||
else
|
||||
@ -300,13 +295,9 @@ namespace Bloxstrap
|
||||
}
|
||||
}
|
||||
|
||||
// there's a bug here that i have yet to fix!
|
||||
// sometimes the task just terminates when the bootstrapper hasn't
|
||||
// actually finished, causing the bootstrapper to hang indefinitely
|
||||
// i have no idea how the fuck this happens, but it happens like VERY
|
||||
// rarely so i'm not too concerned by it
|
||||
// maybe one day ill find out why it happens
|
||||
Task bootstrapperTask = Task.Run(() => bootstrapper.Run()).ContinueWith(t =>
|
||||
Task bootstrapperTask = Task.Run(() => bootstrapper.Run());
|
||||
|
||||
bootstrapperTask.ContinueWith(t =>
|
||||
{
|
||||
Logger.WriteLine("[App::OnStartup] Bootstrapper task has finished");
|
||||
|
||||
@ -318,16 +309,18 @@ namespace Bloxstrap
|
||||
|
||||
Logger.WriteLine($"[App::OnStartup] {t.Exception}");
|
||||
|
||||
#if DEBUG
|
||||
throw t.Exception;
|
||||
#else
|
||||
var exception = t.Exception.InnerExceptions.Count >= 1 ? t.Exception.InnerExceptions[0] : t.Exception;
|
||||
dialog?.ShowError($"{exception.GetType()}: {exception.Message}");
|
||||
Terminate(Bootstrapper.ERROR_INSTALL_FAILURE);
|
||||
Exception exception = t.Exception;
|
||||
|
||||
#if !DEBUG
|
||||
if (t.Exception.GetType().ToString() == "System.AggregateException")
|
||||
exception = t.Exception.InnerException!;
|
||||
#endif
|
||||
|
||||
FinalizeExceptionHandling(exception);
|
||||
});
|
||||
|
||||
dialog?.ShowBootstrapper();
|
||||
|
||||
bootstrapperTask.Wait();
|
||||
|
||||
if (singletonMutex is not null)
|
||||
|
@ -7,25 +7,38 @@
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>True</UseWindowsForms>
|
||||
<ApplicationIcon>Bloxstrap.ico</ApplicationIcon>
|
||||
<Version>2.3.0</Version>
|
||||
<FileVersion>2.3.0.0</FileVersion>
|
||||
<Version>2.4.0</Version>
|
||||
<FileVersion>2.4.0.0</FileVersion>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Bloxstrap.ico" />
|
||||
<Resource Include="Resources\Fonts\Rubik-VariableFont_wght.ttf" />
|
||||
<Resource Include="Resources\BootstrapperStyles\ByfronDialog\ByfronLogoDark.jpg" />
|
||||
<Resource Include="Resources\BootstrapperStyles\ByfronDialog\ByfronLogoLight.jpg" />
|
||||
<Resource Include="Resources\Menu\StartMenuLocation.png" />
|
||||
<Resource Include="Resources\MessageBox\Error.png" />
|
||||
<Resource Include="Resources\MessageBox\Information.png" />
|
||||
<Resource Include="Resources\MessageBox\Question.png" />
|
||||
<Resource Include="Resources\MessageBox\Warning.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\Mods\OldCursor.png" />
|
||||
<EmbeddedResource Include="Resources\Mods\Cursor\From2006\ArrowCursor.png" />
|
||||
<EmbeddedResource Include="Resources\Mods\Cursor\From2006\ArrowFarCursor.png" />
|
||||
<EmbeddedResource Include="Resources\Mods\Cursor\From2013\ArrowCursor.png" />
|
||||
<EmbeddedResource Include="Resources\Mods\Cursor\From2013\ArrowFarCursor.png" />
|
||||
<EmbeddedResource Include="Resources\Mods\Empty.mp3" />
|
||||
<EmbeddedResource Include="Resources\Mods\OldAvatarBackground.rbxl" />
|
||||
<EmbeddedResource Include="Resources\Mods\OldDeath.ogg" />
|
||||
<EmbeddedResource Include="Resources\Mods\OldFarCursor.png" />
|
||||
<EmbeddedResource Include="Resources\Mods\OldJump.mp3" />
|
||||
<EmbeddedResource Include="Resources\Mods\OldWalk.mp3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.0" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.1.4.20" />
|
||||
<PackageReference Include="ini-parser-netstandard" Version="2.5.2" />
|
||||
<PackageReference Include="securifybv.ShellLink" Version="0.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@ -33,4 +46,13 @@
|
||||
<ProjectReference Include="..\wpfui\src\Wpf.Ui\Wpf.Ui.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="Bloxstrap.Models.Attributes.BuildMetadataAttribute">
|
||||
<_Parameter1>$([System.DateTime]::UtcNow.ToString("s"))Z</_Parameter1>
|
||||
<_Parameter2>$(COMPUTERNAME)\$(USERNAME)</_Parameter2>
|
||||
<_Parameter3>$(CommitHash)</_Parameter3>
|
||||
<_Parameter4>$(CommitRef)</_Parameter4>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,35 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows;
|
||||
|
||||
using Microsoft.Win32;
|
||||
|
||||
using Bloxstrap.Dialogs;
|
||||
using Bloxstrap.Enums;
|
||||
using Bloxstrap.Integrations;
|
||||
using Bloxstrap.Models;
|
||||
using Bloxstrap.Tools;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Bloxstrap
|
||||
{
|
||||
public class Bootstrapper
|
||||
{
|
||||
#region Properties
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/msi/error-codes
|
||||
public const int ERROR_SUCCESS = 0;
|
||||
public const int ERROR_INSTALL_USEREXIT = 1602;
|
||||
public const int ERROR_INSTALL_FAILURE = 1603;
|
||||
|
||||
// in case a new package is added, you can find the corresponding directory
|
||||
// by opening the stock bootstrapper in a hex editor
|
||||
// TODO - there ideally should be a less static way to do this that's not hardcoded?
|
||||
@ -73,7 +53,6 @@ namespace Bloxstrap
|
||||
|
||||
private static bool FreshInstall => String.IsNullOrEmpty(App.State.Prop.VersionGuid);
|
||||
private static string DesktopShortcutLocation => Path.Combine(Directories.Desktop, "Play Roblox.lnk");
|
||||
private static bool ShouldInstallWebView2 = false;
|
||||
|
||||
private string _playerLocation => Path.Combine(_versionFolder, "RobloxPlayerBeta.exe");
|
||||
|
||||
@ -96,26 +75,21 @@ namespace Bloxstrap
|
||||
public Bootstrapper(string launchCommandLine)
|
||||
{
|
||||
_launchCommandLine = launchCommandLine;
|
||||
|
||||
// check if the webview2 runtime needs to be installed
|
||||
// webview2 can either be installed be per-user or globally, so we need to check in both hklm and hkcu
|
||||
// https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#detect-if-a-suitable-webview2-runtime-is-already-installed
|
||||
|
||||
string hklmLocation = "SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}";
|
||||
string hkcuLocation = "Software\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}";
|
||||
|
||||
ShouldInstallWebView2 = Registry.LocalMachine.OpenSubKey(hklmLocation) is null && Registry.CurrentUser.OpenSubKey(hkcuLocation) is null;
|
||||
}
|
||||
|
||||
private void SetStatus(string message)
|
||||
{
|
||||
App.Logger.WriteLine($"[Bootstrapper::SetStatus] {message}");
|
||||
|
||||
// yea idk
|
||||
if (App.Settings.Prop.BootstrapperStyle == BootstrapperStyle.ByfronDialog)
|
||||
message = message.Replace("...", "");
|
||||
|
||||
if (Dialog is not null)
|
||||
Dialog.Message = message;
|
||||
}
|
||||
|
||||
private void UpdateProgressbar()
|
||||
private void UpdateProgressBar()
|
||||
{
|
||||
int newProgress = (int)Math.Floor(_progressIncrement * _totalDownloadedBytes);
|
||||
|
||||
@ -174,28 +148,25 @@ namespace Bloxstrap
|
||||
|
||||
CheckInstallMigration();
|
||||
|
||||
// only update roblox if we're running for the first time, or if
|
||||
// roblox isn't running and our version guid is out of date, or the player exe doesn't exist
|
||||
if (App.IsFirstRun || !Utilities.CheckIfRobloxRunning() && (_latestVersionGuid != App.State.Prop.VersionGuid || !File.Exists(_playerLocation)))
|
||||
// install/update roblox if we're running for the first time, needs updating, or the player location doesn't exist
|
||||
if (App.IsFirstRun || _latestVersionGuid != App.State.Prop.VersionGuid || !File.Exists(_playerLocation))
|
||||
await InstallLatestVersion();
|
||||
|
||||
// last time the version folder was set, it was set to the latest version guid
|
||||
// but if we skipped updating because roblox is already running, we want it to be set to our current version
|
||||
_versionFolder = Path.Combine(Directories.Versions, App.State.Prop.VersionGuid);
|
||||
|
||||
if (App.IsFirstRun)
|
||||
App.ShouldSaveConfigs = true;
|
||||
|
||||
MigrateIntegrations();
|
||||
|
||||
if (ShouldInstallWebView2)
|
||||
await InstallWebView2();
|
||||
await InstallWebView2();
|
||||
|
||||
App.FastFlags.Save();
|
||||
await ApplyModifications();
|
||||
|
||||
if (App.IsFirstRun || FreshInstall)
|
||||
{
|
||||
Register();
|
||||
RegisterProgramSize();
|
||||
}
|
||||
|
||||
CheckInstall();
|
||||
|
||||
@ -218,42 +189,6 @@ namespace Bloxstrap
|
||||
|
||||
ClientVersion clientVersion = await RobloxDeployment.GetInfo(App.Settings.Prop.Channel);
|
||||
|
||||
// briefly check if current channel is suitable to use
|
||||
if (App.Settings.Prop.Channel.ToLower() != RobloxDeployment.DefaultChannel.ToLower() && App.Settings.Prop.ChannelChangeMode != ChannelChangeMode.Ignore)
|
||||
{
|
||||
string? switchDefaultPrompt = null;
|
||||
ClientVersion? defaultChannelInfo = null;
|
||||
|
||||
App.Logger.WriteLine($"[Bootstrapper::CheckLatestVersion] Checking if current channel is suitable to use...");
|
||||
|
||||
if (String.IsNullOrEmpty(switchDefaultPrompt))
|
||||
{
|
||||
// this SUCKS
|
||||
defaultChannelInfo = await RobloxDeployment.GetInfo(RobloxDeployment.DefaultChannel);
|
||||
int defaultChannelVersion = Int32.Parse(defaultChannelInfo.Version.Split('.')[1]);
|
||||
int currentChannelVersion = Int32.Parse(clientVersion.Version.Split('.')[1]);
|
||||
|
||||
if (currentChannelVersion < defaultChannelVersion)
|
||||
switchDefaultPrompt = $"Your current preferred channel ({App.Settings.Prop.Channel}) appears to no longer be receiving updates. Would you like to switch to {RobloxDeployment.DefaultChannel}?";
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(switchDefaultPrompt))
|
||||
{
|
||||
MessageBoxResult result = App.Settings.Prop.ChannelChangeMode == ChannelChangeMode.Automatic ? MessageBoxResult.Yes : App.ShowMessageBox(switchDefaultPrompt, MessageBoxImage.Question, MessageBoxButton.YesNo);
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
App.Settings.Prop.Channel = RobloxDeployment.DefaultChannel;
|
||||
App.Logger.WriteLine($"[DeployManager::SwitchToDefault] Changed Roblox release channel from {App.Settings.Prop.Channel} to {RobloxDeployment.DefaultChannel}");
|
||||
|
||||
if (defaultChannelInfo is null)
|
||||
defaultChannelInfo = await RobloxDeployment.GetInfo(RobloxDeployment.DefaultChannel);
|
||||
|
||||
clientVersion = defaultChannelInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_latestVersionGuid = clientVersion.VersionGuid;
|
||||
_versionFolder = Path.Combine(Directories.Versions, _latestVersionGuid);
|
||||
_versionPackageManifest = await PackageManifest.Get(_latestVersionGuid);
|
||||
@ -265,15 +200,26 @@ namespace Bloxstrap
|
||||
|
||||
if (_launchCommandLine == "--app" && App.Settings.Prop.UseDisableAppPatch)
|
||||
{
|
||||
Utilities.OpenWebsite("https://www.roblox.com/games");
|
||||
Utilities.ShellExecute("https://www.roblox.com/games");
|
||||
Dialog?.CloseBootstrapper();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!File.Exists("C:\\Windows\\System32\\mfplat.dll"))
|
||||
{
|
||||
Controls.ShowMessageBox(
|
||||
"Roblox requires the use of Windows Media Foundation components. You appear to be missing them, likely because you are using an N edition of Windows. Please install them first, and then launch Roblox.",
|
||||
MessageBoxImage.Error
|
||||
);
|
||||
Utilities.ShellExecute("https://support.microsoft.com/en-us/topic/media-feature-pack-list-for-windows-n-editions-c1c6fffa-d052-8338-7a79-a4bb980a700a");
|
||||
Dialog?.CloseBootstrapper();
|
||||
return;
|
||||
}
|
||||
|
||||
_launchCommandLine = _launchCommandLine.Replace("LAUNCHTIMEPLACEHOLDER", DateTimeOffset.Now.ToUnixTimeMilliseconds().ToString());
|
||||
|
||||
if (App.Settings.Prop.Channel.ToLower() != RobloxDeployment.DefaultChannel.ToLower())
|
||||
_launchCommandLine += " -channel " + App.Settings.Prop.Channel.ToLower();
|
||||
if (App.Settings.Prop.Channel.ToLowerInvariant() != RobloxDeployment.DefaultChannel.ToLowerInvariant())
|
||||
_launchCommandLine += " -channel " + App.Settings.Prop.Channel.ToLowerInvariant();
|
||||
|
||||
// whether we should wait for roblox to exit to handle stuff in the background or clean up after roblox closes
|
||||
bool shouldWait = false;
|
||||
@ -374,7 +320,7 @@ namespace Bloxstrap
|
||||
{
|
||||
if (!_isInstalling)
|
||||
{
|
||||
App.Terminate(ERROR_INSTALL_USEREXIT);
|
||||
App.Terminate(ErrorCode.ERROR_CANCELLED);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -397,7 +343,7 @@ namespace Bloxstrap
|
||||
App.Logger.WriteLine($"[Bootstrapper::CancelInstall] {ex}");
|
||||
}
|
||||
|
||||
App.Terminate(ERROR_INSTALL_USEREXIT);
|
||||
App.Terminate(ErrorCode.ERROR_CANCELLED);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@ -432,6 +378,20 @@ namespace Bloxstrap
|
||||
App.Logger.WriteLine("[Bootstrapper::StartRoblox] Registered application");
|
||||
}
|
||||
|
||||
public void RegisterProgramSize()
|
||||
{
|
||||
App.Logger.WriteLine("[Bootstrapper::RegisterProgramSize] Registering approximate program size...");
|
||||
|
||||
using RegistryKey uninstallKey = Registry.CurrentUser.CreateSubKey($"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{App.ProjectName}");
|
||||
|
||||
// sum compressed and uncompressed package sizes and convert to kilobytes
|
||||
int totalSize = (_versionPackageManifest.Sum(x => x.Size) + _versionPackageManifest.Sum(x => x.PackedSize)) / 1000;
|
||||
|
||||
uninstallKey.SetValue("EstimatedSize", totalSize);
|
||||
|
||||
App.Logger.WriteLine($"[Bootstrapper::RegisterProgramSize] Registered as {totalSize} KB");
|
||||
}
|
||||
|
||||
private void CheckInstallMigration()
|
||||
{
|
||||
// check if we've changed our install location since the last time we started
|
||||
@ -557,31 +517,48 @@ namespace Bloxstrap
|
||||
private async Task CheckForUpdates()
|
||||
{
|
||||
// don't update if there's another instance running (likely running in the background)
|
||||
if (Utilities.GetProcessCount(App.ProjectName) > 1)
|
||||
if (Process.GetProcessesByName(App.ProjectName).Count() > 1)
|
||||
{
|
||||
App.Logger.WriteLine($"[Bootstrapper::CheckForUpdates] More than one Bloxstrap instance running, aborting update check");
|
||||
return;
|
||||
}
|
||||
|
||||
string currentVersion = $"{App.ProjectName} v{App.Version}";
|
||||
App.Logger.WriteLine($"[Bootstrapper::CheckForUpdates] Checking for updates...");
|
||||
|
||||
App.Logger.WriteLine($"[Bootstrapper::CheckForUpdates] Checking for {App.ProjectName} updates...");
|
||||
GithubRelease? releaseInfo;
|
||||
try
|
||||
{
|
||||
releaseInfo = await Http.GetJson<GithubRelease>($"https://api.github.com/repos/{App.ProjectRepository}/releases/latest");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.Logger.WriteLine($"[Bootstrapper::CheckForUpdates] Failed to fetch releases: {ex}");
|
||||
return;
|
||||
}
|
||||
|
||||
var releaseInfo = await Utilities.GetJson<GithubRelease>($"https://api.github.com/repos/{App.ProjectRepository}/releases/latest");
|
||||
|
||||
if (releaseInfo?.Assets is null || currentVersion == releaseInfo.Name)
|
||||
if (releaseInfo is null || releaseInfo.Assets is null)
|
||||
{
|
||||
App.Logger.WriteLine($"[Bootstrapper::CheckForUpdates] No updates found");
|
||||
return;
|
||||
}
|
||||
|
||||
int versionComparison = Utilities.CompareVersions(App.Version, releaseInfo.TagName);
|
||||
|
||||
// check if we aren't using a deployed build, so we can update to one if a new version comes out
|
||||
if (versionComparison == 0 && App.BuildMetadata.CommitRef.StartsWith("tag") || versionComparison == -1)
|
||||
{
|
||||
App.Logger.WriteLine($"[Bootstrapper::CheckForUpdates] No updates found");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
SetStatus($"Getting the latest {App.ProjectName}...");
|
||||
|
||||
// 64-bit is always the first option
|
||||
GithubReleaseAsset asset = releaseInfo.Assets[0];
|
||||
string downloadLocation = Path.Combine(Directories.LocalAppData, "Temp", asset.Name);
|
||||
|
||||
App.Logger.WriteLine($"[Bootstrapper::CheckForUpdates] Downloading {releaseInfo.Name}...");
|
||||
App.Logger.WriteLine($"[Bootstrapper::CheckForUpdates] Downloading {releaseInfo.TagName}...");
|
||||
|
||||
if (!File.Exists(downloadLocation))
|
||||
{
|
||||
@ -591,7 +568,7 @@ namespace Bloxstrap
|
||||
await response.Content.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
App.Logger.WriteLine($"[Bootstrapper::CheckForUpdates] Starting {releaseInfo.Name}...");
|
||||
App.Logger.WriteLine($"[Bootstrapper::CheckForUpdates] Starting {releaseInfo.TagName}...");
|
||||
|
||||
ProcessStartInfo startInfo = new()
|
||||
{
|
||||
@ -602,20 +579,28 @@ namespace Bloxstrap
|
||||
startInfo.ArgumentList.Add(arg);
|
||||
|
||||
App.Settings.Save();
|
||||
App.ShouldSaveConfigs = false;
|
||||
|
||||
Process.Start(startInfo);
|
||||
|
||||
Environment.Exit(0);
|
||||
App.Terminate();
|
||||
}
|
||||
|
||||
private void Uninstall()
|
||||
{
|
||||
// prompt to shutdown roblox if its currently running
|
||||
if (Utilities.CheckIfRobloxRunning())
|
||||
if (Process.GetProcessesByName(App.RobloxAppName).Any())
|
||||
{
|
||||
App.Logger.WriteLine($"[Bootstrapper::Uninstall] Prompting to shut down all open Roblox instances");
|
||||
|
||||
Dialog?.PromptShutdown();
|
||||
MessageBoxResult result = Controls.ShowMessageBox(
|
||||
"Roblox is currently running, but must be closed before uninstalling Bloxstrap. Would you like close Roblox now?",
|
||||
MessageBoxImage.Information,
|
||||
MessageBoxButton.OKCancel
|
||||
);
|
||||
|
||||
if (result != MessageBoxResult.OK)
|
||||
App.Terminate(ErrorCode.ERROR_CANCELLED);
|
||||
|
||||
try
|
||||
{
|
||||
@ -635,7 +620,6 @@ namespace Bloxstrap
|
||||
|
||||
SetStatus($"Uninstalling {App.ProjectName}...");
|
||||
|
||||
//App.Settings.ShouldSave = false;
|
||||
App.ShouldSaveConfigs = false;
|
||||
|
||||
// check if stock bootstrapper is still installed
|
||||
@ -655,30 +639,75 @@ namespace Bloxstrap
|
||||
ProtocolHandler.Register("roblox-player", "Roblox", bootstrapperLocation);
|
||||
}
|
||||
|
||||
try
|
||||
// if the folder we're installed to does not end with "Bloxstrap", we're installed to a user-selected folder
|
||||
// in which case, chances are they chose to install to somewhere they didn't really mean to (prior to the added warning in 2.4.0)
|
||||
// if so, we're walking on eggshells and have to ensure we only clean up what we need to clean up
|
||||
bool cautiousUninstall = !Directories.Base.EndsWith(App.ProjectName);
|
||||
|
||||
var cleanupSequence = new List<Action>
|
||||
{
|
||||
// delete application key
|
||||
Registry.CurrentUser.DeleteSubKey($@"Software\{App.ProjectName}");
|
||||
() => Registry.CurrentUser.DeleteSubKey($@"Software\{App.ProjectName}"),
|
||||
() => Directory.Delete(Directories.StartMenu, true),
|
||||
() => File.Delete(Path.Combine(Directories.Desktop, "Play Roblox.lnk")),
|
||||
() => Registry.CurrentUser.DeleteSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{App.ProjectName}")
|
||||
};
|
||||
|
||||
// delete start menu folder
|
||||
Directory.Delete(Directories.StartMenu, true);
|
||||
if (cautiousUninstall)
|
||||
{
|
||||
cleanupSequence.Add(() => Directory.Delete(Directories.Downloads, true));
|
||||
cleanupSequence.Add(() => Directory.Delete(Directories.Modifications, true));
|
||||
cleanupSequence.Add(() => Directory.Delete(Directories.Versions, true));
|
||||
cleanupSequence.Add(() => Directory.Delete(Directories.Logs, true));
|
||||
|
||||
// delete desktop shortcut
|
||||
File.Delete(Path.Combine(Directories.Desktop, "Play Roblox.lnk"));
|
||||
|
||||
// delete uninstall key
|
||||
Registry.CurrentUser.DeleteSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{App.ProjectName}");
|
||||
|
||||
// delete installation folder
|
||||
// (should delete everything except bloxstrap itself)
|
||||
Directory.Delete(Directories.Base, true);
|
||||
cleanupSequence.Add(() => File.Delete(App.Settings.FileLocation));
|
||||
cleanupSequence.Add(() => File.Delete(App.State.FileLocation));
|
||||
}
|
||||
catch (Exception ex)
|
||||
else
|
||||
{
|
||||
App.Logger.WriteLine($"Could not fully uninstall! ({ex})");
|
||||
cleanupSequence.Add(() => Directory.Delete(Directories.Base, true));
|
||||
}
|
||||
|
||||
Dialog?.ShowSuccess($"{App.ProjectName} has succesfully uninstalled");
|
||||
foreach (var process in cleanupSequence)
|
||||
{
|
||||
try
|
||||
{
|
||||
process();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.Logger.WriteLine($"[Bootstrapper::Uninstall] Encountered exception when running cleanup sequence (#{cleanupSequence.IndexOf(process)})");
|
||||
App.Logger.WriteLine($"[Bootstrapper::Uninstall] {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
Action? callback = null;
|
||||
|
||||
if (Directory.Exists(Directories.Base))
|
||||
{
|
||||
callback = delegate
|
||||
{
|
||||
// this is definitely one of the workaround hacks of all time
|
||||
// could antiviruses falsely detect this as malicious behaviour though?
|
||||
// "hmm whats this program doing running a cmd command chain quietly in the background that auto deletes an entire folder"
|
||||
|
||||
string deleteCommand;
|
||||
|
||||
if (cautiousUninstall)
|
||||
deleteCommand = $"del /Q \"{Directories.Application}\"";
|
||||
else
|
||||
deleteCommand = $"del /Q \"{Directories.Base}\\*\" && rmdir \"{Directories.Base}\"";
|
||||
|
||||
Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
FileName = "cmd.exe",
|
||||
Arguments = $"/c timeout 5 && {deleteCommand}",
|
||||
UseShellExecute = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
Dialog?.ShowSuccess($"{App.ProjectName} has succesfully uninstalled", callback);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@ -700,8 +729,12 @@ namespace Bloxstrap
|
||||
|
||||
if (Utilities.GetFreeDiskSpace(Directories.Base) < totalSizeRequired)
|
||||
{
|
||||
App.ShowMessageBox($"{App.ProjectName} does not have enough disk space to download and install Roblox. Please free up some disk space and try again.", MessageBoxImage.Error);
|
||||
App.Terminate(ERROR_INSTALL_FAILURE);
|
||||
Controls.ShowMessageBox(
|
||||
$"{App.ProjectName} does not have enough disk space to download and install Roblox. Please free up some disk space and try again.",
|
||||
MessageBoxImage.Error
|
||||
);
|
||||
|
||||
App.Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -762,19 +795,13 @@ namespace Bloxstrap
|
||||
{
|
||||
if (!_versionPackageManifest.Exists(package => filename.Contains(package.Signature)))
|
||||
{
|
||||
App.Logger.WriteLine($"Deleting unused package {filename}");
|
||||
App.Logger.WriteLine($"[Bootstrapper::InstallLatestVersion] Deleting unused package {filename}");
|
||||
File.Delete(filename);
|
||||
}
|
||||
}
|
||||
|
||||
string oldVersionFolder = Path.Combine(Directories.Versions, App.State.Prop.VersionGuid);
|
||||
|
||||
if (_latestVersionGuid != App.State.Prop.VersionGuid && Directory.Exists(oldVersionFolder))
|
||||
{
|
||||
// and also to delete our old version folder
|
||||
Directory.Delete(oldVersionFolder, true);
|
||||
}
|
||||
|
||||
// move old compatibility flags for the old location
|
||||
using (RegistryKey appFlagsKey = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers"))
|
||||
{
|
||||
@ -788,19 +815,45 @@ namespace Bloxstrap
|
||||
appFlagsKey.DeleteValue(oldGameClientLocation);
|
||||
}
|
||||
}
|
||||
|
||||
// delete any old version folders
|
||||
// we only do this if roblox isnt running just in case an update happened
|
||||
// while they were launching a second instance or something idk
|
||||
if (!Process.GetProcessesByName(App.RobloxAppName).Any())
|
||||
{
|
||||
foreach (DirectoryInfo dir in new DirectoryInfo(Directories.Versions).GetDirectories())
|
||||
{
|
||||
if (dir.Name == _latestVersionGuid || !dir.Name.StartsWith("version-"))
|
||||
continue;
|
||||
|
||||
App.Logger.WriteLine($"[Bootstrapper::InstallLatestVersion] Removing old version folder for {dir.Name}");
|
||||
dir.Delete(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
App.State.Prop.VersionGuid = _latestVersionGuid;
|
||||
|
||||
// don't register program size until the program is registered, which will be done after this
|
||||
if (!App.IsFirstRun && !FreshInstall)
|
||||
RegisterProgramSize();
|
||||
|
||||
if (Dialog is not null)
|
||||
Dialog.CancelEnabled = false;
|
||||
|
||||
App.State.Prop.VersionGuid = _latestVersionGuid;
|
||||
|
||||
_isInstalling = false;
|
||||
}
|
||||
|
||||
private async Task InstallWebView2()
|
||||
{
|
||||
if (!ShouldInstallWebView2)
|
||||
// check if the webview2 runtime needs to be installed
|
||||
// webview2 can either be installed be per-user or globally, so we need to check in both hklm and hkcu
|
||||
// https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#detect-if-a-suitable-webview2-runtime-is-already-installed
|
||||
|
||||
using RegistryKey? hklmKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}");
|
||||
using RegistryKey? hkcuKey = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}");
|
||||
|
||||
if (hklmKey is not null || hkcuKey is not null)
|
||||
return;
|
||||
|
||||
App.Logger.WriteLine($"[Bootstrapper::InstallWebView2] Installing runtime...");
|
||||
@ -848,8 +901,8 @@ namespace Bloxstrap
|
||||
|
||||
if (File.Exists(injectorLocation))
|
||||
{
|
||||
App.ShowMessageBox(
|
||||
"Roblox has now completeted rollout of the new client update, featuring 64-bit support and the Hyperion anticheat. ReShade does not work with this update, and so it has now been removed from Bloxstrap.\n\n"+
|
||||
Controls.ShowMessageBox(
|
||||
"Roblox has now finished rolling out the new game client update, featuring 64-bit support and the Hyperion anticheat. ReShade does not work with this update, and so it has now been disabled and removed from Bloxstrap.\n\n"+
|
||||
"Your ReShade configuration files will still be saved, and you can locate them by opening the folder where Bloxstrap is installed to, and navigating to the Integrations folder. You can choose to delete these if you want.",
|
||||
MessageBoxImage.Warning
|
||||
);
|
||||
@ -901,10 +954,85 @@ namespace Bloxstrap
|
||||
if (!Directory.Exists(modFolder))
|
||||
Directory.CreateDirectory(modFolder);
|
||||
|
||||
bool appDisabled = App.Settings.Prop.UseDisableAppPatch && !_launchCommandLine.Contains("--deeplink");
|
||||
|
||||
// cursors
|
||||
await CheckModPreset(App.Settings.Prop.CursorType == CursorType.From2006, @"content\textures\Cursors\KeyboardMouse\ArrowCursor.png", "Cursor.From2006.ArrowCursor.png");
|
||||
await CheckModPreset(App.Settings.Prop.CursorType == CursorType.From2006, @"content\textures\Cursors\KeyboardMouse\ArrowFarCursor.png", "Cursor.From2006.ArrowFarCursor.png");
|
||||
await CheckModPreset(App.Settings.Prop.CursorType == CursorType.From2013, @"content\textures\Cursors\KeyboardMouse\ArrowCursor.png", "Cursor.From2013.ArrowCursor.png");
|
||||
await CheckModPreset(App.Settings.Prop.CursorType == CursorType.From2013, @"content\textures\Cursors\KeyboardMouse\ArrowFarCursor.png", "Cursor.From2013.ArrowFarCursor.png");
|
||||
|
||||
// character sounds
|
||||
await CheckModPreset(App.Settings.Prop.UseOldCharacterSounds, @"content\sounds\action_footsteps_plastic.mp3", "OldWalk.mp3");
|
||||
await CheckModPreset(App.Settings.Prop.UseOldCharacterSounds, @"content\sounds\action_jump.mp3", "OldJump.mp3");
|
||||
await CheckModPreset(App.Settings.Prop.UseOldCharacterSounds, @"content\sounds\action_falling.mp3", "Empty.mp3");
|
||||
await CheckModPreset(App.Settings.Prop.UseOldCharacterSounds, @"content\sounds\action_jump_land.mp3", "Empty.mp3");
|
||||
await CheckModPreset(App.Settings.Prop.UseOldCharacterSounds, @"content\sounds\action_swim.mp3", "Empty.mp3");
|
||||
await CheckModPreset(App.Settings.Prop.UseOldCharacterSounds, @"content\sounds\impact_water.mp3", "Empty.mp3");
|
||||
await CheckModPreset(App.Settings.Prop.UseOldDeathSound, @"content\sounds\ouch.ogg", "OldDeath.ogg");
|
||||
await CheckModPreset(App.Settings.Prop.UseOldMouseCursor, @"content\textures\Cursors\KeyboardMouse\ArrowCursor.png", "OldCursor.png");
|
||||
await CheckModPreset(App.Settings.Prop.UseOldMouseCursor, @"content\textures\Cursors\KeyboardMouse\ArrowFarCursor.png", "OldFarCursor.png");
|
||||
await CheckModPreset(App.Settings.Prop.UseDisableAppPatch, @"ExtraContent\places\Mobile.rbxl", "");
|
||||
|
||||
// Mobile.rbxl
|
||||
await CheckModPreset(appDisabled, @"ExtraContent\places\Mobile.rbxl", "");
|
||||
await CheckModPreset(App.Settings.Prop.UseOldAvatarBackground && !appDisabled, @"ExtraContent\places\Mobile.rbxl", "OldAvatarBackground.rbxl");
|
||||
|
||||
// emoji presets are downloaded remotely from github due to how large they are
|
||||
string contentFonts = Path.Combine(Directories.Modifications, "content\\fonts");
|
||||
string emojiFontLocation = Path.Combine(contentFonts, "TwemojiMozilla.ttf");
|
||||
string emojiFontHash = File.Exists(emojiFontLocation) ? Utility.MD5Hash.FromFile(emojiFontLocation) : "";
|
||||
|
||||
if (App.Settings.Prop.EmojiType == EmojiType.Default && EmojiTypeEx.Hashes.Values.Contains(emojiFontHash))
|
||||
{
|
||||
File.Delete(emojiFontLocation);
|
||||
}
|
||||
else if (App.Settings.Prop.EmojiType != EmojiType.Default && emojiFontHash != App.Settings.Prop.EmojiType.GetHash())
|
||||
{
|
||||
if (emojiFontHash != "")
|
||||
File.Delete(emojiFontLocation);
|
||||
|
||||
Directory.CreateDirectory(contentFonts);
|
||||
|
||||
var response = await App.HttpClient.GetAsync(App.Settings.Prop.EmojiType.GetUrl());
|
||||
await using var fileStream = new FileStream(emojiFontLocation, FileMode.CreateNew);
|
||||
await response.Content.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
// check custom font mod
|
||||
// instead of replacing the fonts themselves, we'll just alter the font family manifests
|
||||
|
||||
string modFontFamiliesFolder = Path.Combine(Directories.Modifications, "content\\fonts\\families");
|
||||
string customFontLocation = Path.Combine(Directories.Modifications, "content\\fonts\\CustomFont.ttf");
|
||||
|
||||
if (File.Exists(customFontLocation))
|
||||
{
|
||||
App.Logger.WriteLine("[Bootstrapper::ApplyModifications] Begin font check");
|
||||
|
||||
Directory.CreateDirectory(modFontFamiliesFolder);
|
||||
|
||||
foreach (string jsonFilePath in Directory.GetFiles(Path.Combine(_versionFolder, "content\\fonts\\families")))
|
||||
{
|
||||
string jsonFilename = Path.GetFileName(jsonFilePath);
|
||||
string modFilepath = Path.Combine(modFontFamiliesFolder, jsonFilename);
|
||||
|
||||
if (File.Exists(modFilepath))
|
||||
continue;
|
||||
|
||||
FontFamily? fontFamilyData = JsonSerializer.Deserialize<FontFamily>(File.ReadAllText(jsonFilePath));
|
||||
|
||||
if (fontFamilyData is null)
|
||||
continue;
|
||||
|
||||
foreach (FontFace fontFace in fontFamilyData.Faces)
|
||||
fontFace.AssetId = "rbxasset://fonts/CustomFont.ttf";
|
||||
|
||||
File.WriteAllText(modFilepath, JsonSerializer.Serialize(fontFamilyData, new JsonSerializerOptions { WriteIndented = true }));
|
||||
}
|
||||
|
||||
App.Logger.WriteLine("[Bootstrapper::ApplyModifications] End font check");
|
||||
}
|
||||
else if (Directory.Exists(modFontFamiliesFolder))
|
||||
{
|
||||
Directory.Delete(modFontFamiliesFolder, true);
|
||||
}
|
||||
|
||||
foreach (string file in Directory.GetFiles(modFolder, "*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
@ -929,7 +1057,7 @@ namespace Bloxstrap
|
||||
|
||||
if (File.Exists(fileVersionFolder))
|
||||
{
|
||||
if (Utilities.MD5File(fileModFolder) == Utilities.MD5File(fileVersionFolder))
|
||||
if (Utility.MD5Hash.FromFile(fileModFolder) == Utility.MD5Hash.FromFile(fileVersionFolder))
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -980,26 +1108,26 @@ namespace Bloxstrap
|
||||
|
||||
private static async Task CheckModPreset(bool condition, string location, string name)
|
||||
{
|
||||
string modFolderLocation = Path.Combine(Directories.Modifications, location);
|
||||
byte[] binaryData = string.IsNullOrEmpty(name) ? Array.Empty<byte>() : await Resource.Get(name);
|
||||
string fullLocation = Path.Combine(Directories.Modifications, location);
|
||||
string fileHash = File.Exists(fullLocation) ? Utility.MD5Hash.FromFile(fullLocation) : "";
|
||||
|
||||
if (condition)
|
||||
byte[] embeddedData = string.IsNullOrEmpty(name) ? Array.Empty<byte>() : await Resource.Get(name);
|
||||
string embeddedHash = Utility.MD5Hash.FromBytes(embeddedData);
|
||||
|
||||
if (!condition)
|
||||
{
|
||||
if (!File.Exists(modFolderLocation))
|
||||
{
|
||||
string? directory = Path.GetDirectoryName(modFolderLocation);
|
||||
if (fileHash != "" && fileHash == embeddedHash)
|
||||
File.Delete(fullLocation);
|
||||
|
||||
if (directory is null)
|
||||
return;
|
||||
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
await File.WriteAllBytesAsync(modFolderLocation, binaryData);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (File.Exists(modFolderLocation) && Utilities.MD5File(modFolderLocation) == Utilities.MD5Data(binaryData))
|
||||
|
||||
if (fileHash != embeddedHash)
|
||||
{
|
||||
File.Delete(modFolderLocation);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullLocation)!);
|
||||
File.Delete(fullLocation);
|
||||
|
||||
await File.WriteAllBytesAsync(fullLocation, embeddedData);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1016,7 +1144,8 @@ namespace Bloxstrap
|
||||
{
|
||||
FileInfo file = new(packageLocation);
|
||||
|
||||
string calculatedMD5 = Utilities.MD5File(packageLocation);
|
||||
string calculatedMD5 = Utility.MD5Hash.FromFile(packageLocation);
|
||||
|
||||
if (calculatedMD5 != package.Signature)
|
||||
{
|
||||
App.Logger.WriteLine($"[Bootstrapper::DownloadPackage] {package.Name} is corrupted ({calculatedMD5} != {package.Signature})! Deleting and re-downloading...");
|
||||
@ -1026,7 +1155,7 @@ namespace Bloxstrap
|
||||
{
|
||||
App.Logger.WriteLine($"[Bootstrapper::DownloadPackage] {package.Name} is already downloaded, skipping...");
|
||||
_totalDownloadedBytes += package.PackedSize;
|
||||
UpdateProgressbar();
|
||||
UpdateProgressBar();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -1038,7 +1167,7 @@ namespace Bloxstrap
|
||||
App.Logger.WriteLine($"[Bootstrapper::DownloadPackage] Found existing version of {package.Name} ({robloxPackageLocation})! Copying to Downloads folder...");
|
||||
File.Copy(robloxPackageLocation, packageLocation);
|
||||
_totalDownloadedBytes += package.PackedSize;
|
||||
UpdateProgressbar();
|
||||
UpdateProgressBar();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1070,7 +1199,7 @@ namespace Bloxstrap
|
||||
await fileStream.WriteAsync(buffer, 0, bytesRead, _cancelTokenSource.Token);
|
||||
|
||||
_totalDownloadedBytes += bytesRead;
|
||||
UpdateProgressbar();
|
||||
UpdateProgressBar();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,128 +0,0 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using Bloxstrap.Extensions;
|
||||
|
||||
namespace Bloxstrap.Dialogs
|
||||
{
|
||||
public class BootstrapperDialogForm : Form, IBootstrapperDialog
|
||||
{
|
||||
public Bootstrapper? Bootstrapper { get; set; }
|
||||
|
||||
#region UI Elements
|
||||
protected virtual string _message { get; set; } = "Please wait...";
|
||||
protected virtual ProgressBarStyle _progressStyle { get; set; }
|
||||
protected virtual int _progressValue { get; set; }
|
||||
protected virtual bool _cancelEnabled { get; set; }
|
||||
|
||||
public string Message
|
||||
{
|
||||
get => _message;
|
||||
set
|
||||
{
|
||||
if (this.InvokeRequired)
|
||||
this.Invoke(() => _message = value);
|
||||
else
|
||||
_message = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ProgressBarStyle ProgressStyle
|
||||
{
|
||||
get => _progressStyle;
|
||||
set
|
||||
{
|
||||
if (this.InvokeRequired)
|
||||
this.Invoke(() => _progressStyle = value);
|
||||
else
|
||||
_progressStyle = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int ProgressValue
|
||||
{
|
||||
get => _progressValue;
|
||||
set
|
||||
{
|
||||
if (this.InvokeRequired)
|
||||
this.Invoke(() => _progressValue = value);
|
||||
else
|
||||
_progressValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CancelEnabled
|
||||
{
|
||||
get => _cancelEnabled;
|
||||
set
|
||||
{
|
||||
if (this.InvokeRequired)
|
||||
this.Invoke(() => _cancelEnabled = value);
|
||||
else
|
||||
_cancelEnabled = value;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void ScaleWindow()
|
||||
{
|
||||
this.Size = this.MinimumSize = this.MaximumSize = WindowScaling.GetScaledSize(this.Size);
|
||||
|
||||
foreach (Control control in this.Controls)
|
||||
{
|
||||
control.Size = WindowScaling.GetScaledSize(control.Size);
|
||||
control.Location = WindowScaling.GetScaledPoint(control.Location);
|
||||
control.Padding = WindowScaling.GetScaledPadding(control.Padding);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetupDialog()
|
||||
{
|
||||
this.Text = App.Settings.Prop.BootstrapperTitle;
|
||||
this.Icon = App.Settings.Prop.BootstrapperIcon.GetIcon();
|
||||
}
|
||||
|
||||
public void ButtonCancel_Click(object? sender, EventArgs e)
|
||||
{
|
||||
Bootstrapper?.CancelInstall();
|
||||
this.Close();
|
||||
}
|
||||
|
||||
#region IBootstrapperDialog Methods
|
||||
public void ShowBootstrapper() => this.ShowDialog();
|
||||
|
||||
public virtual void CloseBootstrapper()
|
||||
{
|
||||
if (this.InvokeRequired)
|
||||
this.Invoke(CloseBootstrapper);
|
||||
else
|
||||
this.Close();
|
||||
}
|
||||
|
||||
public virtual void ShowSuccess(string message)
|
||||
{
|
||||
App.ShowMessageBox(message, MessageBoxImage.Information);
|
||||
App.Terminate();
|
||||
}
|
||||
|
||||
public virtual void ShowError(string message)
|
||||
{
|
||||
App.ShowMessageBox($"An error occurred while starting Roblox\n\nDetails: {message}", MessageBoxImage.Error);
|
||||
App.Terminate(Bootstrapper.ERROR_INSTALL_FAILURE);
|
||||
}
|
||||
|
||||
public void PromptShutdown()
|
||||
{
|
||||
MessageBoxResult result = App.ShowMessageBox(
|
||||
"Roblox is currently running, but needs to close. Would you like close Roblox now?",
|
||||
MessageBoxImage.Information,
|
||||
MessageBoxButton.OKCancel
|
||||
);
|
||||
|
||||
if (result != MessageBoxResult.OK)
|
||||
Environment.Exit(Bootstrapper.ERROR_INSTALL_USEREXIT);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Bloxstrap
|
||||
namespace Bloxstrap
|
||||
{
|
||||
static class Directories
|
||||
{
|
||||
@ -15,23 +12,27 @@ namespace Bloxstrap
|
||||
|
||||
public static string Base { get; private set; } = "";
|
||||
public static string Downloads { get; private set; } = "";
|
||||
public static string Logs { get; private set; } = "";
|
||||
public static string Integrations { get; private set; } = "";
|
||||
public static string Versions { get; private set; } = "";
|
||||
public static string Modifications { get; private set; } = "";
|
||||
|
||||
public static string Application { get; private set; } = "";
|
||||
|
||||
public static bool Initialized => string.IsNullOrEmpty(Base);
|
||||
public static bool Initialized => !String.IsNullOrEmpty(Base);
|
||||
|
||||
public static void Initialize(string baseDirectory)
|
||||
{
|
||||
Base = baseDirectory;
|
||||
Downloads = Path.Combine(Base, "Downloads");
|
||||
Logs = Path.Combine(Base, "Logs");
|
||||
Integrations = Path.Combine(Base, "Integrations");
|
||||
Versions = Path.Combine(Base, "Versions");
|
||||
Modifications = Path.Combine(Base, "Modifications");
|
||||
|
||||
Application = Path.Combine(Base, $"{App.ProjectName}.exe");
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
public enum BootstrapperIcon
|
||||
{
|
||||
IconBloxstrap,
|
||||
Icon2009,
|
||||
Icon2008,
|
||||
Icon2011,
|
||||
IconEarly2015,
|
||||
IconLate2015,
|
||||
|
@ -3,9 +3,10 @@
|
||||
public enum BootstrapperStyle
|
||||
{
|
||||
VistaDialog,
|
||||
LegacyDialog2009,
|
||||
LegacyDialog2008,
|
||||
LegacyDialog2011,
|
||||
ProgressDialog,
|
||||
FluentDialog
|
||||
FluentDialog,
|
||||
ByfronDialog
|
||||
}
|
||||
}
|
||||
|
9
Bloxstrap/Enums/CursorType.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Bloxstrap.Enums
|
||||
{
|
||||
public enum CursorType
|
||||
{
|
||||
Default,
|
||||
From2006,
|
||||
From2013
|
||||
}
|
||||
}
|
11
Bloxstrap/Enums/EmojiType.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Bloxstrap.Enums
|
||||
{
|
||||
public enum EmojiType
|
||||
{
|
||||
Default,
|
||||
Catmoji,
|
||||
Windows11,
|
||||
Windows10,
|
||||
Windows8
|
||||
}
|
||||
}
|
14
Bloxstrap/Enums/ErrorCode.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace Bloxstrap.Enums
|
||||
{
|
||||
// https://learn.microsoft.com/en-us/windows/win32/msi/error-codes
|
||||
// https://i-logic.com/serial/errorcodes.htm
|
||||
// just the ones that we're interested in
|
||||
|
||||
public enum ErrorCode
|
||||
{
|
||||
ERROR_SUCCESS = 0,
|
||||
ERROR_INSTALL_USEREXIT = 1602,
|
||||
ERROR_INSTALL_FAILURE = 1603,
|
||||
ERROR_CANCELLED = 1223
|
||||
}
|
||||
}
|
@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
using Bloxstrap.Enums;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Bloxstrap.Extensions
|
||||
{
|
||||
@ -33,7 +30,7 @@ namespace Bloxstrap.Extensions
|
||||
return icon switch
|
||||
{
|
||||
BootstrapperIcon.IconBloxstrap => Properties.Resources.IconBloxstrap,
|
||||
BootstrapperIcon.Icon2009 => Properties.Resources.Icon2009,
|
||||
BootstrapperIcon.Icon2008 => Properties.Resources.Icon2008,
|
||||
BootstrapperIcon.Icon2011 => Properties.Resources.Icon2011,
|
||||
BootstrapperIcon.IconEarly2015 => Properties.Resources.IconEarly2015,
|
||||
BootstrapperIcon.IconLate2015 => Properties.Resources.IconLate2015,
|
||||
|
@ -1,21 +1,7 @@
|
||||
using Bloxstrap.Dialogs;
|
||||
using Bloxstrap.Enums;
|
||||
|
||||
namespace Bloxstrap.Extensions
|
||||
namespace Bloxstrap.Extensions
|
||||
{
|
||||
static class BootstrapperStyleEx
|
||||
{
|
||||
public static IBootstrapperDialog GetNew(this BootstrapperStyle bootstrapperStyle)
|
||||
{
|
||||
return bootstrapperStyle switch
|
||||
{
|
||||
BootstrapperStyle.VistaDialog => new VistaDialog(),
|
||||
BootstrapperStyle.LegacyDialog2009 => new LegacyDialog2009(),
|
||||
BootstrapperStyle.LegacyDialog2011 => new LegacyDialog2011(),
|
||||
BootstrapperStyle.ProgressDialog => new ProgressDialog(),
|
||||
BootstrapperStyle.FluentDialog => new FluentDialog(),
|
||||
_ => new FluentDialog()
|
||||
};
|
||||
}
|
||||
public static IBootstrapperDialog GetNew(this BootstrapperStyle bootstrapperStyle) => Controls.GetBootstrapperDialog(bootstrapperStyle);
|
||||
}
|
||||
}
|
||||
|
12
Bloxstrap/Extensions/CursorTypeEx.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Bloxstrap.Extensions
|
||||
{
|
||||
static class CursorTypeEx
|
||||
{
|
||||
public static IReadOnlyDictionary<string, CursorType> Selections => new Dictionary<string, CursorType>
|
||||
{
|
||||
{ "Default", CursorType.Default },
|
||||
{ "2013 (Angular)", CursorType.From2013 },
|
||||
{ "2006 (Cartoony)", CursorType.From2006 },
|
||||
};
|
||||
}
|
||||
}
|
10
Bloxstrap/Extensions/DateTimeEx.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Bloxstrap.Extensions
|
||||
{
|
||||
static class DateTimeEx
|
||||
{
|
||||
public static string ToFriendlyString(this DateTime dateTime)
|
||||
{
|
||||
return dateTime.ToString("dddd, d MMMM yyyy 'at' h:mm:ss tt");
|
||||
}
|
||||
}
|
||||
}
|
40
Bloxstrap/Extensions/EmojiTypeEx.cs
Normal file
@ -0,0 +1,40 @@
|
||||
namespace Bloxstrap.Extensions
|
||||
{
|
||||
static class EmojiTypeEx
|
||||
{
|
||||
public static IReadOnlyDictionary<string, EmojiType> Selections => new Dictionary<string, EmojiType>
|
||||
{
|
||||
{ "Default (Twemoji)", EmojiType.Default },
|
||||
{ "Catmoji", EmojiType.Catmoji },
|
||||
{ "Windows 11", EmojiType.Windows11 },
|
||||
{ "Windows 10", EmojiType.Windows10 },
|
||||
{ "Windows 8", EmojiType.Windows8 },
|
||||
};
|
||||
|
||||
public static IReadOnlyDictionary<EmojiType, string> Filenames => new Dictionary<EmojiType, string>
|
||||
{
|
||||
{ EmojiType.Catmoji, "Catmoji.ttf" },
|
||||
{ EmojiType.Windows11, "Win1122H2SegoeUIEmoji.ttf" },
|
||||
{ EmojiType.Windows10, "Win10April2018SegoeUIEmoji.ttf" },
|
||||
{ EmojiType.Windows8, "Win8.1SegoeUIEmoji.ttf" },
|
||||
};
|
||||
|
||||
public static IReadOnlyDictionary<EmojiType, string> Hashes => new Dictionary<EmojiType, string>
|
||||
{
|
||||
{ EmojiType.Catmoji, "98138f398a8cde897074dd2b8d53eca0" },
|
||||
{ EmojiType.Windows11, "d50758427673578ddf6c9edcdbf367f5" },
|
||||
{ EmojiType.Windows10, "d8a7eecbebf9dfdf622db8ccda63aff5" },
|
||||
{ EmojiType.Windows8, "2b01c6caabbe95afc92aa63b9bf100f3" },
|
||||
};
|
||||
|
||||
public static string GetHash(this EmojiType emojiType) => Hashes[emojiType];
|
||||
|
||||
public static string GetUrl(this EmojiType emojiType)
|
||||
{
|
||||
if (emojiType == EmojiType.Default)
|
||||
return "";
|
||||
|
||||
return $"https://github.com/NikSavchenk0/rbxcustom-fontemojis/raw/8a552f4aaaecfa58d6bd9b0540e1ac16e81faadb/{Filenames[emojiType]}";
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Media;
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Microsoft.Win32;
|
||||
using Bloxstrap.Enums;
|
||||
|
||||
namespace Bloxstrap.Extensions
|
||||
{
|
||||
|
167
Bloxstrap/FastFlagManager.cs
Normal file
@ -0,0 +1,167 @@
|
||||
namespace Bloxstrap
|
||||
{
|
||||
public class FastFlagManager : JsonManager<Dictionary<string, object>>
|
||||
{
|
||||
public override string FileLocation => Path.Combine(Directories.Modifications, "ClientSettings\\ClientAppSettings.json");
|
||||
|
||||
// we put any changes we want to make to fastflags here
|
||||
// these will apply after bloxstrap finishes installing or after the menu closes
|
||||
// to delete a fastflag, set the value to null
|
||||
public Dictionary<string, object?> Changes = new();
|
||||
|
||||
// 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]}}";
|
||||
|
||||
// only one missing here is Metal because lol
|
||||
public static IReadOnlyDictionary<string, string> RenderingModes => new Dictionary<string, string>
|
||||
{
|
||||
{ "Automatic", "" },
|
||||
{ "Vulkan", "FFlagDebugGraphicsPreferVulkan" },
|
||||
{ "Direct3D 11", "FFlagDebugGraphicsPreferD3D11" },
|
||||
{ "Direct3D 10", "FFlagDebugGraphicsPreferD3D11FL10" },
|
||||
{ "OpenGL", "FFlagDebugGraphicsPreferOpenGL" }
|
||||
};
|
||||
|
||||
public static IReadOnlyDictionary<string, string> LightingTechnologies => new Dictionary<string, string>
|
||||
{
|
||||
{ "Chosen by game", "" },
|
||||
{ "Voxel (Phase 1)", "DFFlagDebugRenderForceTechnologyVoxel" },
|
||||
{ "ShadowMap (Phase 2)", "FFlagDebugForceFutureIsBrightPhase2" },
|
||||
{ "Future (Phase 3)", "FFlagDebugForceFutureIsBrightPhase3" }
|
||||
};
|
||||
|
||||
// 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?
|
||||
public static IReadOnlyDictionary<string, Dictionary<string, string?>> IGMenuVersions => new Dictionary<string, Dictionary<string, string?>>
|
||||
{
|
||||
{
|
||||
"Default",
|
||||
new Dictionary<string, string?>
|
||||
{
|
||||
{ "FFlagDisableNewIGMinDUA", null },
|
||||
{ "FFlagEnableInGameMenuControls", null },
|
||||
{ "FFlagEnableV3MenuABTest3", null },
|
||||
{ "FFlagEnableMenuControlsABTest", null }
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"Version 1 (2015)",
|
||||
new Dictionary<string, string?>
|
||||
{
|
||||
{ "FFlagDisableNewIGMinDUA", "True" },
|
||||
{ "FFlagEnableInGameMenuControls", "False" },
|
||||
{ "FFlagEnableV3MenuABTest3", "False" },
|
||||
{ "FFlagEnableMenuControlsABTest", "False" }
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"Version 2 (2020)",
|
||||
new Dictionary<string, string?>
|
||||
{
|
||||
{ "FFlagDisableNewIGMinDUA", "False" },
|
||||
{ "FFlagEnableInGameMenuControls", "False" },
|
||||
{ "FFlagEnableV3MenuABTest3", "False" },
|
||||
{ "FFlagEnableMenuControlsABTest", "False" }
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"Version 4 (2023)",
|
||||
new Dictionary<string, string?>
|
||||
{
|
||||
{ "FFlagDisableNewIGMinDUA", "True" },
|
||||
{ "FFlagEnableInGameMenuControls", "True" },
|
||||
{ "FFlagEnableV3MenuABTest3", "True" },
|
||||
{ "FFlagEnableMenuControlsABTest", "True" }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// all fflags are stored as strings
|
||||
// to delete a flag, set the value as null
|
||||
public void SetValue(string key, object? value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
Changes[key] = null;
|
||||
App.Logger.WriteLine($"[FastFlagManager::SetValue] Deletion of '{key}' is pending");
|
||||
}
|
||||
else
|
||||
{
|
||||
Changes[key] = value.ToString();
|
||||
App.Logger.WriteLine($"[FastFlagManager::SetValue] Value change for '{key}' to '{value}' is pending");
|
||||
}
|
||||
}
|
||||
|
||||
// this will set the flag to the corresponding value if the condition is true
|
||||
// if the condition is not true, the flag will be erased
|
||||
public void SetValueIf(bool condition, string key, object? value)
|
||||
{
|
||||
if (condition)
|
||||
SetValue(key, value);
|
||||
else if (GetValue(key) is not null)
|
||||
SetValue(key, null);
|
||||
}
|
||||
|
||||
public void SetValueOnce(string key, object? value)
|
||||
{
|
||||
if (GetValue(key) is null)
|
||||
SetValue(key, value);
|
||||
}
|
||||
|
||||
// this returns null if the fflag doesn't exist
|
||||
public string? GetValue(string key)
|
||||
{
|
||||
// check if we have an updated change for it pushed first
|
||||
if (Changes.TryGetValue(key, out object? changedValue))
|
||||
return changedValue?.ToString();
|
||||
|
||||
if (Prop.TryGetValue(key, out object? value) && value is not null)
|
||||
return value.ToString();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
base.Load();
|
||||
|
||||
// set to 9999 by default if it doesnt already exist
|
||||
SetValueOnce("DFIntTaskSchedulerTargetFps", 9999);
|
||||
SetValueOnce("FFlagHandleAltEnterFullscreenManually", "False");
|
||||
SetValueOnce("DFFlagDisableDPIScale", "True");
|
||||
SetValueOnce("DFFlagVariableDPIScale2", "False");
|
||||
}
|
||||
|
||||
public override void Save()
|
||||
{
|
||||
// reload for any changes made while the menu was open
|
||||
Load();
|
||||
|
||||
if (Changes.Count == 0)
|
||||
{
|
||||
App.Logger.WriteLine($"[FastFlagManager::Save] No changes to apply, aborting.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var change in Changes)
|
||||
{
|
||||
if (change.Value is null)
|
||||
{
|
||||
App.Logger.WriteLine($"[FastFlagManager::Save] Removing '{change.Key}'");
|
||||
Prop.Remove(change.Key);
|
||||
continue;
|
||||
}
|
||||
|
||||
App.Logger.WriteLine($"[FastFlagManager::Save] Setting '{change.Key}' to '{change.Value}'");
|
||||
Prop[change.Key] = change.Value;
|
||||
}
|
||||
|
||||
base.Save();
|
||||
|
||||
Changes.Clear();
|
||||
}
|
||||
}
|
||||
}
|
22
Bloxstrap/GlobalUsings.cs
Normal file
@ -0,0 +1,22 @@
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Diagnostics;
|
||||
global using System.IO;
|
||||
global using System.IO.Compression;
|
||||
global using System.Text;
|
||||
global using System.Text.Json;
|
||||
global using System.Text.Json.Serialization;
|
||||
global using System.Text.RegularExpressions;
|
||||
global using System.Linq;
|
||||
global using System.Net;
|
||||
global using System.Net.Http;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
|
||||
global using Bloxstrap.Enums;
|
||||
global using Bloxstrap.Extensions;
|
||||
global using Bloxstrap.Models;
|
||||
global using Bloxstrap.Models.Attributes;
|
||||
global using Bloxstrap.Models.RobloxApi;
|
||||
global using Bloxstrap.UI;
|
||||
global using Bloxstrap.Utility;
|
22
Bloxstrap/HttpClientLoggingHandler.cs
Normal file
@ -0,0 +1,22 @@
|
||||
namespace Bloxstrap
|
||||
{
|
||||
internal class HttpClientLoggingHandler : MessageProcessingHandler
|
||||
{
|
||||
public HttpClientLoggingHandler(HttpMessageHandler innerHandler)
|
||||
: base(innerHandler)
|
||||
{
|
||||
}
|
||||
|
||||
protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
App.Logger.WriteLine($"[HttpClientLoggingHandler::HttpRequestMessage] {request.Method} {request.RequestUri}");
|
||||
return request;
|
||||
}
|
||||
|
||||
protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken)
|
||||
{
|
||||
App.Logger.WriteLine($"[HttpClientLoggingHandler::HttpResponseMessage] {(int)response.StatusCode} {response.ReasonPhrase} {response.RequestMessage!.RequestUri}");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using DiscordRPC;
|
||||
|
||||
using Bloxstrap.Models.RobloxApi;
|
||||
using DiscordRPC;
|
||||
|
||||
namespace Bloxstrap.Integrations
|
||||
{
|
||||
@ -14,6 +7,8 @@ namespace Bloxstrap.Integrations
|
||||
private readonly DiscordRpcClient _rpcClient = new("1005469189907173486");
|
||||
private readonly RobloxActivity _activityWatcher;
|
||||
|
||||
private RichPresence? _currentPresence;
|
||||
private string? _initialStatus;
|
||||
private long _currentUniverseId;
|
||||
private DateTime? _timeStartedUniverse;
|
||||
|
||||
@ -21,8 +16,9 @@ namespace Bloxstrap.Integrations
|
||||
{
|
||||
_activityWatcher = activityWatcher;
|
||||
|
||||
_activityWatcher.OnGameJoin += (_, _) => Task.Run(() => SetPresence());
|
||||
_activityWatcher.OnGameLeave += (_, _) => Task.Run(() => SetPresence());
|
||||
_activityWatcher.OnGameJoin += (_, _) => Task.Run(() => SetCurrentGame());
|
||||
_activityWatcher.OnGameLeave += (_, _) => Task.Run(() => SetCurrentGame());
|
||||
_activityWatcher.OnGameMessage += (_, message) => OnGameMessage(message);
|
||||
|
||||
_rpcClient.OnReady += (_, e) =>
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::DiscordRichPresence] Received ready from user {e.User.Username} ({e.User.ID})");
|
||||
@ -46,28 +42,77 @@ namespace Bloxstrap.Integrations
|
||||
_rpcClient.Initialize();
|
||||
}
|
||||
|
||||
public async Task<bool> SetPresence()
|
||||
public void OnGameMessage(GameMessage message)
|
||||
{
|
||||
if (message.Command == "SetPresenceStatus")
|
||||
SetStatus(message.Data);
|
||||
}
|
||||
|
||||
public void SetStatus(string status)
|
||||
{
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetStatus] Setting status to '{status}'");
|
||||
|
||||
if (_currentPresence is null)
|
||||
{
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetStatus] Presence is not set, aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
if (status.Length > 128)
|
||||
{
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetStatus] Status cannot be longer than 128 characters, aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_initialStatus is null)
|
||||
_initialStatus = _currentPresence.State;
|
||||
|
||||
string finalStatus;
|
||||
|
||||
if (string.IsNullOrEmpty(status))
|
||||
{
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetStatus] Status is empty, reverting to initial status");
|
||||
finalStatus = _initialStatus;
|
||||
}
|
||||
else
|
||||
{
|
||||
finalStatus = status;
|
||||
}
|
||||
|
||||
if (_currentPresence.State == finalStatus)
|
||||
{
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetStatus] Status is unchanged, aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
_currentPresence.State = finalStatus;
|
||||
UpdatePresence();
|
||||
}
|
||||
|
||||
public async Task<bool> SetCurrentGame()
|
||||
{
|
||||
if (!_activityWatcher.ActivityInGame)
|
||||
{
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetPresence] Clearing presence");
|
||||
_rpcClient.ClearPresence();
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Not in game, clearing presence");
|
||||
_currentPresence = null;
|
||||
_initialStatus = null;
|
||||
UpdatePresence();
|
||||
return true;
|
||||
}
|
||||
|
||||
string icon = "roblox";
|
||||
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetPresence] Setting presence for Place ID {_activityWatcher.ActivityPlaceId}");
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Setting presence for Place ID {_activityWatcher.ActivityPlaceId}");
|
||||
|
||||
var universeIdResponse = await Utilities.GetJson<UniverseIdResponse>($"https://apis.roblox.com/universes/v1/places/{_activityWatcher.ActivityPlaceId}/universe");
|
||||
var universeIdResponse = await Utility.Http.GetJson<UniverseIdResponse>($"https://apis.roblox.com/universes/v1/places/{_activityWatcher.ActivityPlaceId}/universe");
|
||||
if (universeIdResponse is null)
|
||||
{
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetPresence] Could not get Universe ID!");
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Could not get Universe ID!");
|
||||
return false;
|
||||
}
|
||||
|
||||
long universeId = universeIdResponse.UniverseId;
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetPresence] Got Universe ID as {universeId}");
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Got Universe ID as {universeId}");
|
||||
|
||||
// preserve time spent playing if we're teleporting between places in the same universe
|
||||
if (_timeStartedUniverse is null || !_activityWatcher.ActivityIsTeleport || universeId != _currentUniverseId)
|
||||
@ -76,25 +121,25 @@ namespace Bloxstrap.Integrations
|
||||
_activityWatcher.ActivityIsTeleport = false;
|
||||
_currentUniverseId = universeId;
|
||||
|
||||
var gameDetailResponse = await Utilities.GetJson<ApiArrayResponse<GameDetailResponse>>($"https://games.roblox.com/v1/games?universeIds={universeId}");
|
||||
var gameDetailResponse = await Utility.Http.GetJson<ApiArrayResponse<GameDetailResponse>>($"https://games.roblox.com/v1/games?universeIds={universeId}");
|
||||
if (gameDetailResponse is null || !gameDetailResponse.Data.Any())
|
||||
{
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetPresence] Could not get Universe info!");
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Could not get Universe info!");
|
||||
return false;
|
||||
}
|
||||
|
||||
GameDetailResponse universeDetails = gameDetailResponse.Data.ToArray()[0];
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetPresence] Got Universe details");
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Got Universe details");
|
||||
|
||||
var universeThumbnailResponse = await Utilities.GetJson<ApiArrayResponse<ThumbnailResponse>>($"https://thumbnails.roblox.com/v1/games/icons?universeIds={universeId}&returnPolicy=PlaceHolder&size=512x512&format=Png&isCircular=false");
|
||||
var universeThumbnailResponse = await Utility.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())
|
||||
{
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetPresence] Could not get Universe thumbnail info!");
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Could not get Universe thumbnail info!");
|
||||
}
|
||||
else
|
||||
{
|
||||
icon = universeThumbnailResponse.Data.ToArray()[0].ImageUrl;
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetPresence] Got Universe thumbnail as {icon}");
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::SetCurrentGame] Got Universe thumbnail as {icon}");
|
||||
}
|
||||
|
||||
List<Button> buttons = new()
|
||||
@ -119,7 +164,7 @@ namespace Bloxstrap.Integrations
|
||||
if (universeDetails.Name.Length < 2)
|
||||
universeDetails.Name = $"{universeDetails.Name}\x2800\x2800\x2800";
|
||||
|
||||
_rpcClient.SetPresence(new RichPresence
|
||||
_currentPresence = new RichPresence
|
||||
{
|
||||
Details = universeDetails.Name,
|
||||
State = $"by {universeDetails.Creator.Name}" + (universeDetails.Creator.HasVerifiedBadge ? " ☑️" : ""),
|
||||
@ -132,11 +177,27 @@ namespace Bloxstrap.Integrations
|
||||
SmallImageKey = "roblox",
|
||||
SmallImageText = "Roblox"
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
UpdatePresence();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void UpdatePresence()
|
||||
{
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::UpdatePresence] Updating presence");
|
||||
|
||||
if (_currentPresence is null)
|
||||
{
|
||||
App.Logger.WriteLine($"[DiscordRichPresence::UpdatePresence] Presence is empty, clearing");
|
||||
_rpcClient.ClearPresence();
|
||||
return;
|
||||
}
|
||||
|
||||
_rpcClient.SetPresence(_currentPresence);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
App.Logger.WriteLine("[DiscordRichPresence::Dispose] Cleaning up Discord RPC and Presence");
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows;
|
||||
|
||||
namespace Bloxstrap.Integrations
|
||||
{
|
||||
@ -39,7 +37,7 @@ namespace Bloxstrap.Integrations
|
||||
else
|
||||
message = $"Location: {locationCity}, {locationRegion}, {locationCountry}";
|
||||
|
||||
message += "\nClick to copy Job ID";
|
||||
message += "\nClick to copy Instance ID";
|
||||
|
||||
App.Logger.WriteLine($"[ServerNotifier::Notify] {message.ReplaceLineEndings("\\n")}");
|
||||
|
||||
|
@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Bloxstrap.Singletons
|
||||
namespace Bloxstrap
|
||||
{
|
||||
public class JsonManager<T> where T : new()
|
||||
{
|
||||
@ -11,7 +7,7 @@ namespace Bloxstrap.Singletons
|
||||
|
||||
public virtual void Load()
|
||||
{
|
||||
App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Load] Loading JSON from {FileLocation}...");
|
||||
App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Load] Loading from {FileLocation}...");
|
||||
|
||||
try
|
||||
{
|
||||
@ -22,28 +18,28 @@ namespace Bloxstrap.Singletons
|
||||
|
||||
Prop = settings;
|
||||
|
||||
App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Load] JSON loaded successfully!");
|
||||
App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Load] Loaded successfully!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Load] Failed to load JSON! ({ex.Message})");
|
||||
App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Load] Failed to load! ({ex.Message})");
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Save()
|
||||
{
|
||||
App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Save] Attempting to save JSON to {FileLocation}...");
|
||||
|
||||
if (!App.ShouldSaveConfigs)
|
||||
{
|
||||
App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Save] Aborted save (ShouldSave set to false)");
|
||||
App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Save] Save request ignored");
|
||||
return;
|
||||
}
|
||||
|
||||
App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Save] Saving to {FileLocation}...");
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(FileLocation)!);
|
||||
File.WriteAllText(FileLocation, JsonSerializer.Serialize(Prop, new JsonSerializerOptions { WriteIndented = true }));
|
||||
|
||||
App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Save] JSON saved!");
|
||||
App.Logger.WriteLine($"[JsonManager<{typeof(T).Name}>::Save] Save complete!");
|
||||
}
|
||||
}
|
||||
}
|
95
Bloxstrap/Logger.cs
Normal file
@ -0,0 +1,95 @@
|
||||
namespace Bloxstrap
|
||||
{
|
||||
// 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
|
||||
{
|
||||
private readonly SemaphoreSlim _semaphore = new(1, 1);
|
||||
private FileStream? _filestream;
|
||||
|
||||
public readonly List<string> Backlog = new();
|
||||
public bool Initialized = false;
|
||||
public string? FileLocation;
|
||||
|
||||
public void Initialize(bool useTempDir = false)
|
||||
{
|
||||
string directory = useTempDir ? Path.Combine(Directories.LocalAppData, "Temp") : Path.Combine(Directories.Base, "Logs");
|
||||
string timestamp = DateTime.UtcNow.ToString("yyyyMMdd'T'HHmmss'Z'");
|
||||
string filename = $"{App.ProjectName}_{timestamp}.log";
|
||||
string location = Path.Combine(directory, filename);
|
||||
|
||||
WriteLine($"[Logger::Initialize] Initializing at {location}");
|
||||
|
||||
if (Initialized)
|
||||
{
|
||||
WriteLine("[Logger::Initialize] Failed to initialize because logger is already initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
if (File.Exists(location))
|
||||
{
|
||||
WriteLine("[Logger::Initialize] Failed to initialize because log file already exists");
|
||||
return;
|
||||
}
|
||||
|
||||
_filestream = File.Open(location, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
|
||||
if (Backlog.Count > 0)
|
||||
WriteToLog(string.Join("\r\n", Backlog));
|
||||
|
||||
WriteLine($"[Logger::Initialize] Finished initializing!");
|
||||
|
||||
Initialized = true;
|
||||
FileLocation = location;
|
||||
|
||||
// clean up any logs older than a week
|
||||
if (Directories.Initialized && Directory.Exists(Directories.Logs))
|
||||
{
|
||||
foreach (FileInfo log in new DirectoryInfo(Directories.Logs).GetFiles())
|
||||
{
|
||||
if (log.LastWriteTimeUtc.AddDays(7) > DateTime.UtcNow)
|
||||
continue;
|
||||
|
||||
App.Logger.WriteLine($"[Logger::Initialize] Cleaning up old log file '{log.Name}'");
|
||||
log.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteLine(string message)
|
||||
{
|
||||
string timestamp = DateTime.UtcNow.ToString("s") + "Z";
|
||||
string outcon = $"{timestamp} {message}";
|
||||
string outlog = outcon.Replace(Directories.UserProfile, "%UserProfile%");
|
||||
|
||||
Debug.WriteLine(outcon);
|
||||
WriteToLog(outlog);
|
||||
}
|
||||
|
||||
private async void WriteToLog(string message)
|
||||
{
|
||||
if (!Initialized)
|
||||
{
|
||||
Backlog.Add(message);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _semaphore.WaitAsync();
|
||||
await _filestream!.WriteAsync(Encoding.Unicode.GetBytes($"{message}\r\n"));
|
||||
await _filestream.FlushAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
Bloxstrap/Models/Attributes/BuildMetadataAttribute.cs
Normal file
@ -0,0 +1,19 @@
|
||||
namespace Bloxstrap.Models.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Assembly)]
|
||||
public class BuildMetadataAttribute : Attribute
|
||||
{
|
||||
public DateTime Timestamp { get; set; }
|
||||
public string Machine { get; set; }
|
||||
public string CommitHash { get; set; }
|
||||
public string CommitRef { get; set; }
|
||||
|
||||
public BuildMetadataAttribute(string timestamp, string machine, string commitHash, string commitRef)
|
||||
{
|
||||
Timestamp = DateTime.Parse(timestamp).ToLocalTime();
|
||||
Machine = machine;
|
||||
CommitHash = commitHash;
|
||||
CommitRef = commitRef;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Bloxstrap.Models
|
||||
namespace Bloxstrap.Models
|
||||
{
|
||||
public class ClientVersion
|
||||
{
|
||||
@ -15,5 +12,7 @@ namespace Bloxstrap.Models
|
||||
public string BootstrapperVersion { get; set; } = null!;
|
||||
|
||||
public DateTime? Timestamp { get; set; }
|
||||
|
||||
public bool IsBehindDefaultChannel { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bloxstrap.Models
|
||||
namespace Bloxstrap.Models
|
||||
{
|
||||
public class DeployInfo
|
||||
{
|
||||
|
17
Bloxstrap/Models/FontFace.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace Bloxstrap.Models
|
||||
{
|
||||
public class FontFace
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("weight")]
|
||||
public int Weight { get; set; }
|
||||
|
||||
[JsonPropertyName("style")]
|
||||
public string Style { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("assetId")]
|
||||
public string AssetId { get; set; } = null!;
|
||||
}
|
||||
}
|
11
Bloxstrap/Models/FontFamily.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Bloxstrap.Models
|
||||
{
|
||||
public class FontFamily
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("faces")]
|
||||
public IEnumerable<FontFace> Faces { get; set; } = null!;
|
||||
}
|
||||
}
|
11
Bloxstrap/Models/GameMessage.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Bloxstrap.Models
|
||||
{
|
||||
public class GameMessage
|
||||
{
|
||||
[JsonPropertyName("command")]
|
||||
public string Command { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("data")]
|
||||
public string Data { get; set; } = null!;
|
||||
}
|
||||
}
|
@ -1,7 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Bloxstrap.Models
|
||||
namespace Bloxstrap.Models
|
||||
{
|
||||
public class GithubRelease
|
||||
{
|
||||
|
@ -4,11 +4,6 @@
|
||||
* Copyright (c) 2015-present MaximumADHD
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bloxstrap.Models
|
||||
{
|
||||
public class PackageManifest : List<Package>
|
||||
|
@ -1,7 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Bloxstrap.Models.RobloxApi
|
||||
namespace Bloxstrap.Models.RobloxApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Roblox.Web.WebAPI.Models.ApiArrayResponse
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Bloxstrap.Models.RobloxApi
|
||||
namespace Bloxstrap.Models.RobloxApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Roblox.Games.Api.Models.Response.GameCreator
|
||||
|
@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Bloxstrap.Models.RobloxApi
|
||||
namespace Bloxstrap.Models.RobloxApi
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Bloxstrap.Models.RobloxApi
|
||||
namespace Bloxstrap.Models.RobloxApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Roblox.Web.Responses.Thumbnails.ThumbnailResponse
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Bloxstrap.Models.RobloxApi
|
||||
namespace Bloxstrap.Models.RobloxApi
|
||||
{
|
||||
// lmao its just one property
|
||||
public class UniverseIdResponse
|
||||
|
@ -1,7 +1,5 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
using Bloxstrap.Enums;
|
||||
|
||||
namespace Bloxstrap.Models
|
||||
{
|
||||
public class Settings
|
||||
@ -15,6 +13,7 @@ namespace Bloxstrap.Models
|
||||
public bool CheckForUpdates { get; set; } = true;
|
||||
public bool CreateDesktopIcon { get; set; } = true;
|
||||
public bool MultiInstanceLaunching { get; set; } = false;
|
||||
public bool OhHeyYouFoundMe { get; set; } = false;
|
||||
|
||||
// channel configuration
|
||||
public string Channel { get; set; } = RobloxDeployment.DefaultChannel;
|
||||
@ -28,8 +27,11 @@ namespace Bloxstrap.Models
|
||||
|
||||
// mod preset configuration
|
||||
public bool UseOldDeathSound { get; set; } = true;
|
||||
public bool UseOldMouseCursor { get; set; } = false;
|
||||
public bool UseOldCharacterSounds { get; set; } = false;
|
||||
public bool UseDisableAppPatch { get; set; } = false;
|
||||
public bool UseOldAvatarBackground { get; set; } = false;
|
||||
public CursorType CursorType { get; set; } = CursorType.Default;
|
||||
public EmojiType EmojiType { get; set; } = EmojiType.Default;
|
||||
public bool DisableFullscreenOptimizations { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bloxstrap.Models
|
||||
namespace Bloxstrap.Models
|
||||
{
|
||||
public class State
|
||||
{
|
||||
public string LastEnrolledChannel { get; set; } = "";
|
||||
public string VersionGuid { get; set; } = "";
|
||||
public List<string> ModManifest { get; set; } = new();
|
||||
}
|
||||
|
4
Bloxstrap/Properties/Resources.Designer.cs
generated
@ -115,11 +115,11 @@ namespace Bloxstrap.Properties {
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
|
||||
/// </summary>
|
||||
internal static System.Drawing.Icon Icon2009
|
||||
internal static System.Drawing.Icon Icon2008
|
||||
{
|
||||
get
|
||||
{
|
||||
object obj = ResourceManager.GetObject("Icon2009", resourceCulture);
|
||||
object obj = ResourceManager.GetObject("Icon2008", resourceCulture);
|
||||
return ((System.Drawing.Icon)(obj));
|
||||
}
|
||||
}
|
||||
|
@ -130,8 +130,8 @@
|
||||
<data name="DarkCancelButtonHover" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\DarkCancelButtonHover.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="Icon2009" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\Icon2009.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
<data name="Icon2008" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\Icon2008.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="Icon2011" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\Icon2011.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
|
@ -18,6 +18,10 @@
|
||||
"Bloxstrap (Menu)": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "-menu"
|
||||
},
|
||||
"Bloxstrap (Deeplink)": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "roblox://experiences/start?placeId=95206881"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using System.Web;
|
||||
using System.Windows;
|
||||
|
||||
using Microsoft.Win32;
|
||||
|
||||
using Bloxstrap.Enums;
|
||||
|
||||
namespace Bloxstrap
|
||||
{
|
||||
static class ProtocolHandler
|
||||
@ -31,6 +26,8 @@ namespace Bloxstrap
|
||||
string[] keyvalPair;
|
||||
string key;
|
||||
string val;
|
||||
bool channelArgPresent = false;
|
||||
|
||||
StringBuilder commandLine = new();
|
||||
|
||||
foreach (var parameter in protocol.Split('+'))
|
||||
@ -55,23 +52,10 @@ namespace Bloxstrap
|
||||
if (key == "launchtime")
|
||||
val = "LAUNCHTIMEPLACEHOLDER";
|
||||
|
||||
if (key == "channel")
|
||||
if (key == "channel" && !String.IsNullOrEmpty(val))
|
||||
{
|
||||
if (val.ToLower() != App.Settings.Prop.Channel.ToLower() && App.Settings.Prop.ChannelChangeMode != ChannelChangeMode.Ignore)
|
||||
{
|
||||
MessageBoxResult result = App.Settings.Prop.ChannelChangeMode == ChannelChangeMode.Automatic ? MessageBoxResult.Yes : App.ShowMessageBox(
|
||||
$"{App.ProjectName} was launched with the Roblox build channel set to {val}, however your current preferred channel is {App.Settings.Prop.Channel}.\n\n" +
|
||||
$"Would you like to switch channels from {App.Settings.Prop.Channel} to {val}?",
|
||||
MessageBoxImage.Question,
|
||||
MessageBoxButton.YesNo
|
||||
);
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
App.Logger.WriteLine($"[Protocol::ParseUri] Changed Roblox build channel from {App.Settings.Prop.Channel} to {val}");
|
||||
App.Settings.Prop.Channel = val;
|
||||
}
|
||||
}
|
||||
channelArgPresent = true;
|
||||
EnrollChannel(val);
|
||||
|
||||
// we'll set the arg when launching
|
||||
continue;
|
||||
@ -80,9 +64,47 @@ namespace Bloxstrap
|
||||
commandLine.Append(UriKeyArgMap[key] + val + " ");
|
||||
}
|
||||
|
||||
if (!channelArgPresent)
|
||||
EnrollChannel(RobloxDeployment.DefaultChannel);
|
||||
|
||||
return commandLine.ToString();
|
||||
}
|
||||
|
||||
public static void ChangeChannel(string channel)
|
||||
{
|
||||
if (channel.ToLowerInvariant() == App.Settings.Prop.Channel.ToLowerInvariant())
|
||||
return;
|
||||
|
||||
if (App.Settings.Prop.ChannelChangeMode == ChannelChangeMode.Ignore)
|
||||
return;
|
||||
|
||||
if (App.Settings.Prop.ChannelChangeMode != ChannelChangeMode.Automatic)
|
||||
{
|
||||
if (channel == App.State.Prop.LastEnrolledChannel)
|
||||
return;
|
||||
|
||||
MessageBoxResult result = Controls.ShowMessageBox(
|
||||
$"Roblox is attempting to set your channel to {channel}, however your current preferred channel is {App.Settings.Prop.Channel}.\n\n" +
|
||||
$"Would you like to switch your preferred channel to {channel}?",
|
||||
MessageBoxImage.Question,
|
||||
MessageBoxButton.YesNo
|
||||
);
|
||||
|
||||
if (result != MessageBoxResult.Yes)
|
||||
return;
|
||||
}
|
||||
|
||||
App.Logger.WriteLine($"[Protocol::ParseUri] Changed Roblox build channel from {App.Settings.Prop.Channel} to {channel}");
|
||||
App.Settings.Prop.Channel = channel;
|
||||
}
|
||||
|
||||
public static void EnrollChannel(string channel)
|
||||
{
|
||||
ChangeChannel(channel);
|
||||
App.State.Prop.LastEnrolledChannel = channel;
|
||||
App.State.Save();
|
||||
}
|
||||
|
||||
public static void Register(string key, string name, string handler)
|
||||
{
|
||||
string handlerArgs = $"\"{handler}\" %1";
|
||||
|
@ -1,7 +1,4 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Bloxstrap
|
||||
{
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
After Width: | Height: | Size: 5.6 KiB |
BIN
Bloxstrap/Resources/Fonts/Rubik-VariableFont_wght.ttf
Normal file
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
BIN
Bloxstrap/Resources/Menu/StartMenuLocation.png
Normal file
After Width: | Height: | Size: 196 KiB |
BIN
Bloxstrap/Resources/MessageBox/Error.png
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
Bloxstrap/Resources/MessageBox/Information.png
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
Bloxstrap/Resources/MessageBox/Question.png
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
Bloxstrap/Resources/MessageBox/Warning.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
Bloxstrap/Resources/Mods/Cursor/From2006/ArrowCursor.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
Bloxstrap/Resources/Mods/Cursor/From2006/ArrowFarCursor.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 232 B |
Before Width: | Height: | Size: 235 B After Width: | Height: | Size: 235 B |
BIN
Bloxstrap/Resources/Mods/Empty.mp3
Normal file
BIN
Bloxstrap/Resources/Mods/OldAvatarBackground.rbxl
Normal file
BIN
Bloxstrap/Resources/Mods/OldJump.mp3
Normal file
BIN
Bloxstrap/Resources/Mods/OldWalk.mp3
Normal file
@ -1,11 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bloxstrap
|
||||
namespace Bloxstrap
|
||||
{
|
||||
public class RobloxActivity : IDisposable
|
||||
{
|
||||
@ -16,6 +9,7 @@ namespace Bloxstrap
|
||||
private const string GameJoinedEntry = "[FLog::Network] serverId:";
|
||||
private const string GameDisconnectedEntry = "[FLog::Network] Time to disconnect replication data:";
|
||||
private const string GameTeleportingEntry = "[FLog::SingleSurfaceApp] initiateTeleport";
|
||||
private const string GameMessageEntry = "[FLog::Output] [SendBloxstrapMessage]";
|
||||
|
||||
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]+";
|
||||
@ -25,6 +19,7 @@ namespace Bloxstrap
|
||||
|
||||
public event EventHandler? OnGameJoin;
|
||||
public event EventHandler? OnGameLeave;
|
||||
public event EventHandler<GameMessage>? OnGameMessage;
|
||||
|
||||
// these are values to use assuming the player isn't currently in a game
|
||||
// keep in mind ActivityIsTeleport is only reset by DiscordRichPresence when it's done accessing it
|
||||
@ -184,6 +179,37 @@ namespace Bloxstrap
|
||||
App.Logger.WriteLine($"[RobloxActivity::ExamineLogEntry] Initiating teleport to server ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
|
||||
ActivityIsTeleport = true;
|
||||
}
|
||||
else if (entry.Contains(GameMessageEntry))
|
||||
{
|
||||
string messagePlain = entry.Substring(entry.IndexOf(GameMessageEntry) + GameMessageEntry.Length + 1);
|
||||
GameMessage? message;
|
||||
|
||||
App.Logger.WriteLine($"[RobloxActivity::ExamineLogEntry] Received message: '{messagePlain}'");
|
||||
|
||||
try
|
||||
{
|
||||
message = JsonSerializer.Deserialize<GameMessage>(messagePlain);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
App.Logger.WriteLine($"[Utilities::ExamineLogEntry] Failed to parse message! (JSON deserialization threw an exception)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (message is null)
|
||||
{
|
||||
App.Logger.WriteLine($"[Utilities::ExamineLogEntry] Failed to parse message! (JSON deserialization returned null)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(message.Command))
|
||||
{
|
||||
App.Logger.WriteLine($"[Utilities::ExamineLogEntry] Failed to parse message! (Command is empty)");
|
||||
return;
|
||||
}
|
||||
|
||||
OnGameMessage?.Invoke(this, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Bloxstrap.Models;
|
||||
|
||||
namespace Bloxstrap
|
||||
namespace Bloxstrap
|
||||
{
|
||||
public static class RobloxDeployment
|
||||
{
|
||||
#region Properties
|
||||
public const string DefaultChannel = "LIVE";
|
||||
|
||||
private static Dictionary<string, ClientVersion> ClientVersionCache = new();
|
||||
|
||||
// a list of roblox delpoyment locations that we check for, in case one of them don't work
|
||||
private static List<string> BaseUrls = new()
|
||||
{
|
||||
@ -78,46 +71,55 @@ namespace Bloxstrap
|
||||
|
||||
string location = BaseUrl;
|
||||
|
||||
if (channel.ToLower() != DefaultChannel.ToLower())
|
||||
location += $"/channel/{channel.ToLower()}";
|
||||
if (channel.ToLowerInvariant() != DefaultChannel.ToLowerInvariant())
|
||||
location += $"/channel/{channel.ToLowerInvariant()}";
|
||||
|
||||
location += resource;
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
public static async Task<ClientVersion> GetInfo(string channel, bool timestamp = false)
|
||||
public static async Task<ClientVersion> GetInfo(string channel, bool extraInformation = false)
|
||||
{
|
||||
App.Logger.WriteLine($"[RobloxDeployment::GetInfo] Getting deploy info for channel {channel} (timestamp={timestamp})");
|
||||
App.Logger.WriteLine($"[RobloxDeployment::GetInfo] Getting deploy info for channel {channel} (extraInformation={extraInformation})");
|
||||
|
||||
HttpResponseMessage deployInfoResponse = await App.HttpClient.GetAsync($"https://clientsettingscdn.roblox.com/v2/client-version/WindowsPlayer/channel/{channel}");
|
||||
ClientVersion clientVersion;
|
||||
|
||||
string rawResponse = await deployInfoResponse.Content.ReadAsStringAsync();
|
||||
|
||||
if (!deployInfoResponse.IsSuccessStatusCode)
|
||||
if (ClientVersionCache.ContainsKey(channel))
|
||||
{
|
||||
// 400 = Invalid binaryType.
|
||||
// 404 = Could not find version details for binaryType.
|
||||
// 500 = Error while fetching version information.
|
||||
// either way, we throw
|
||||
App.Logger.WriteLine($"[RobloxDeployment::GetInfo] Deploy information is cached");
|
||||
clientVersion = ClientVersionCache[channel];
|
||||
}
|
||||
else
|
||||
{
|
||||
HttpResponseMessage deployInfoResponse = await App.HttpClient.GetAsync($"https://clientsettingscdn.roblox.com/v2/client-version/WindowsPlayer/channel/{channel}");
|
||||
|
||||
App.Logger.WriteLine(
|
||||
"[RobloxDeployment::GetInfo] Failed to fetch deploy info!\r\n" +
|
||||
$"\tStatus code: {deployInfoResponse.StatusCode}\r\n" +
|
||||
$"\tResponse: {rawResponse}"
|
||||
);
|
||||
string rawResponse = await deployInfoResponse.Content.ReadAsStringAsync();
|
||||
|
||||
throw new Exception($"Could not get latest deploy for channel {channel}! (HTTP {deployInfoResponse.StatusCode})");
|
||||
if (!deployInfoResponse.IsSuccessStatusCode)
|
||||
{
|
||||
// 400 = Invalid binaryType.
|
||||
// 404 = Could not find version details for binaryType.
|
||||
// 500 = Error while fetching version information.
|
||||
// either way, we throw
|
||||
|
||||
App.Logger.WriteLine(
|
||||
"[RobloxDeployment::GetInfo] Failed to fetch deploy info!\r\n" +
|
||||
$"\tStatus code: {deployInfoResponse.StatusCode}\r\n" +
|
||||
$"\tResponse: {rawResponse}"
|
||||
);
|
||||
|
||||
throw new Exception($"Could not get latest deploy for channel {channel}! (HTTP {deployInfoResponse.StatusCode})");
|
||||
}
|
||||
|
||||
clientVersion = JsonSerializer.Deserialize<ClientVersion>(rawResponse)!;
|
||||
}
|
||||
|
||||
App.Logger.WriteLine($"[RobloxDeployment::GetInfo] Got JSON: {rawResponse}");
|
||||
|
||||
ClientVersion clientVersion = JsonSerializer.Deserialize<ClientVersion>(rawResponse)!;
|
||||
|
||||
// for preferences
|
||||
if (timestamp)
|
||||
if (extraInformation && clientVersion.Timestamp is null)
|
||||
{
|
||||
App.Logger.WriteLine("[RobloxDeployment::GetInfo] Getting timestamp...");
|
||||
App.Logger.WriteLine("[RobloxDeployment::GetInfo] Getting extra information...");
|
||||
|
||||
string manifestUrl = GetLocation($"/{clientVersion.VersionGuid}-rbxPkgManifest.txt", channel);
|
||||
|
||||
@ -130,8 +132,19 @@ namespace Bloxstrap
|
||||
App.Logger.WriteLine($"[RobloxDeployment::GetInfo] {manifestUrl} - Last-Modified: {lastModified}");
|
||||
clientVersion.Timestamp = DateTime.Parse(lastModified).ToLocalTime();
|
||||
}
|
||||
|
||||
// check if channel is behind LIVE
|
||||
if (channel != DefaultChannel)
|
||||
{
|
||||
var defaultClientVersion = await GetInfo(DefaultChannel);
|
||||
|
||||
if (Utilities.CompareVersions(clientVersion.Version, defaultClientVersion.Version) == -1)
|
||||
clientVersion.IsBehindDefaultChannel = true;
|
||||
}
|
||||
}
|
||||
|
||||
ClientVersionCache[channel] = clientVersion;
|
||||
|
||||
return clientVersion;
|
||||
}
|
||||
}
|
||||
|
@ -1,162 +0,0 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Bloxstrap.Singletons
|
||||
{
|
||||
public class FastFlagManager : JsonManager<Dictionary<string, object>>
|
||||
{
|
||||
public override string FileLocation => Path.Combine(Directories.Modifications, "ClientSettings\\ClientAppSettings.json");
|
||||
|
||||
// we put any changes we want to make to fastflags here
|
||||
// these will apply after bloxstrap finishes installing or after the menu closes
|
||||
// to delete a fastflag, set the value to null
|
||||
public Dictionary<string, object?> Changes = new();
|
||||
|
||||
// only one missing here is Metal because lol
|
||||
public static IReadOnlyDictionary<string, string> RenderingModes => new Dictionary<string, string>
|
||||
{
|
||||
{ "Automatic", "" },
|
||||
{ "Direct3D 11", "FFlagDebugGraphicsPreferD3D11" },
|
||||
{ "Vulkan", "FFlagDebugGraphicsPreferVulkan" },
|
||||
{ "OpenGL", "FFlagDebugGraphicsPreferOpenGL" }
|
||||
};
|
||||
|
||||
public static IReadOnlyDictionary<string, string> LightingTechnologies => new Dictionary<string, string>
|
||||
{
|
||||
{ "Automatic", "" },
|
||||
{ "Voxel", "DFFlagDebugRenderForceTechnologyVoxel" },
|
||||
{ "Future Is Bright", "FFlagDebugForceFutureIsBrightPhase3" }
|
||||
};
|
||||
|
||||
// this is one hell of a variable definition lmao
|
||||
public static IReadOnlyDictionary<string, Dictionary<string, string?>> IGMenuVersions => new Dictionary<string, Dictionary<string, string?>>
|
||||
{
|
||||
{
|
||||
"Default",
|
||||
new Dictionary<string, string?>
|
||||
{
|
||||
{ "FFlagDisableNewIGMinDUA", null },
|
||||
{ "FFlagEnableInGameMenuV3", null }
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"Version 1 (2015)",
|
||||
new Dictionary<string, string?>
|
||||
{
|
||||
{ "FFlagDisableNewIGMinDUA", "True" },
|
||||
{ "FFlagEnableInGameMenuV3", "False" }
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"Version 2 (2020)",
|
||||
new Dictionary<string, string?>
|
||||
{
|
||||
{ "FFlagDisableNewIGMinDUA", "False" },
|
||||
{ "FFlagEnableInGameMenuV3", "False" }
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"Version 3 (2021)",
|
||||
new Dictionary<string, string?>
|
||||
{
|
||||
{ "FFlagDisableNewIGMinDUA", "False" },
|
||||
{ "FFlagEnableInGameMenuV3", "True" }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// all fflags are stored as strings
|
||||
// to delete a flag, set the value as null
|
||||
public void SetValue(string key, object? value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
Changes[key] = null;
|
||||
App.Logger.WriteLine($"[FastFlagManager::SetValue] Deletion of '{key}' is pending");
|
||||
}
|
||||
else
|
||||
{
|
||||
Changes[key] = value.ToString();
|
||||
App.Logger.WriteLine($"[FastFlagManager::SetValue] Value change for '{key}' to '{value}' is pending");
|
||||
}
|
||||
}
|
||||
|
||||
// this returns null if the fflag doesn't exist
|
||||
public string? GetValue(string key)
|
||||
{
|
||||
// check if we have an updated change for it pushed first
|
||||
if (Changes.TryGetValue(key, out object? changedValue))
|
||||
return changedValue?.ToString();
|
||||
|
||||
if (Prop.TryGetValue(key, out object? value) && value is not null)
|
||||
return value.ToString();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SetRenderingMode(string value)
|
||||
{
|
||||
foreach (var mode in RenderingModes)
|
||||
{
|
||||
if (mode.Key != "Automatic")
|
||||
SetValue(mode.Value, null);
|
||||
}
|
||||
|
||||
if (value != "Automatic")
|
||||
SetValue(RenderingModes[value], "True");
|
||||
}
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
base.Load();
|
||||
|
||||
// set to 9999 by default if it doesnt already exist
|
||||
if (GetValue("DFIntTaskSchedulerTargetFps") is null)
|
||||
SetValue("DFIntTaskSchedulerTargetFps", 9999);
|
||||
|
||||
// reshade / exclusive fullscreen requires direct3d 11 to work
|
||||
if (GetValue(RenderingModes["Direct3D 11"]) != "True" && App.FastFlags.GetValue("FFlagHandleAltEnterFullscreenManually") == "False")
|
||||
SetRenderingMode("Direct3D 11");
|
||||
}
|
||||
|
||||
public override void Save()
|
||||
{
|
||||
App.Logger.WriteLine($"[FastFlagManager::Save] Attempting to save JSON to {FileLocation}...");
|
||||
|
||||
// reload for any changes made while the menu was open
|
||||
Load();
|
||||
|
||||
if (Changes.Count == 0)
|
||||
{
|
||||
App.Logger.WriteLine($"[FastFlagManager::Save] No changes to apply, aborting.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var change in Changes)
|
||||
{
|
||||
if (change.Value is null)
|
||||
{
|
||||
App.Logger.WriteLine($"[FastFlagManager::Save] Removing '{change.Key}'");
|
||||
Prop.Remove(change.Key);
|
||||
continue;
|
||||
}
|
||||
|
||||
App.Logger.WriteLine($"[FastFlagManager::Save] Setting '{change.Key}' to '{change.Value}'");
|
||||
Prop[change.Key] = change.Value;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(FileLocation)!);
|
||||
File.WriteAllText(FileLocation, JsonSerializer.Serialize(Prop, new JsonSerializerOptions { WriteIndented = true }));
|
||||
|
||||
Changes.Clear();
|
||||
|
||||
App.Logger.WriteLine($"[FastFlagManager::Save] JSON saved!");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Bloxstrap.Singletons
|
||||
{
|
||||
// https://stackoverflow.com/a/53873141/11852173
|
||||
public class Logger
|
||||
{
|
||||
private readonly SemaphoreSlim _semaphore = new(1, 1);
|
||||
private readonly List<string> _backlog = new();
|
||||
private FileStream? _filestream;
|
||||
|
||||
public void Initialize(string filename)
|
||||
{
|
||||
if (_filestream is not null)
|
||||
throw new Exception("Logger is already initialized");
|
||||
|
||||
string? directory = Path.GetDirectoryName(filename);
|
||||
|
||||
if (directory is not null)
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
_filestream = File.Open(filename, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
|
||||
if (_backlog.Count > 0)
|
||||
WriteToLog(string.Join("\r\n", _backlog));
|
||||
|
||||
WriteLine($"[Logger::Logger] Initialized at {filename}");
|
||||
}
|
||||
|
||||
public void WriteLine(string message)
|
||||
{
|
||||
string timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||
string outcon = $"{timestamp} {message}";
|
||||
string outlog = outcon.Replace(Directories.UserProfile, "<UserProfileFolder>");
|
||||
|
||||
Debug.WriteLine(outcon);
|
||||
WriteToLog(outlog);
|
||||
}
|
||||
|
||||
private async void WriteToLog(string message)
|
||||
{
|
||||
if (_filestream is null)
|
||||
{
|
||||
_backlog.Add(message);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _semaphore.WaitAsync();
|
||||
await _filestream.WriteAsync(Encoding.Unicode.GetBytes($"{message}\r\n"));
|
||||
await _filestream.FlushAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
56
Bloxstrap/UI/Controls.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using System.Windows;
|
||||
|
||||
using Bloxstrap.UI.Elements.Bootstrapper;
|
||||
using Bloxstrap.UI.Menu;
|
||||
using Bloxstrap.UI.MessageBox;
|
||||
|
||||
namespace Bloxstrap.UI
|
||||
{
|
||||
static class Controls
|
||||
{
|
||||
public static void ShowMenu() => new MainWindow().ShowDialog();
|
||||
|
||||
public static MessageBoxResult ShowMessageBox(string message, MessageBoxImage icon = MessageBoxImage.None, MessageBoxButton buttons = MessageBoxButton.OK, MessageBoxResult defaultResult = MessageBoxResult.None)
|
||||
{
|
||||
if (App.IsQuiet)
|
||||
return defaultResult;
|
||||
|
||||
switch (App.Settings.Prop.BootstrapperStyle)
|
||||
{
|
||||
case BootstrapperStyle.FluentDialog:
|
||||
case BootstrapperStyle.ByfronDialog:
|
||||
return Application.Current.Dispatcher.Invoke(new Func<MessageBoxResult>(() =>
|
||||
{
|
||||
var messagebox = new FluentMessageBox(message, icon, buttons);
|
||||
messagebox.ShowDialog();
|
||||
return messagebox.Result;
|
||||
}));
|
||||
|
||||
default:
|
||||
return System.Windows.MessageBox.Show(message, App.ProjectName, buttons, icon);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ShowExceptionDialog(Exception exception)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
new ExceptionDialog(exception).ShowDialog();
|
||||
});
|
||||
}
|
||||
|
||||
public static IBootstrapperDialog GetBootstrapperDialog(BootstrapperStyle style)
|
||||
{
|
||||
return style switch
|
||||
{
|
||||
BootstrapperStyle.VistaDialog => new VistaDialog(),
|
||||
BootstrapperStyle.LegacyDialog2008 => new LegacyDialog2008(),
|
||||
BootstrapperStyle.LegacyDialog2011 => new LegacyDialog2011(),
|
||||
BootstrapperStyle.ProgressDialog => new ProgressDialog(),
|
||||
BootstrapperStyle.FluentDialog => new FluentDialog(),
|
||||
BootstrapperStyle.ByfronDialog => new ByfronDialog(),
|
||||
_ => new FluentDialog()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
17
Bloxstrap/UI/Elements/Bootstrapper/Base/BaseFunctions.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Bootstrapper.Base
|
||||
{
|
||||
static class BaseFunctions
|
||||
{
|
||||
public static void ShowSuccess(string message, Action? callback)
|
||||
{
|
||||
Controls.ShowMessageBox(message, MessageBoxImage.Information);
|
||||
|
||||
if (callback is not null)
|
||||
callback();
|
||||
|
||||
App.Terminate();
|
||||
}
|
||||
}
|
||||
}
|
104
Bloxstrap/UI/Elements/Bootstrapper/Base/WinFormsDialogBase.cs
Normal file
@ -0,0 +1,104 @@
|
||||
using System.Windows.Forms;
|
||||
|
||||
using Bloxstrap.UI.Utility;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Bootstrapper.Base
|
||||
{
|
||||
public class WinFormsDialogBase : Form, IBootstrapperDialog
|
||||
{
|
||||
public Bloxstrap.Bootstrapper? Bootstrapper { get; set; }
|
||||
|
||||
#region UI Elements
|
||||
protected virtual string _message { get; set; } = "Please wait...";
|
||||
protected virtual ProgressBarStyle _progressStyle { get; set; }
|
||||
protected virtual int _progressValue { get; set; }
|
||||
protected virtual bool _cancelEnabled { get; set; }
|
||||
|
||||
public string Message
|
||||
{
|
||||
get => _message;
|
||||
set
|
||||
{
|
||||
if (InvokeRequired)
|
||||
Invoke(() => _message = value);
|
||||
else
|
||||
_message = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ProgressBarStyle ProgressStyle
|
||||
{
|
||||
get => _progressStyle;
|
||||
set
|
||||
{
|
||||
if (InvokeRequired)
|
||||
Invoke(() => _progressStyle = value);
|
||||
else
|
||||
_progressStyle = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int ProgressValue
|
||||
{
|
||||
get => _progressValue;
|
||||
set
|
||||
{
|
||||
if (InvokeRequired)
|
||||
Invoke(() => _progressValue = value);
|
||||
else
|
||||
_progressValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CancelEnabled
|
||||
{
|
||||
get => _cancelEnabled;
|
||||
set
|
||||
{
|
||||
if (InvokeRequired)
|
||||
Invoke(() => _cancelEnabled = value);
|
||||
else
|
||||
_cancelEnabled = value;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void ScaleWindow()
|
||||
{
|
||||
Size = MinimumSize = MaximumSize = WindowScaling.GetScaledSize(Size);
|
||||
|
||||
foreach (Control control in Controls)
|
||||
{
|
||||
control.Size = WindowScaling.GetScaledSize(control.Size);
|
||||
control.Location = WindowScaling.GetScaledPoint(control.Location);
|
||||
control.Padding = WindowScaling.GetScaledPadding(control.Padding);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetupDialog()
|
||||
{
|
||||
Text = App.Settings.Prop.BootstrapperTitle;
|
||||
Icon = App.Settings.Prop.BootstrapperIcon.GetIcon();
|
||||
}
|
||||
|
||||
public void ButtonCancel_Click(object? sender, EventArgs e)
|
||||
{
|
||||
Bootstrapper?.CancelInstall();
|
||||
Close();
|
||||
}
|
||||
|
||||
#region IBootstrapperDialog Methods
|
||||
public void ShowBootstrapper() => ShowDialog();
|
||||
|
||||
public virtual void CloseBootstrapper()
|
||||
{
|
||||
if (InvokeRequired)
|
||||
Invoke(CloseBootstrapper);
|
||||
else
|
||||
Close();
|
||||
}
|
||||
|
||||
public virtual void ShowSuccess(string message, Action? callback) => BaseFunctions.ShowSuccess(message, callback);
|
||||
#endregion
|
||||
}
|
||||
}
|
44
Bloxstrap/UI/Elements/Bootstrapper/ByfronDialog.xaml
Normal file
@ -0,0 +1,44 @@
|
||||
<Window x:Class="Bloxstrap.UI.Elements.Bootstrapper.ByfronDialog"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
Width="600"
|
||||
Height="400"
|
||||
ResizeMode="NoResize"
|
||||
WindowStyle="None"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent">
|
||||
<Border CornerRadius="10" BorderBrush="#33393B3D" Background="{Binding Background}" BorderThickness="{Binding DialogBorder}">
|
||||
<Grid>
|
||||
<Image Source="{Binding ByfronLogoLocation}" Width="114" Height="108" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="17,13,0,0" />
|
||||
<Button Margin="15" VerticalAlignment="Top" HorizontalAlignment="Right" Width="20" Height="20" BorderThickness="0" Padding="1" Background="Transparent" BorderBrush="Transparent" Visibility="{Binding CancelButtonVisibility, Mode=OneWay}" Command="{Binding CancelInstallCommand}">
|
||||
<Path Fill="{Binding IconColor}" Stretch="Fill">
|
||||
<Path.Data>
|
||||
<PathGeometry Figures="m 6.7507011 2.1168752 c -1.0144874 0 -2.0291944 0.3884677 -2.8065471 1.1658203 -1.5547052 1.5547053 -1.5547052 4.0583888 10e-8 5.6130941 L 28.499761 33.45088 3.9441541 58.006487 c -1.5547058 1.554706 -1.5547051 4.057873 0 5.612578 1.554705 1.554705 4.0583891 1.554705 5.6130942 0 l 24.5550907 -24.55509 24.555607 24.55509 c 1.554705 1.554705 4.057871 1.554705 5.612577 0 1.554705 -1.554706 1.554705 -4.057872 0 -5.612577 L 39.725433 33.450881 64.280523 8.89579 c 1.554706 -1.5547058 1.554705 -4.0583892 0 -5.6130942 -1.554705 -1.5547051 -4.057872 -1.5547058 -5.612578 -7e-7 L 34.112338 27.838303 9.5572482 3.2826955 C 8.7798955 2.5053428 7.7651883 2.1168752 6.7507011 2.1168752 Z" FillRule="NonZero"/>
|
||||
</Path.Data>
|
||||
</Path>
|
||||
</Button>
|
||||
<TextBlock Margin="15" VerticalAlignment="Top" HorizontalAlignment="Right" FontSize="10" FontFamily="{StaticResource Rubik}" Text="{Binding VersionText}" Foreground="{Binding Foreground}" Visibility="{Binding VersionTextVisibility, Mode=OneWay}" />
|
||||
<Path Fill="{Binding IconColor}" Width="300" Height="56" Stretch="Fill">
|
||||
<Path.Data>
|
||||
<PathGeometry Figures="M38.5796 38.4043L47.7906 55.2626H30.6999L22.9226 40.8556H15.5546V55.2626H0V4.19308H28.4486C40.2169 4.19308 47.6883 10.7246 47.6883 22.4706C47.6883 30.0289 44.209 35.4433 38.5796 38.4043ZM15.5546 17.4658V27.5775H26.6066C29.8813 27.5775 31.9279 25.6369 31.9279 22.4706C31.9279 19.3043 29.8813 17.4658 26.6066 17.4658H15.5546ZM97.2175 59.0374L50.656 46.4743L63.1406 0L86.4214 6.28155L109.702 12.5631L97.2175 59.0374ZM88.4169 24.8198L75.4206 21.2449L71.9413 34.2166L84.9376 37.7925L88.4169 24.8198ZM163.019 40.8556C163.019 50.661 156.777 55.2626 147.055 55.2626H116.56V4.19308H146.032C155.753 4.19308 161.995 9.19789 161.995 17.9818C161.995 23.4973 159.949 27.1754 156.06 29.7289C160.461 31.6631 163.019 35.5455 163.019 40.8556ZM131.705 16.4498V24.008H141.83C144.593 24.008 146.231 22.7824 146.231 20.1268C146.231 17.6754 144.593 16.4498 141.83 16.4498H131.705ZM131.705 43.0059H143.064C145.725 43.0059 147.265 41.576 147.265 39.1235C147.265 36.469 145.73 35.2433 143.064 35.2433H131.705V43.0059ZM170.694 4.19308H186.246V40.1417H208.555V55.2626H170.692L170.694 4.19308ZM265.762 29.7289C265.762 34.9812 264.202 40.1156 261.278 44.4827C258.355 48.8498 254.199 52.2536 249.338 54.2636C244.476 56.2736 239.126 56.7995 233.965 55.7748C228.804 54.7501 224.063 52.2209 220.342 48.5069C216.621 44.793 214.087 40.0611 213.06 34.9098C212.034 29.7584 212.561 24.4188 214.574 19.5663C216.588 14.7138 219.998 10.5663 224.374 7.64828C228.749 4.73025 233.893 3.17276 239.156 3.17276C242.651 3.16582 246.114 3.8478 249.345 5.17958C252.575 6.51135 255.511 8.46672 257.983 10.9335C260.455 13.4003 262.415 16.3299 263.75 19.5544C265.085 22.7788 265.769 26.2346 265.762 29.7235V29.7289ZM250.208 29.7289C250.208 23.3952 245.193 18.3904 239.156 18.3904C233.118 18.3904 228.103 23.3952 228.103 29.7289C228.103 36.0626 233.118 41.0663 239.156 41.0663C245.193 41.0663 250.208 36.0551 250.208 29.7235V29.7289ZM303.216 28.9107L320 55.2626H301.472L292.267 40.2428L282.75 55.2626H263.92L281.419 29.5225L265.353 4.19308H283.875L292.369 17.9818L300.556 4.19308H318.976L303.216 28.9107Z" FillRule="NonZero"/>
|
||||
</Path.Data>
|
||||
</Path>
|
||||
<Grid Margin="0,0,0,29" VerticalAlignment="Bottom">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Margin="0,0,0,23" TextAlignment="Center" Grid.Row="0" Text="{Binding Message}" Foreground="{Binding Foreground}" FontFamily="{StaticResource Rubik}" FontSize="17" FontWeight="Light">
|
||||
<TextBlock.LayoutTransform>
|
||||
<ScaleTransform ScaleY="0.9"/>
|
||||
</TextBlock.LayoutTransform>
|
||||
</TextBlock>
|
||||
<ProgressBar Grid.Row="1" Width="480" Height="12" Foreground="{Binding Foreground}" Background="{Binding ProgressBarBackground}" BorderThickness="0" IsIndeterminate="{Binding ProgressIndeterminate}" Value="{Binding ProgressValue}"></ProgressBar>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
97
Bloxstrap/UI/Elements/Bootstrapper/ByfronDialog.xaml.cs
Normal file
@ -0,0 +1,97 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
using Bloxstrap.UI.Elements.Bootstrapper.Base;
|
||||
using Bloxstrap.UI.ViewModels.Bootstrapper;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Bootstrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for ByfronDialog.xaml
|
||||
/// </summary>
|
||||
public partial class ByfronDialog : IBootstrapperDialog
|
||||
{
|
||||
private readonly ByfronDialogViewModel _viewModel;
|
||||
|
||||
public Bloxstrap.Bootstrapper? Bootstrapper { get; set; }
|
||||
|
||||
#region UI Elements
|
||||
public string Message
|
||||
{
|
||||
get => _viewModel.Message;
|
||||
set
|
||||
{
|
||||
_viewModel.Message = value;
|
||||
_viewModel.OnPropertyChanged(nameof(_viewModel.Message));
|
||||
}
|
||||
}
|
||||
|
||||
public ProgressBarStyle ProgressStyle
|
||||
{
|
||||
get => _viewModel.ProgressIndeterminate ? ProgressBarStyle.Marquee : ProgressBarStyle.Continuous;
|
||||
set
|
||||
{
|
||||
_viewModel.ProgressIndeterminate = (value == ProgressBarStyle.Marquee);
|
||||
_viewModel.OnPropertyChanged(nameof(_viewModel.ProgressIndeterminate));
|
||||
}
|
||||
}
|
||||
|
||||
public int ProgressValue
|
||||
{
|
||||
get => _viewModel.ProgressValue;
|
||||
set
|
||||
{
|
||||
_viewModel.ProgressValue = value;
|
||||
_viewModel.OnPropertyChanged(nameof(_viewModel.ProgressValue));
|
||||
}
|
||||
}
|
||||
|
||||
public bool CancelEnabled
|
||||
{
|
||||
get => _viewModel.CancelEnabled;
|
||||
set
|
||||
{
|
||||
_viewModel.CancelEnabled = value;
|
||||
|
||||
_viewModel.OnPropertyChanged(nameof(_viewModel.CancelEnabled));
|
||||
_viewModel.OnPropertyChanged(nameof(_viewModel.CancelButtonVisibility));
|
||||
|
||||
_viewModel.OnPropertyChanged(nameof(_viewModel.VersionTextVisibility));
|
||||
_viewModel.OnPropertyChanged(nameof(_viewModel.VersionText));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public ByfronDialog()
|
||||
{
|
||||
_viewModel = new ByfronDialogViewModel(this);
|
||||
DataContext = _viewModel;
|
||||
Title = App.Settings.Prop.BootstrapperTitle;
|
||||
Icon = App.Settings.Prop.BootstrapperIcon.GetIcon().GetImageSource();
|
||||
|
||||
if (App.Settings.Prop.Theme.GetFinal() == Theme.Light)
|
||||
{
|
||||
// Matching the roblox website light theme as close as possible.
|
||||
_viewModel.DialogBorder = new Thickness(1);
|
||||
_viewModel.Background = new SolidColorBrush(Color.FromRgb(242, 244, 245));
|
||||
_viewModel.Foreground = new SolidColorBrush(Color.FromRgb(57, 59, 61));
|
||||
_viewModel.IconColor = new SolidColorBrush(Color.FromRgb(57, 59, 61));
|
||||
_viewModel.ProgressBarBackground = new SolidColorBrush(Color.FromRgb(189, 190, 190));
|
||||
_viewModel.ByfronLogoLocation = new BitmapImage(new Uri("pack://application:,,,/Resources/BootstrapperStyles/ByfronDialog/ByfronLogoLight.jpg"));
|
||||
}
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
#region IBootstrapperDialog Methods
|
||||
// Referencing FluentDialog
|
||||
public void ShowBootstrapper() => this.ShowDialog();
|
||||
|
||||
public void CloseBootstrapper() => Dispatcher.BeginInvoke(this.Close);
|
||||
|
||||
public void ShowSuccess(string message, Action? callback) => BaseFunctions.ShowSuccess(message, callback);
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -1,31 +1,34 @@
|
||||
<ui:UiWindow x:Class="Bloxstrap.Dialogs.FluentDialog"
|
||||
<ui:UiWindow x:Class="Bloxstrap.UI.Elements.Bootstrapper.FluentDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Bloxstrap.Dialogs"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
mc:Ignorable="d"
|
||||
Width="420"
|
||||
Height="192"
|
||||
MinHeight="191"
|
||||
MaxHeight="192"
|
||||
MinHeight="0"
|
||||
SizeToContent="Height"
|
||||
ResizeMode="NoResize"
|
||||
Background="{ui:ThemeResource ApplicationBackgroundBrush}"
|
||||
ExtendsContentIntoTitleBar="True"
|
||||
WindowBackdropType="Mica"
|
||||
WindowCornerPreference="Round"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
<StackPanel>
|
||||
<ui:TitleBar Background="{ui:ThemeResource ApplicationBackgroundBrush}" Title="{Binding Title, Mode=OneTime}" Padding="15,15,0,10" x:Name="RootTitleBar" Grid.Row="0" ForceShutdown="False" MinimizeToTray="False" ShowHelp="False" UseSnapLayout="False" ShowClose="False" ShowMinimize="False" ShowMaximize="False" />
|
||||
<Grid Margin="16,0,16,0">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ui:TitleBar Grid.Row="0" x:Name="RootTitleBar" Padding="8" Title="{Binding Title, Mode=OneTime}" ShowMinimize="False" ShowMaximize="False" CanMaximize="False" ShowClose="False" />
|
||||
|
||||
<Grid Grid.Row="1" Margin="16,8,16,16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Margin="0,12,0,0" Width="48" Height="48" VerticalAlignment="Top">
|
||||
<Border.Background>
|
||||
<ImageBrush ImageSource="{Binding Icon, Mode=OneWay}" />
|
||||
<ImageBrush ImageSource="{Binding Icon, Mode=OneWay}" RenderOptions.BitmapScalingMode="HighQuality" />
|
||||
</Border.Background>
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1">
|
||||
@ -33,8 +36,9 @@
|
||||
<ProgressBar Margin="16,16,0,16" IsIndeterminate="{Binding ProgressIndeterminate, Mode=OneWay}" Value="{Binding ProgressValue, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Margin="8,16,16,16" Content="{Binding CancelButtonText, Mode=OneWay}" Width="120" HorizontalAlignment="Right" Visibility="{Binding CancelButtonVisibility, Mode=OneWay}" Command="{Binding CancelInstallCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<Border Grid.Row="2" Padding="15" Background="{ui:ThemeResource SolidBackgroundFillColorSecondaryBrush}">
|
||||
<Button Margin="0" Content="Cancel" Width="120" HorizontalAlignment="Right" IsEnabled="{Binding CancelEnabled, Mode=OneWay}" Command="{Binding CancelInstallCommand}" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</ui:UiWindow>
|
@ -1,16 +1,13 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using Bloxstrap.Enums;
|
||||
using Bloxstrap.Extensions;
|
||||
using Bloxstrap.ViewModels;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using Wpf.Ui.Appearance;
|
||||
using Wpf.Ui.Mvvm.Contracts;
|
||||
using Wpf.Ui.Mvvm.Services;
|
||||
|
||||
namespace Bloxstrap.Dialogs
|
||||
using Bloxstrap.UI.ViewModels.Bootstrapper;
|
||||
using Bloxstrap.UI.Elements.Bootstrapper.Base;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Bootstrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for FluentDialog.xaml
|
||||
@ -19,9 +16,9 @@ namespace Bloxstrap.Dialogs
|
||||
{
|
||||
private readonly IThemeService _themeService = new ThemeService();
|
||||
|
||||
private readonly FluentDialogViewModel _viewModel;
|
||||
private readonly BootstrapperDialogViewModel _viewModel;
|
||||
|
||||
public Bootstrapper? Bootstrapper { get; set; }
|
||||
public Bloxstrap.Bootstrapper? Bootstrapper { get; set; }
|
||||
|
||||
#region UI Elements
|
||||
public string Message
|
||||
@ -56,18 +53,20 @@ namespace Bloxstrap.Dialogs
|
||||
|
||||
public bool CancelEnabled
|
||||
{
|
||||
get => _viewModel.CancelButtonVisibility == Visibility.Visible;
|
||||
get => _viewModel.CancelEnabled;
|
||||
set
|
||||
{
|
||||
_viewModel.CancelButtonVisibility = (value ? Visibility.Visible : Visibility.Collapsed);
|
||||
_viewModel.CancelEnabled = value;
|
||||
|
||||
_viewModel.OnPropertyChanged(nameof(_viewModel.CancelButtonVisibility));
|
||||
_viewModel.OnPropertyChanged(nameof(_viewModel.CancelEnabled));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public FluentDialog()
|
||||
{
|
||||
_viewModel = new FluentDialogViewModel(this);
|
||||
_viewModel = new BootstrapperDialogViewModel(this);
|
||||
DataContext = _viewModel;
|
||||
Title = App.Settings.Prop.BootstrapperTitle;
|
||||
Icon = App.Settings.Prop.BootstrapperIcon.GetIcon().GetImageSource();
|
||||
@ -84,31 +83,7 @@ namespace Bloxstrap.Dialogs
|
||||
|
||||
public void CloseBootstrapper() => Dispatcher.BeginInvoke(this.Close);
|
||||
|
||||
// TODO: make prompts use dialog view natively rather than using message dialog boxes
|
||||
|
||||
public void ShowSuccess(string message)
|
||||
{
|
||||
App.ShowMessageBox(message, MessageBoxImage.Information);
|
||||
App.Terminate();
|
||||
}
|
||||
|
||||
public void ShowError(string message)
|
||||
{
|
||||
App.ShowMessageBox($"An error occurred while starting Roblox\n\nDetails: {message}", MessageBoxImage.Error);
|
||||
App.Terminate(Bootstrapper.ERROR_INSTALL_FAILURE);
|
||||
}
|
||||
|
||||
public void PromptShutdown()
|
||||
{
|
||||
MessageBoxResult result = App.ShowMessageBox(
|
||||
"Roblox is currently running, but needs to close. Would you like close Roblox now?",
|
||||
MessageBoxImage.Information,
|
||||
MessageBoxButton.OKCancel
|
||||
);
|
||||
|
||||
if (result != MessageBoxResult.OK)
|
||||
Environment.Exit(Bootstrapper.ERROR_INSTALL_USEREXIT);
|
||||
}
|
||||
public void ShowSuccess(string message, Action? callback) => BaseFunctions.ShowSuccess(message, callback);
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Bloxstrap.Dialogs
|
||||
namespace Bloxstrap.UI.Elements.Bootstrapper
|
||||
{
|
||||
partial class LegacyDialog2009
|
||||
partial class LegacyDialog2008
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
@ -65,7 +65,7 @@ namespace Bloxstrap.Dialogs
|
||||
this.buttonCancel.UseVisualStyleBackColor = true;
|
||||
this.buttonCancel.Click += new System.EventHandler(this.ButtonCancel_Click);
|
||||
//
|
||||
// LegacyDialog2009
|
||||
// LegacyDialog2008
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
@ -79,10 +79,10 @@ namespace Bloxstrap.Dialogs
|
||||
this.MaximumSize = new System.Drawing.Size(327, 161);
|
||||
this.MinimizeBox = false;
|
||||
this.MinimumSize = new System.Drawing.Size(327, 161);
|
||||
this.Name = "LegacyDialog2009";
|
||||
this.Name = "LegacyDialog2008";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.Text = "LegacyDialog2009";
|
||||
this.Load += new System.EventHandler(this.LegacyDialog2009_Load);
|
||||
this.Text = "LegacyDialog2008";
|
||||
this.Load += new System.EventHandler(this.LegacyDialog2008_Load);
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Bloxstrap.Dialogs
|
||||
using Bloxstrap.UI.Elements.Bootstrapper.Base;
|
||||
|
||||
namespace Bloxstrap.UI.Elements.Bootstrapper
|
||||
{
|
||||
// windows: https://youtu.be/VpduiruysuM?t=18
|
||||
// mac: https://youtu.be/ncHhbcVDRgQ?t=63
|
||||
|
||||
public partial class LegacyDialog2009 : BootstrapperDialogForm
|
||||
public partial class LegacyDialog2008 : WinFormsDialogBase
|
||||
{
|
||||
protected override string _message
|
||||
{
|
||||
@ -32,7 +33,7 @@ namespace Bloxstrap.Dialogs
|
||||
set => this.buttonCancel.Enabled = value;
|
||||
}
|
||||
|
||||
public LegacyDialog2009()
|
||||
public LegacyDialog2008()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
@ -40,7 +41,7 @@ namespace Bloxstrap.Dialogs
|
||||
SetupDialog();
|
||||
}
|
||||
|
||||
private void LegacyDialog2009_Load(object sender, EventArgs e)
|
||||
private void LegacyDialog2008_Load(object sender, EventArgs e)
|
||||
{
|
||||
this.Activate();
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Bloxstrap.Dialogs
|
||||
namespace Bloxstrap.UI.Elements.Bootstrapper
|
||||
{
|
||||
partial class LegacyDialog2011
|
||||
{
|
@ -1,13 +1,12 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using Bloxstrap.Extensions;
|
||||
using Bloxstrap.UI.Elements.Bootstrapper.Base;
|
||||
|
||||
namespace Bloxstrap.Dialogs
|
||||
namespace Bloxstrap.UI.Elements.Bootstrapper
|
||||
{
|
||||
// https://youtu.be/3K9oCEMHj2s?t=35
|
||||
|
||||
public partial class LegacyDialog2011 : BootstrapperDialogForm
|
||||
public partial class LegacyDialog2011 : WinFormsDialogBase
|
||||
{
|
||||
protected override string _message
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Bloxstrap.Dialogs
|
||||
namespace Bloxstrap.UI.Elements.Bootstrapper
|
||||
{
|
||||
partial class ProgressDialog
|
||||
{
|
@ -1,15 +1,13 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using Bloxstrap.Enums;
|
||||
using Bloxstrap.Extensions;
|
||||
using Bloxstrap.UI.Elements.Bootstrapper.Base;
|
||||
|
||||
namespace Bloxstrap.Dialogs
|
||||
namespace Bloxstrap.UI.Elements.Bootstrapper
|
||||
{
|
||||
// basically just the modern dialog
|
||||
|
||||
public partial class ProgressDialog : BootstrapperDialogForm
|
||||
public partial class ProgressDialog : WinFormsDialogBase
|
||||
{
|
||||
protected override string _message
|
||||
{
|
@ -1,4 +1,4 @@
|
||||
namespace Bloxstrap.Dialogs
|
||||
namespace Bloxstrap.UI.Elements.Bootstrapper
|
||||
{
|
||||
partial class VistaDialog
|
||||
{
|
@ -1,9 +1,8 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using Bloxstrap.Extensions;
|
||||
using Bloxstrap.UI.Elements.Bootstrapper.Base;
|
||||
|
||||
namespace Bloxstrap.Dialogs
|
||||
namespace Bloxstrap.UI.Elements.Bootstrapper
|
||||
{
|
||||
// https://youtu.be/h0_AL95Sc3o?t=48
|
||||
|
||||
@ -11,7 +10,7 @@ namespace Bloxstrap.Dialogs
|
||||
// since taskdialog is part of winforms, it can't really be properly used without a form
|
||||
// for example, cross-threaded calls to ui controls can't really be done outside of a form
|
||||
|
||||
public partial class VistaDialog : BootstrapperDialogForm
|
||||
public partial class VistaDialog : WinFormsDialogBase
|
||||
{
|
||||
private TaskDialogPage _dialogPage;
|
||||
|
||||
@ -80,11 +79,11 @@ namespace Bloxstrap.Dialogs
|
||||
SetupDialog();
|
||||
}
|
||||
|
||||
public override void ShowSuccess(string message)
|
||||
public override void ShowSuccess(string message, Action? callback)
|
||||
{
|
||||
if (this.InvokeRequired)
|
||||
{
|
||||
this.Invoke(ShowSuccess, message);
|
||||
this.Invoke(ShowSuccess, message, callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -96,43 +95,19 @@ namespace Bloxstrap.Dialogs
|
||||
Buttons = { TaskDialogButton.OK }
|
||||
};
|
||||
|
||||
successDialog.Buttons[0].Click += (_, _) => App.Terminate();
|
||||
successDialog.Buttons[0].Click += (_, _) =>
|
||||
{
|
||||
if (callback is not null)
|
||||
callback();
|
||||
|
||||
App.Terminate();
|
||||
};
|
||||
|
||||
_dialogPage.Navigate(successDialog);
|
||||
_dialogPage = successDialog;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ShowError(string message)
|
||||
{
|
||||
if (this.InvokeRequired)
|
||||
{
|
||||
this.Invoke(ShowError, message);
|
||||
}
|
||||
else
|
||||
{
|
||||
TaskDialogPage errorDialog = new()
|
||||
{
|
||||
Icon = TaskDialogIcon.Error,
|
||||
Caption = App.Settings.Prop.BootstrapperTitle,
|
||||
Heading = "An error occurred while starting Roblox",
|
||||
Buttons = { TaskDialogButton.Close },
|
||||
Expander = new TaskDialogExpander()
|
||||
{
|
||||
Text = message,
|
||||
CollapsedButtonText = "See details",
|
||||
ExpandedButtonText = "Hide details",
|
||||
Position = TaskDialogExpanderPosition.AfterText
|
||||
}
|
||||
};
|
||||
|
||||
errorDialog.Buttons[0].Click += (sender, e) => App.Terminate(Bootstrapper.ERROR_INSTALL_FAILURE);
|
||||
|
||||
_dialogPage.Navigate(errorDialog);
|
||||
_dialogPage = errorDialog;
|
||||
}
|
||||
}
|
||||
|
||||
public override void CloseBootstrapper()
|
||||
{
|
||||
if (this.InvokeRequired)
|
49
Bloxstrap/UI/Elements/ExceptionDialog.xaml
Normal file
@ -0,0 +1,49 @@
|
||||
<ui:UiWindow x:Class="Bloxstrap.UI.ExceptionDialog"
|
||||
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.MessageBox"
|
||||
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" />
|
||||
|
||||
<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 Text="An exception occurred while running Bloxstrap" FontSize="18" Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
<RichTextBox x:Name="ErrorRichTextBox" Padding="8" Margin="0,16,0,0" Block.LineHeight="2" FontFamily="Courier New" IsReadOnly="True" />
|
||||
<TextBlock Text="Please report this exception through a GitHub issue or in our Discord chat, along with a copy of the log file that was created." Margin="0,16,0,0" TextWrapping="Wrap" Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Border Grid.Row="2" Padding="15" Background="{ui:ThemeResource SolidBackgroundFillColorSecondaryBrush}">
|
||||
<StackPanel Orientation="Horizontal" FlowDirection="LeftToRight" HorizontalAlignment="Right">
|
||||
<Button x:Name="LocateLogFileButton" Content="Locate log file" />
|
||||
<ComboBox x:Name="ReportOptions" SelectedIndex="0" Padding="12,6,12,6" Margin="12,0,0,0">
|
||||
<ComboBoxItem Content="Submit report..." Visibility="Collapsed" />
|
||||
<ComboBoxItem Content="Submit report via GitHub" />
|
||||
<ComboBoxItem Content="Submit report via Discord" />
|
||||
</ComboBox>
|
||||
<Button x:Name="CloseButton" MinWidth="100" Content="Close" Margin="12,0,0,0" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ui:UiWindow>
|
67
Bloxstrap/UI/Elements/ExceptionDialog.xaml.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System.Media;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
|
||||
namespace Bloxstrap.UI
|
||||
{
|
||||
// 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 ExceptionDialog
|
||||
{
|
||||
public ExceptionDialog(Exception exception)
|
||||
{
|
||||
Exception? innerException = exception.InnerException;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
Title = RootTitleBar.Title = $"{App.ProjectName} Exception";
|
||||
ErrorRichTextBox.Selection.Text = $"{exception.GetType()}: {exception.Message}";
|
||||
|
||||
if (innerException is not null)
|
||||
ErrorRichTextBox.Selection.Text += $"\n\n===== Inner Exception =====\n{innerException.GetType()}: {innerException.Message}";
|
||||
|
||||
if (!App.Logger.Initialized)
|
||||
LocateLogFileButton.Content = "Copy log contents";
|
||||
|
||||
LocateLogFileButton.Click += delegate
|
||||
{
|
||||
if (App.Logger.Initialized)
|
||||
Process.Start("explorer.exe", $"/select,\"{App.Logger.FileLocation}\"");
|
||||
else
|
||||
Clipboard.SetText(String.Join("\r\n", App.Logger.Backlog));
|
||||
};
|
||||
|
||||
ReportOptions.DropDownClosed += (sender, e) =>
|
||||
{
|
||||
string? selectionName = ReportOptions.SelectedItem.ToString();
|
||||
|
||||
ReportOptions.SelectedIndex = 0;
|
||||
|
||||
if (selectionName is null)
|
||||
return;
|
||||
|
||||
if (selectionName.EndsWith("GitHub"))
|
||||
Utilities.ShellExecute($"https://github.com/{App.ProjectRepository}/issues");
|
||||
else if (selectionName.EndsWith("Discord"))
|
||||
Utilities.ShellExecute("https://discord.gg/nKjV3mGq6R");
|
||||
};
|
||||
|
||||
CloseButton.Click += delegate
|
||||
{
|
||||
Close();
|
||||
};
|
||||
|
||||
SystemSounds.Hand.Play();
|
||||
|
||||
Loaded += delegate
|
||||
{
|
||||
IntPtr hWnd = new WindowInteropHelper(this).Handle;
|
||||
NativeMethods.FlashWindow(hWnd, true);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
46
Bloxstrap/UI/Elements/FluentMessageBox.xaml
Normal file
@ -0,0 +1,46 @@
|
||||
<ui:UiWindow x:Class="Bloxstrap.UI.MessageBox.FluentMessageBox"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
xmlns:local="clr-namespace:Bloxstrap.UI.MessageBox"
|
||||
mc:Ignorable="d"
|
||||
Title="Bloxstrap"
|
||||
d:DesignWidth="480"
|
||||
MinWidth="180"
|
||||
MaxWidth="480"
|
||||
Width="180"
|
||||
MinHeight="160"
|
||||
SizeToContent="Height"
|
||||
ResizeMode="NoResize"
|
||||
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" Title="Bloxstrap" ShowMinimize="False" ShowMaximize="False" CanMaximize="False" KeyboardNavigation.TabNavigation="None" />
|
||||
|
||||
<Grid Grid.Row="1" Margin="15">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image x:Name="IconImage" Grid.Column="0" RenderOptions.BitmapScalingMode="HighQuality" Width="32" Height="32" Margin="0,0,15,0" VerticalAlignment="Top" />
|
||||
<TextBlock x:Name="MessageTextBlock" Grid.Column="1" VerticalAlignment="Center" TextWrapping="Wrap" Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
</Grid>
|
||||
|
||||
<Border Grid.Row="2" Margin="0,10,0,0" Padding="15" Background="{ui:ThemeResource SolidBackgroundFillColorSecondaryBrush}">
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" FlowDirection="LeftToRight" HorizontalAlignment="Right">
|
||||
<Button x:Name="ButtonOne" MinWidth="100" Content="Button 1" />
|
||||
<Button x:Name="ButtonTwo" MinWidth="100" Margin="12,0,0,0" Content="Button 2" />
|
||||
<Button x:Name="ButtonThree" MinWidth="100" Margin="12,0,0,0" Content="Button 3" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ui:UiWindow>
|
126
Bloxstrap/UI/Elements/FluentMessageBox.xaml.cs
Normal file
@ -0,0 +1,126 @@
|
||||
using System.Media;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
using Bloxstrap.UI.Utility;
|
||||
|
||||
namespace Bloxstrap.UI.MessageBox
|
||||
{
|
||||
// wpfui does have its own messagebox control but it SUCKS so heres this instead
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for FluentMessageBox.xaml
|
||||
/// </summary>
|
||||
public partial class FluentMessageBox
|
||||
{
|
||||
public MessageBoxResult Result = MessageBoxResult.None;
|
||||
|
||||
public FluentMessageBox(string message, MessageBoxImage image, MessageBoxButton buttons)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
string? iconFilename = null;
|
||||
SystemSound? sound = null;
|
||||
|
||||
switch (image)
|
||||
{
|
||||
case MessageBoxImage.Error:
|
||||
iconFilename = "Error";
|
||||
sound = SystemSounds.Hand;
|
||||
break;
|
||||
|
||||
case MessageBoxImage.Question:
|
||||
iconFilename = "Question";
|
||||
sound = SystemSounds.Question;
|
||||
break;
|
||||
|
||||
case MessageBoxImage.Warning:
|
||||
iconFilename = "Warning";
|
||||
sound = SystemSounds.Asterisk;
|
||||
break;
|
||||
|
||||
case MessageBoxImage.Information:
|
||||
iconFilename = "Information";
|
||||
sound = SystemSounds.Asterisk;
|
||||
break;
|
||||
}
|
||||
|
||||
if (iconFilename is null)
|
||||
IconImage.Visibility = Visibility.Collapsed;
|
||||
else
|
||||
IconImage.Source = new BitmapImage(new Uri($"pack://application:,,,/Resources/MessageBox/{iconFilename}.png"));
|
||||
|
||||
Title = App.ProjectName;
|
||||
MessageTextBlock.Text = message;
|
||||
ButtonOne.Visibility = Visibility.Collapsed;
|
||||
ButtonTwo.Visibility = Visibility.Collapsed;
|
||||
ButtonThree.Visibility = Visibility.Collapsed;
|
||||
|
||||
switch (buttons)
|
||||
{
|
||||
case MessageBoxButton.YesNo:
|
||||
SetButton(ButtonOne, MessageBoxResult.Yes);
|
||||
SetButton(ButtonTwo, MessageBoxResult.No);
|
||||
break;
|
||||
|
||||
case MessageBoxButton.YesNoCancel:
|
||||
SetButton(ButtonOne, MessageBoxResult.Yes);
|
||||
SetButton(ButtonTwo, MessageBoxResult.No);
|
||||
SetButton(ButtonThree, MessageBoxResult.Cancel);
|
||||
break;
|
||||
|
||||
case MessageBoxButton.OKCancel:
|
||||
SetButton(ButtonOne, MessageBoxResult.OK);
|
||||
SetButton(ButtonTwo, MessageBoxResult.Cancel);
|
||||
break;
|
||||
|
||||
case MessageBoxButton.OK:
|
||||
default:
|
||||
SetButton(ButtonOne, MessageBoxResult.OK);
|
||||
break;
|
||||
}
|
||||
|
||||
// we're doing the width manually for this because ye
|
||||
|
||||
if (ButtonThree.Visibility == Visibility.Visible)
|
||||
Width = 356;
|
||||
else if (ButtonTwo.Visibility == Visibility.Visible)
|
||||
Width = 245;
|
||||
|
||||
double textWidth = Math.Ceiling(Rendering.GetTextWidth(MessageTextBlock));
|
||||
|
||||
// offset to account for box size
|
||||
textWidth += 40;
|
||||
|
||||
// offset to account for icon
|
||||
if (image != MessageBoxImage.None)
|
||||
textWidth += 50;
|
||||
|
||||
if (textWidth > MaxWidth)
|
||||
Width = MaxWidth;
|
||||
else if (textWidth > Width)
|
||||
Width = textWidth;
|
||||
|
||||
sound?.Play();
|
||||
|
||||
Loaded += delegate
|
||||
{
|
||||
IntPtr hWnd = new WindowInteropHelper(this).Handle;
|
||||
NativeMethods.FlashWindow(hWnd, true);
|
||||
};
|
||||
}
|
||||
|
||||
public void SetButton(Button button, MessageBoxResult result)
|
||||
{
|
||||
button.Visibility = Visibility.Visible;
|
||||
button.Content = result.ToString();
|
||||
button.Click += (_, _) =>
|
||||
{
|
||||
Result = result;
|
||||
Close();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
83
Bloxstrap/UI/Elements/Menu/MainWindow.xaml
Normal file
@ -0,0 +1,83 @@
|
||||
<ui:UiWindow x:Class="Bloxstrap.UI.Menu.MainWindow"
|
||||
x:Name="ConfigurationWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:pages="clr-namespace:Bloxstrap.UI.Menu.Pages"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
mc:Ignorable="d"
|
||||
Title="Bloxstrap Menu"
|
||||
MinWidth="960"
|
||||
Width="960"
|
||||
Height="580"
|
||||
Background="{ui:ThemeResource ApplicationBackgroundBrush}"
|
||||
ExtendsContentIntoTitleBar="True"
|
||||
WindowBackdropType="Mica"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ui:TitleBar Padding="8" x:Name="RootTitleBar" Grid.Row="0" ForceShutdown="False" MinimizeToTray="False" ShowHelp="True" HelpClicked="OpenWiki" UseSnapLayout="True" Title="Bloxstrap Menu" Icon="pack://application:,,,/Bloxstrap.ico" />
|
||||
|
||||
<Grid x:Name="RootGrid" Grid.Row="1" Margin="12,12,0,0" Visibility="Visible">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ui:NavigationFluent x:Name="RootNavigation" Grid.Row="1" Grid.Column="0" Margin="0,0,12,0" Frame="{Binding ElementName=RootFrame}" SelectedPageIndex="0" Visibility="{Binding NavigationVisibility, Mode=OneWay}">
|
||||
<ui:NavigationFluent.Items>
|
||||
<ui:NavigationItem Content="Integrations" PageType="{x:Type pages:IntegrationsPage}" Icon="Add28" Tag="integrations" />
|
||||
<ui:NavigationItem Content="Mods" PageType="{x:Type pages:ModsPage}" Icon="WrenchScrewdriver20" Tag="mods" />
|
||||
<ui:NavigationItem Content="FastFlags" PageType="{x:Type pages:FastFlagsPage}" Icon="Flag24" Tag="fastflags" />
|
||||
<ui:NavigationItem Content="Appearance" PageType="{x:Type pages:AppearancePage}" Icon="PaintBrush24" Tag="appearance" />
|
||||
<ui:NavigationItem Content="Behaviour" PageType="{x:Type pages:BehaviourPage}" Icon="Settings24" Tag="behaviour" />
|
||||
<ui:NavigationItem Content="Installation" PageType="{x:Type pages:InstallationPage}" Icon="HardDrive20" Tag="installation" />
|
||||
<ui:NavigationItem Content="About" PageType="{x:Type pages:AboutPage}" Icon="QuestionCircle48" Tag="about" />
|
||||
|
||||
<ui:NavigationItem Content="Before you install..." PageType="{x:Type pages:PreInstallPage}" Tag="preinstall" Visibility="Collapsed" />
|
||||
</ui:NavigationFluent.Items>
|
||||
</ui:NavigationFluent>
|
||||
|
||||
<Grid Grid.Row="0" Grid.RowSpan="2" Grid.Column="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ui:Breadcrumb Grid.Row="0" Margin="0,0,0,5" Navigation="{Binding ElementName=RootNavigation}" />
|
||||
<Frame x:Name="RootFrame" Grid.Row="1" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<StatusBar x:Name="RootStatusBar" Grid.Row="2" Padding="14,10" Background="{ui:ThemeResource ApplicationBackgroundBrush}" BorderThickness="0,1,0,0">
|
||||
<StatusBar.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
</Grid>
|
||||
</ItemsPanelTemplate>
|
||||
</StatusBar.ItemsPanel>
|
||||
<StatusBarItem Grid.Column="1" Padding="0,0,4,0">
|
||||
<ui:Button Content="{Binding ConfirmButtonText, Mode=OneTime}" Appearance="Primary" Command="{Binding ConfirmSettingsCommand, Mode=OneWay}" IsEnabled="{Binding ConfirmButtonEnabled, Mode=OneWay}" />
|
||||
</StatusBarItem>
|
||||
<StatusBarItem Grid.Column="2" Padding="4,0,0,0">
|
||||
<ui:Button Content="Cancel" Command="{Binding CloseWindowCommand, Mode=OneWay}" />
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
|
||||
<ui:Dialog x:Name="RootDialog" Title="WPF UI" Grid.Row="1" Grid.RowSpan="2" ButtonLeftVisibility="Collapsed" ButtonRightName="Continue" DialogHeight="225" DialogWidth="430" />
|
||||
</Grid>
|
||||
</ui:UiWindow>
|
@ -1,15 +1,13 @@
|
||||
using System;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls;
|
||||
|
||||
using Wpf.Ui.Appearance;
|
||||
using Wpf.Ui.Controls.Interfaces;
|
||||
using Wpf.Ui.Mvvm.Contracts;
|
||||
using Wpf.Ui.Mvvm.Services;
|
||||
|
||||
using Bloxstrap.Extensions;
|
||||
using Bloxstrap.ViewModels;
|
||||
using Bloxstrap.UI.ViewModels.Menu;
|
||||
|
||||
namespace Bloxstrap.Views
|
||||
namespace Bloxstrap.UI.Menu
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
@ -35,6 +33,8 @@ namespace Bloxstrap.Views
|
||||
_themeService.SetSystemAccent();
|
||||
}
|
||||
|
||||
public void OpenWiki(object? sender, EventArgs e) => Utilities.ShellExecute($"https://github.com/{App.ProjectRepository}/wiki");
|
||||
|
||||
#region INavigationWindow methods
|
||||
|
||||
public Frame GetFrame() => RootFrame;
|
226
Bloxstrap/UI/Elements/Menu/Pages/AboutPage.xaml
Normal file
@ -0,0 +1,226 @@
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Menu.Pages.AboutPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:models="clr-namespace:Bloxstrap.UI.ViewModels"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="700" d:DesignWidth="800"
|
||||
Title="AboutPage"
|
||||
Scrollable="True">
|
||||
<StackPanel Margin="0,0,14,14">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border Grid.Column="0" Width="60" Height="60" VerticalAlignment="Center">
|
||||
<Border.Background>
|
||||
<ImageBrush ImageSource="pack://application:,,,/Bloxstrap.ico" />
|
||||
</Border.Background>
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" Margin="12,0,0,0" VerticalAlignment="Center">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="Bloxstrap" Margin="0,0,4,0" FontSize="24" FontWeight="Medium" />
|
||||
<TextBlock Grid.Column="1" Text="{Binding Version, Mode=OneTime}" Margin="4,0,0,2" VerticalAlignment="Bottom" FontSize="16" FontWeight="Medium" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</Grid>
|
||||
<TextBlock Text="An open-source, feature-packed alternative bootstrapper for Roblox" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
<TextBlock Text="Developed by pizzaboxer - if you like this, please consider leaving a star on GitHub!" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ui:Anchor Margin="0,16,4,0" Content="GitHub repository" Icon="Code24" NavigateUri="https://github.com/pizzaboxer/bloxstrap" />
|
||||
<ui:Anchor Margin="4,16,4,0" Content="Report an issue" Icon="Chat48" NavigateUri="https://github.com/pizzaboxer/bloxstrap/issues" />
|
||||
<ui:Anchor Margin="4,16,4,0" Content="Help and Information" Icon="BookQuestionMark24" NavigateUri="https://github.com/pizzaboxer/bloxstrap/wiki" />
|
||||
<ui:Anchor Margin="4,16,0,0" Content="Discord server" Icon="Chat48" NavigateUri="https://discord.gg/nKjV3mGq6R" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Visibility="{Binding BuildInformationVisibility, Mode=OneTime}">
|
||||
<TextBlock Text="Build Information" FontWeight="Medium" FontSize="20" Margin="0,16,0,0" />
|
||||
<TextBlock Text="Using an unreleased version, I see?" TextWrapping="Wrap" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
|
||||
<Grid Column="0" Margin="0,8,0,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Margin="0,4,16,4" FontSize="14" FontWeight="Medium" Text="Timestamp" />
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Margin="0,0,0,4" VerticalAlignment="Bottom" Text="{Binding BuildTimestamp, Mode=OneTime}" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0,4,16,4" FontSize="14" FontWeight="Medium" Text="Machine" />
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Margin="0,0,0,4" VerticalAlignment="Bottom" Text="{Binding BuildMetadata.Machine, Mode=OneTime}" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Margin="0,4,16,4" FontSize="14" FontWeight="Medium" Text="Commit Hash" Visibility="{Binding BuildCommitVisibility, Mode=OneTime}" />
|
||||
<TextBlock Grid.Row="2" Grid.Column="1" Margin="0,0,0,4" VerticalAlignment="Bottom" Foreground="{DynamicResource TextFillColorTertiaryBrush}" Visibility="{Binding BuildCommitVisibility, Mode=OneTime}">
|
||||
<Hyperlink Foreground="{DynamicResource TextFillColorTertiaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="{Binding BuildCommitHashUrl, Mode=OneTime}">
|
||||
<TextBlock Text="{Binding BuildMetadata.CommitHash, Mode=OneTime}" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Margin="0,4,16,4" FontSize="14" FontWeight="Medium" Text="Commit Ref" Visibility="{Binding BuildCommitVisibility, Mode=OneTime}" />
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Margin="0,0,0,4" VerticalAlignment="Bottom" Text="{Binding BuildMetadata.CommitRef, Mode=OneTime}" Foreground="{DynamicResource TextFillColorTertiaryBrush}" Visibility="{Binding BuildCommitVisibility, Mode=OneTime}" />
|
||||
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Contributors" FontWeight="Medium" FontSize="20" Margin="0,16,0,0" />
|
||||
<TextBlock Text="These are the people who have made notable contributions to Bloxstrap, helping make it what it is." TextWrapping="Wrap" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
<Grid Column="0" Margin="0,8,0,0">
|
||||
<Grid.RowDefinitions>
|
||||
<!-- bruh -->
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Margin="0,4,16,4" FontSize="14" FontWeight="Medium">
|
||||
<Hyperlink Foreground="{DynamicResource TextFillColorPrimaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://www.roblox.com/users/2485612194/profile">Multako</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Margin="0,0,0,4" VerticalAlignment="Bottom" Text="Designing the Bloxstrap logo" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0,4,16,4" FontSize="14" FontWeight="Medium">
|
||||
<Hyperlink Foreground="{DynamicResource TextFillColorPrimaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/bluepilledgreat">bluepilledgreat</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Margin="0,4,0,4" VerticalAlignment="Bottom" Text="Helping with bootstrapper functionality and UX" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Margin="0,4,16,4" FontSize="14" FontWeight="Medium">
|
||||
<Hyperlink Foreground="{DynamicResource TextFillColorPrimaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/1011025m">1011025m</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="2" Grid.Column="1" Margin="0,4,0,4" VerticalAlignment="Bottom" Text="Helping disable the Roblox desktop app, and adding the fake Byfron bootstrapper style" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Margin="0,4,16,4" FontSize="14" FontWeight="Medium">
|
||||
<Hyperlink Foreground="{DynamicResource TextFillColorPrimaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/sitiom">sitiom</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Margin="0,4,0,4" VerticalAlignment="Bottom" Text="Setting up GitHub CI workflows and Winget releases" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" Margin="0,4,16,4" FontSize="14" FontWeight="Medium">
|
||||
<Hyperlink Foreground="{DynamicResource TextFillColorPrimaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/Mantaraix">taskmanager</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="4" Grid.Column="1" Margin="0,4,0,4" VerticalAlignment="Bottom" Text="Helping with designing the new menu look and layout" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
|
||||
<TextBlock Grid.Row="5" Grid.Column="0" Margin="0,4,16,4" FontSize="14" FontWeight="Medium">
|
||||
<Hyperlink Foreground="{DynamicResource TextFillColorPrimaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/Extravi">Extravi</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="5" Grid.Column="1" Margin="0,4,0,4" VerticalAlignment="Bottom" Text="Allowing their ReShade presets to be bundled prior to removal, and helping with improving UX" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
|
||||
<TextBlock Grid.Row="6" Grid.Column="0" Margin="0,4,16,4" FontSize="14" FontWeight="Medium">
|
||||
<Hyperlink Foreground="{DynamicResource TextFillColorPrimaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/he3als">he3als</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="6" Grid.Column="1" Margin="0,4,0,4" VerticalAlignment="Bottom" Text="Helping add graphics options (rendering modes, exclusive fullscreen, etc)" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
|
||||
<TextBlock Grid.Row="7" Grid.Column="0" Margin="0,4,16,4" FontSize="14" FontWeight="Medium">
|
||||
<Hyperlink Foreground="{DynamicResource TextFillColorPrimaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/NikSavchenk0">NikSavchenk0</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="7" Grid.Column="1" Margin="0,4,0,4" VerticalAlignment="Bottom" Text="Suggesting/helping with the mod preset for emoji selection" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
|
||||
<TextBlock Grid.Row="8" Grid.Column="0" Margin="0,4,16,4" FontSize="14" FontWeight="Medium">
|
||||
<Hyperlink Foreground="{DynamicResource TextFillColorPrimaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/EasternBloxxer">EasternBloxxer</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="8" Grid.Column="1" Margin="0,4,0,4" VerticalAlignment="Bottom" Text="Suggesting/helping with the FastFlag preset for preferred lighting mode" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
|
||||
<TextBlock Grid.Row="9" Grid.Column="0" Margin="0,4,16,4" FontSize="14" FontWeight="Medium">
|
||||
<Hyperlink Foreground="{DynamicResource TextFillColorPrimaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/carter0nline">carter0nline</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="9" Grid.Column="1" Margin="0,4,0,4" VerticalAlignment="Bottom" Text="Suggesting/helping with the FastFlag preset for enabling GUI hiding" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
|
||||
<TextBlock Grid.Row="10" Grid.Column="0" Margin="0,4,16,4" FontSize="14" FontWeight="Medium">
|
||||
<Hyperlink Foreground="{DynamicResource TextFillColorPrimaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/MehKako">MehKako</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="10" Grid.Column="1" Margin="0,4,0,4" VerticalAlignment="Bottom" Text="Suggesting the FastFlag preset for enabling old textures" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
|
||||
<TextBlock Grid.Row="11" Grid.Column="0" Margin="0,4,16,4" FontSize="14" FontWeight="Medium">
|
||||
<Hyperlink Foreground="{DynamicResource TextFillColorPrimaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/EpixScripts">EpixScripts</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="11" Grid.Column="1" Margin="0,4,0,4" VerticalAlignment="Bottom" Text="Suggesting the idea for in-game data communication with rich presence statuses" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
|
||||
<TextBlock Grid.Row="12" Grid.Column="0" Margin="0,4,16,4" FontSize="14" FontWeight="Medium">
|
||||
knivesofeylis
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="12" Grid.Column="1" Margin="0,4,0,4" VerticalAlignment="Bottom" Text="Suggesting the idea for bulk font replacement" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
|
||||
<TextBlock Grid.Row="13" Grid.Column="0" Margin="0,4,16,4" FontSize="14" FontWeight="Medium">
|
||||
<Hyperlink Foreground="{DynamicResource TextFillColorPrimaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/sha4owz">sha4owz</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="13" Grid.Column="1" Margin="0,4,0,4" VerticalAlignment="Bottom" Text="Suggesting/helping with the mod preset for the old avatar editor background" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
|
||||
<TextBlock Grid.Row="14" Grid.Column="0" Margin="0,4,16,4" FontSize="14" FontWeight="Medium">
|
||||
<Hyperlink Foreground="{DynamicResource TextFillColorPrimaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://www.roblox.com/users/388534307/profile">DaMlgNoodle</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="14" Grid.Column="1" Margin="0,4,0,4" VerticalAlignment="Bottom" Text="Providing FastFlags for fixing DPI scaling" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</Grid>
|
||||
<ui:Anchor Margin="0,16,0,0" Content="See all code contributors" Icon="People48" NavigateUri="https://github.com/pizzaboxer/bloxstrap/graphs/contributors" />
|
||||
|
||||
<TextBlock Text="Licenses" FontWeight="Medium" FontSize="18" Margin="0,16,0,0" />
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ui:CardAction Grid.Row="0" Grid.Column="0" Margin="0,8,8,0" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/pizzaboxer/bloxstrap/blob/main/LICENSE">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="Bloxstrap by pizzaboxer" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="MIT License" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardAction>
|
||||
<ui:CardAction Grid.Row="0" Grid.Column="1" Margin="0,8,8,0" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/lepoco/wpfui/blob/main/LICENSE">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="WPF-UI by lepoco" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="MIT License" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardAction>
|
||||
<ui:CardAction Grid.Row="0" Grid.Column="2" Margin="0,8,0,0" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/securifybv/ShellLink/blob/master/LICENSE.txt">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="ShellLink by securifybv" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="MIT License" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardAction>
|
||||
<ui:CardAction Grid.Row="1" Grid.Column="0" Margin="0,8,8,0" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/Lachee/discord-rpc-csharp/blob/development/LICENSE">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="DiscordRPC by Lachee" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="MIT License" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardAction>
|
||||
<ui:CardAction Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Margin="0,8,0,0" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/MaximumADHD/Roblox-Studio-Mod-Manager/blob/main/LICENSE">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="Roblox Studio Mod Manager by MaximumADHD" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="MIT License" Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardAction>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ui:UiPage>
|
@ -1,6 +1,6 @@
|
||||
using Bloxstrap.ViewModels;
|
||||
using Bloxstrap.UI.ViewModels.Menu;
|
||||
|
||||
namespace Bloxstrap.Views.Pages
|
||||
namespace Bloxstrap.UI.Menu.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for AboutPage.xaml
|
94
Bloxstrap/UI/Elements/Menu/Pages/AppearancePage.xaml
Normal file
@ -0,0 +1,94 @@
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Menu.Pages.AppearancePage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="520" d:DesignWidth="800"
|
||||
Title="AppearancePage"
|
||||
Scrollable="True">
|
||||
<StackPanel Margin="0,0,14,14">
|
||||
<TextBlock Text="Configure how Bloxstrap should look." FontSize="14" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
|
||||
<ui:CardControl Margin="0,16,0,0">
|
||||
<ui:CardControl.Header>
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="Theme" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Dark theme does not apply to Legacy or Vista styles." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardControl.Header>
|
||||
<ComboBox Width="200" Padding="10,5,10,5" ItemsSource="{Binding Themes.Keys, Mode=OneTime}" Text="{Binding Theme, Mode=TwoWay}" />
|
||||
</ui:CardControl>
|
||||
<ui:CardControl Margin="0,8,0,0">
|
||||
<ui:CardControl.Header>
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="Style" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Choose how the bootstrapper should look." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardControl.Header>
|
||||
<ComboBox Width="200" Padding="10,5,10,5" ItemsSource="{Binding Dialogs.Keys, Mode=OneTime}" Text="{Binding Dialog, Mode=TwoWay}" />
|
||||
</ui:CardControl>
|
||||
<ui:CardControl Margin="0,8,0,0">
|
||||
<ui:CardControl.Header>
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="Icon" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Choose what icon the bootstrapper should use." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardControl.Header>
|
||||
<Grid Width="200">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Width="28" Height="28" Margin="0,0,5,0">
|
||||
<Border.Background>
|
||||
<ImageBrush ImageSource="{Binding IconPreviewSource, Mode=OneWay}" />
|
||||
</Border.Background>
|
||||
</Border>
|
||||
<ComboBox Grid.Column="1" Margin="5,0,0,0" Padding="10,5,10,5" ItemsSource="{Binding Icons.Keys, Mode=OneTime}" Text="{Binding Icon, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
</ui:CardControl>
|
||||
<ui:CardExpander Margin="0,8,0,0" IsExpanded="False">
|
||||
<ui:CardExpander.Header>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock FontSize="14" Text="Bootstrapper customization" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Configure other customizable Bootstrapper options." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ui:CardExpander.Header>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Title" VerticalAlignment="Center" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
<ui:TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Title, Mode=TwoWay}" />
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Margin="0,4,0,0" FontSize="12" Text="The text that shows as the title of the bootstrapper." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Margin="0,12,0,0" Text="Custom Icon" VerticalAlignment="Center" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
<Grid Grid.Row="2" Grid.Column="1" Margin="0,12,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox Grid.Column="0" Margin="0,0,4,0" Text="{Binding CustomIconLocation, Mode=TwoWay}" />
|
||||
<ui:Button Grid.Column="1" Margin="4,0,0,0" Height="35" Icon="Folder24" Content="Browse" Command="{Binding BrowseCustomIconLocationCommand}" />
|
||||
</Grid>
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Margin="0,4,0,0" FontSize="12" Text="Must be a multi-size .ico file with sizes 16px to 128px. Set Icon as 'Custom' to use it." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</Grid>
|
||||
</ui:CardExpander>
|
||||
<ui:Button Content="Preview" HorizontalAlignment="Stretch" Margin="0,8,0,0" Command="{Binding PreviewBootstrapperCommand}" />
|
||||
</StackPanel>
|
||||
</ui:UiPage>
|
16
Bloxstrap/UI/Elements/Menu/Pages/AppearancePage.xaml.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using Bloxstrap.UI.ViewModels.Menu;
|
||||
|
||||
namespace Bloxstrap.UI.Menu.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for AppearancePage.xaml
|
||||
/// </summary>
|
||||
public partial class AppearancePage
|
||||
{
|
||||
public AppearancePage()
|
||||
{
|
||||
DataContext = new AppearanceViewModel(this);
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
118
Bloxstrap/UI/Elements/Menu/Pages/BehaviourPage.xaml
Normal file
@ -0,0 +1,118 @@
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Menu.Pages.BehaviourPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="600" d:DesignWidth="800"
|
||||
Title="BehaviourPage"
|
||||
Scrollable="True">
|
||||
<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}" />
|
||||
|
||||
<ui:CardControl Margin="0,8,0,0">
|
||||
<ui:CardControl.Header>
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="Create desktop icon" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Bloxstrap will place an icon on the desktop that launches Roblox the next time it launches." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardControl.Header>
|
||||
<ui:ToggleSwitch IsChecked="{Binding CreateDesktopIcon, Mode=TwoWay}" />
|
||||
</ui:CardControl>
|
||||
|
||||
<ui:CardControl Margin="0,8,0,0">
|
||||
<ui:CardControl.Header>
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="Automatically update Bloxstrap" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Bloxstrap will automatically check and update itself when launching Roblox." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardControl.Header>
|
||||
<ui:ToggleSwitch IsChecked="{Binding UpdateCheckingEnabled, Mode=TwoWay}" />
|
||||
</ui:CardControl>
|
||||
|
||||
<ui:CardExpander Margin="0,8,0,0" IsExpanded="True">
|
||||
<ui:CardExpander.Header>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock FontSize="14" Text="Channel" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Choose which deployment channel Roblox should be downloaded from." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
<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>
|
||||
</ui:CardExpander.Header>
|
||||
<StackPanel>
|
||||
<Grid Margin="0,0,4,0">
|
||||
<Grid.Style>
|
||||
<Style>
|
||||
<Setter Property="Grid.Visibility" Value="Visible"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ChannelDeployInfo}" Value="{x:Null}">
|
||||
<Setter Property="Grid.Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Grid.Style>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Margin="0,0,16,8" VerticalAlignment="Center" Text="Version" />
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Foreground="{DynamicResource TextFillColorTertiaryBrush}" Text="{Binding ChannelDeployInfo.Version, Mode=OneWay}" />
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0,0,16,8" VerticalAlignment="Center" Text="VersionGuid" />
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Foreground="{DynamicResource TextFillColorTertiaryBrush}" Text="{Binding ChannelDeployInfo.VersionGuid, Mode=OneWay}" />
|
||||
|
||||
<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}" />
|
||||
|
||||
<StackPanel Grid.Row="3" Grid.ColumnSpan="2" Margin="0,16,0,0" Orientation="Horizontal" Visibility="{Binding ChannelWarningVisibility, Mode=OneWay}">
|
||||
<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." />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid Column="0">
|
||||
<Grid.Style>
|
||||
<Style>
|
||||
<Setter Property="Grid.Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ChannelDeployInfo}" Value="{x:Null}">
|
||||
<Setter Property="Grid.Visibility" Value="Visible" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Grid.Style>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</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" />
|
||||
|
||||
<TextBlock Grid.Column="1" Margin="16" VerticalAlignment="Center" Text="{Binding ChannelInfoLoadingText, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ui:CardExpander>
|
||||
|
||||
<ui:CardControl Margin="0,8,0,0">
|
||||
<ui:CardControl.Header>
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="Automatic channel change action" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Roblox or Bloxstrap may try to change your preferred channel." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</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}" />
|
||||
</ui:CardControl>
|
||||
</StackPanel>
|
||||
</ui:UiPage>
|
@ -1,6 +1,6 @@
|
||||
using Bloxstrap.ViewModels;
|
||||
using Bloxstrap.UI.ViewModels.Menu;
|
||||
|
||||
namespace Bloxstrap.Views.Pages
|
||||
namespace Bloxstrap.UI.Menu.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for BehaviourPage.xaml
|
193
Bloxstrap/UI/Elements/Menu/Pages/FastFlagsPage.xaml
Normal file
@ -0,0 +1,193 @@
|
||||
<ui:UiPage x:Class="Bloxstrap.UI.Menu.Pages.FastFlagsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:models="clr-namespace:Bloxstrap.UI.ViewModels"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="1000" d:DesignWidth="800"
|
||||
Title="FastFlagsPage"
|
||||
Scrollable="True">
|
||||
<StackPanel Margin="0,0,14,14">
|
||||
<TextBlock Text="Control how specific Roblox engine parameters and features are configured." FontSize="14" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
|
||||
<ui:CardAction x:Name="OpenClientSettingsCardAction" Grid.Row="1" Grid.ColumnSpan="2" Margin="0,16,0,0" Icon="DocumentEdit24" Command="{Binding OpenClientSettingsCommand}" IsEnabled="{Binding Source={x:Static models:GlobalViewModel.IsNotFirstRun}, Mode=OneTime}">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="Edit ClientAppSettings.json">
|
||||
<!--this is so fucking stupid the disabled state of the cardaction doesnt change the header text colour-->
|
||||
<TextBlock.Style>
|
||||
<Style>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ElementName=OpenClientSettingsCardAction, Path=IsEnabled, Mode=OneTime}" Value="False">
|
||||
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TextFillColorDisabledBrush}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Foreground="{DynamicResource TextFillColorTertiaryBrush}">
|
||||
<TextBlock.Style>
|
||||
<Style>
|
||||
<Setter Property="TextBlock.Text" Value="Where all FastFlags are saved to. Use this to manage your own flags."/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ElementName=OpenClientSettingsCardAction, Path=IsEnabled, Mode=OneTime}" Value="False">
|
||||
<Setter Property="TextBlock.Text" Value="Bloxstrap must first be installed." />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</ui:CardAction>
|
||||
|
||||
<StackPanel Visibility="{Binding ShowDebugFlags, Mode=OneTime}">
|
||||
<TextBlock Text="Debug" FontSize="16" FontWeight="Medium" Margin="0,16,0,0" />
|
||||
<ui:CardControl Margin="0,8,0,0">
|
||||
<ui:CardControl.Header>
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="HTTP request logging" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Enables logging of HTTP requests (DFLogHttpTraceLight=12)." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardControl.Header>
|
||||
<ui:ToggleSwitch IsChecked="{Binding HttpRequestLogging, Mode=TwoWay}" />
|
||||
</ui:CardControl>
|
||||
<ui:CardControl Margin="0,8,0,0">
|
||||
<ui:CardControl.Header>
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="HTTP proxy address" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Set blank if not using a proxy. Don't forget to add cacert.pem as a mod." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardControl.Header>
|
||||
<ui:TextBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" Text="{Binding HttpRequestProxy, Mode=TwoWay}" />
|
||||
</ui:CardControl>
|
||||
<ui:CardControl Margin="0,8,0,0">
|
||||
<ui:CardControl.Header>
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="Flag state overlay" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Show values of specified flags during runtime. Each flag is comma separated." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardControl.Header>
|
||||
<ui:TextBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" Text="{Binding StateOverlayFlags, Mode=TwoWay}" />
|
||||
</ui:CardControl>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Presets" FontSize="16" FontWeight="Medium" Margin="0,16,0,0" />
|
||||
<TextBlock Foreground="{DynamicResource TextFillColorSecondaryBrush}">
|
||||
FastFlags 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>
|
||||
(Alt+Enter) and
|
||||
<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>
|
||||
<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="Framerate limit" />
|
||||
<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#framerate-limit">
|
||||
<ui:SymbolIcon Symbol="QuestionCircle48" Margin="0,1,0,0" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Capped to 60 FPS by default. Use a large number like 9999 for no limit." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardControl.Header>
|
||||
<ui:TextBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" Text="{Binding FramerateLimit, Mode=TwoWay}" PreviewTextInput="ValidateInt32" />
|
||||
</ui:CardControl>
|
||||
<ui:CardControl Margin="0,8,0,0">
|
||||
<ui:CardControl.Header>
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="Preferred lighting technology" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Choose which lighting technology should be forced enabled in all games." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardControl.Header>
|
||||
<ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" ItemsSource="{Binding LightingTechnologies.Keys, Mode=OneTime}" Text="{Binding SelectedLightingTechnology, 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="Preferred escape menu version" />
|
||||
<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#escape-menu-version">
|
||||
<ui:SymbolIcon Symbol="QuestionCircle48" Margin="0,1,0,0" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Choose which version of the escape menu to use." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardControl.Header>
|
||||
<ComboBox Margin="5,0,0,0" Padding="10,5,10,5" Width="200" ItemsSource="{Binding IGMenuVersions.Keys, Mode=OneTime}" Text="{Binding SelectedIGMenuVersion, 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="Enable ability to hide GUIs" />
|
||||
<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#gui-hiding">
|
||||
<ui:SymbolIcon Symbol="QuestionCircle48" Margin="0,1,0,0" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Foreground="{DynamicResource TextFillColorTertiaryBrush}">
|
||||
Toggled with <Hyperlink Foreground="{DynamicResource TextFillColorPrimaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://github.com/pizzaboxer/bloxstrap/wiki/A-guide-to-FastFlags#gui-hiding">keyboard shortcuts</Hyperlink>. Only works if you're in the <Hyperlink Foreground="{DynamicResource TextFillColorPrimaryBrush}" Command="models:GlobalViewModel.OpenWebpageCommand" CommandParameter="https://www.roblox.com/groups/32380007/Bloxstrap">Bloxstrap group</Hyperlink>.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</ui:CardControl.Header>
|
||||
<ui:ToggleSwitch IsChecked="{Binding GuiHidingEnabled, 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="Use old material textures" />
|
||||
<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#old-material-textures">
|
||||
<ui:SymbolIcon Symbol="QuestionCircle48" Margin="0,1,0,0" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Toggle whether to use the old material textures used prior to 2022." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardControl.Header>
|
||||
<ui:ToggleSwitch IsChecked="{Binding Pre2022TexturesEnabled, Mode=TwoWay}" />
|
||||
</ui:CardControl>
|
||||
<ui:CardControl Margin="0,8,0,0">
|
||||
<ui:CardControl.Header>
|
||||
<StackPanel>
|
||||
<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}" />
|
||||
</StackPanel>
|
||||
</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}" />
|
||||
</ui:CardControl>
|
||||
<ui:CardControl Margin="0,8,0,0">
|
||||
<ui:CardControl.Header>
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="14" Text="Use alternate graphics quality selector" />
|
||||
<TextBlock Margin="0,2,0,0" FontSize="12" Text="Toggle between using the consolidated 1-10 / fine-grained 1-21 graphics quality slider." Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:CardControl.Header>
|
||||
<ui:ToggleSwitch IsChecked="{Binding AlternateGraphicsSelectorEnabled, Mode=TwoWay}" />
|
||||
</ui:CardControl>
|
||||
</StackPanel>
|
||||
</ui:UiPage>
|
@ -1,8 +1,8 @@
|
||||
using Bloxstrap.ViewModels;
|
||||
using System.Windows.Input;
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Bloxstrap.Views.Pages
|
||||
using Bloxstrap.UI.ViewModels.Menu;
|
||||
|
||||
namespace Bloxstrap.UI.Menu.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for FastFlagsPage.xaml
|