Merge branch 'main' into user-pfp-discord-rpc

This commit is contained in:
axell 2024-08-30 16:46:46 +02:00 committed by GitHub
commit b61adf1a79
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 407 additions and 328 deletions

View File

@ -6,9 +6,6 @@ using System.Windows.Threading;
using Microsoft.Win32; using Microsoft.Win32;
using Bloxstrap.Models.SettingTasks.Base; using Bloxstrap.Models.SettingTasks.Base;
using Bloxstrap.UI.Elements.About.Pages;
using Bloxstrap.UI.Elements.About;
using System;
namespace Bloxstrap namespace Bloxstrap
{ {
@ -18,7 +15,11 @@ namespace Bloxstrap
public partial class App : Application public partial class App : Application
{ {
public const string ProjectName = "Bloxstrap"; public const string ProjectName = "Bloxstrap";
public const string ProjectOwner = "pizzaboxer";
public const string ProjectRepository = "pizzaboxer/bloxstrap"; public const string ProjectRepository = "pizzaboxer/bloxstrap";
public const string ProjectDownloadLink = "https://bloxstrap.pizzaboxer.xyz";
public const string ProjectHelpLink = "https://github.com/pizzaboxer/bloxstrap/wiki";
public const string ProjectSupportLink = "https://github.com/pizzaboxer/bloxstrap/issues/new";
public const string RobloxPlayerAppName = "RobloxPlayerBeta"; public const string RobloxPlayerAppName = "RobloxPlayerBeta";
public const string RobloxStudioAppName = "RobloxStudioBeta"; public const string RobloxStudioAppName = "RobloxStudioBeta";
@ -106,6 +107,27 @@ namespace Bloxstrap
Terminate(ErrorCode.ERROR_INSTALL_FAILURE); Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
} }
public static async Task<GithubRelease?> GetLatestRelease()
{
const string LOG_IDENT = "App::GetLatestRelease";
GithubRelease? releaseInfo = null;
try
{
releaseInfo = await Http.GetJson<GithubRelease>($"https://api.github.com/repos/{ProjectRepository}/releases/latest");
if (releaseInfo is null || releaseInfo.Assets is null)
Logger.WriteLine(LOG_IDENT, "Encountered invalid data");
}
catch (Exception ex)
{
Logger.WriteException(LOG_IDENT, ex);
}
return releaseInfo;
}
protected override void OnStartup(StartupEventArgs e) protected override void OnStartup(StartupEventArgs e)
{ {
const string LOG_IDENT = "App::OnStartup"; const string LOG_IDENT = "App::OnStartup";
@ -224,7 +246,7 @@ namespace Bloxstrap
Locale.Set(Settings.Prop.Locale); Locale.Set(Settings.Prop.Locale);
#if !DEBUG #if !DEBUG
if (!LaunchSettings.UninstallFlag.Active) if (!LaunchSettings.BypassUpdateCheck)
Installer.HandleUpgrade(); Installer.HandleUpgrade();
#endif #endif

View File

@ -1,4 +1,17 @@
using System.Windows; // To debug the automatic updater:
// - Uncomment the definition below
// - Publish the executable
// - Launch the executable (click no when it asks you to upgrade)
// - Launch Roblox (for testing web launches, run it from the command prompt)
// - To re-test the same executable, delete it from the installation folder
// #define DEBUG_UPDATER
#if DEBUG_UPDATER
#warning "Automatic updater debugging is enabled"
#endif
using System.Windows;
using System.Windows.Forms; using System.Windows.Forms;
using Microsoft.Win32; using Microsoft.Win32;
@ -152,9 +165,14 @@ namespace Bloxstrap
await RobloxDeployment.GetInfo(RobloxDeployment.DefaultChannel); await RobloxDeployment.GetInfo(RobloxDeployment.DefaultChannel);
#if !DEBUG #if !DEBUG || DEBUG_UPDATER
if (App.Settings.Prop.CheckForUpdates) if (App.Settings.Prop.CheckForUpdates && !App.LaunchSettings.UpgradeFlag.Active)
await CheckForUpdates(); {
bool updatePresent = await CheckForUpdates();
if (updatePresent)
return;
}
#endif #endif
// ensure only one instance of the bootstrapper is running at the time // ensure only one instance of the bootstrapper is running at the time
@ -212,7 +230,9 @@ namespace Bloxstrap
await mutex.ReleaseAsync(); await mutex.ReleaseAsync();
if (!App.LaunchSettings.NoLaunchFlag.Active && !_cancelFired) if (!App.LaunchSettings.NoLaunchFlag.Active && !_cancelFired)
await StartRoblox(); StartRoblox();
Dialog?.CloseBootstrapper();
} }
private async Task CheckLatestVersion() private async Task CheckLatestVersion()
@ -273,12 +293,14 @@ namespace Bloxstrap
_versionPackageManifest = await PackageManifest.Get(_latestVersionGuid); _versionPackageManifest = await PackageManifest.Get(_latestVersionGuid);
} }
private async Task StartRoblox() private void StartRoblox()
{ {
const string LOG_IDENT = "Bootstrapper::StartRoblox"; const string LOG_IDENT = "Bootstrapper::StartRoblox";
SetStatus(Strings.Bootstrapper_Status_Starting); SetStatus(Strings.Bootstrapper_Status_Starting);
if (_launchMode == LaunchMode.Player)
{
if (App.Settings.Prop.ForceRobloxLanguage) if (App.Settings.Prop.ForceRobloxLanguage)
{ {
var match = Regex.Match(_launchCommandLine, "gameLocale:([a-z_]+)", RegexOptions.CultureInvariant); var match = Regex.Match(_launchCommandLine, "gameLocale:([a-z_]+)", RegexOptions.CultureInvariant);
@ -287,6 +309,12 @@ namespace Bloxstrap
_launchCommandLine = _launchCommandLine.Replace("robloxLocale:en_us", $"robloxLocale:{match.Groups[1].Value}", StringComparison.InvariantCultureIgnoreCase); _launchCommandLine = _launchCommandLine.Replace("robloxLocale:en_us", $"robloxLocale:{match.Groups[1].Value}", StringComparison.InvariantCultureIgnoreCase);
} }
if (!String.IsNullOrEmpty(_launchCommandLine))
_launchCommandLine += " ";
_launchCommandLine += "-isInstallerLaunch";
}
var startInfo = new ProcessStartInfo() var startInfo = new ProcessStartInfo()
{ {
FileName = _playerLocation, FileName = _playerLocation,
@ -297,10 +325,11 @@ namespace Bloxstrap
if (_launchMode == LaunchMode.StudioAuth) if (_launchMode == LaunchMode.StudioAuth)
{ {
Process.Start(startInfo); Process.Start(startInfo);
Dialog?.CloseBootstrapper();
return; return;
} }
using var startEvent = new EventWaitHandle(false, EventResetMode.ManualReset, AppData.StartEvent);
// v2.2.0 - byfron will trip if we keep a process handle open for over a minute, so we're doing this now // v2.2.0 - byfron will trip if we keep a process handle open for over a minute, so we're doing this now
int gameClientPid; int gameClientPid;
using (var gameClient = Process.Start(startInfo)!) using (var gameClient = Process.Start(startInfo)!)
@ -308,20 +337,16 @@ namespace Bloxstrap
gameClientPid = gameClient.Id; gameClientPid = gameClient.Id;
} }
App.Logger.WriteLine(LOG_IDENT, $"Started Roblox (PID {gameClientPid})"); App.Logger.WriteLine(LOG_IDENT, $"Started Roblox (PID {gameClientPid}), waiting for start event");
using (var startEvent = new SystemEvent(AppData.StartEvent)) if (!startEvent.WaitOne(TimeSpan.FromSeconds(10)))
{ {
// TODO: get rid of this Frontend.ShowPlayerErrorDialog();
bool startEventFired = await startEvent.WaitForEvent();
startEvent.Close();
// TODO: this cannot silently exit like this
if (!startEventFired)
return; return;
} }
App.Logger.WriteLine(LOG_IDENT, "Start event signalled");
var autoclosePids = new List<int>(); var autoclosePids = new List<int>();
// launch custom integrations now // launch custom integrations now
@ -352,20 +377,16 @@ namespace Bloxstrap
autoclosePids.Add(pid); autoclosePids.Add(pid);
} }
using (var proclock = new InterProcessLock("Watcher"))
{
string args = gameClientPid.ToString(); string args = gameClientPid.ToString();
if (autoclosePids.Any()) if (autoclosePids.Any())
args += $";{String.Join(',', autoclosePids)}"; args += $";{String.Join(',', autoclosePids)}";
if (proclock.IsAcquired) using (var ipl = new InterProcessLock("Watcher"))
{
if (ipl.IsAcquired)
Process.Start(Paths.Process, $"-watcher \"{args}\""); Process.Start(Paths.Process, $"-watcher \"{args}\"");
} }
// event fired, wait for 3 seconds then close
await Task.Delay(3000);
Dialog?.CloseBootstrapper();
} }
public void CancelInstall() public void CancelInstall()
@ -446,53 +467,56 @@ namespace Bloxstrap
#endif #endif
} }
private async Task CheckForUpdates() private async Task<bool> CheckForUpdates()
{ {
const string LOG_IDENT = "Bootstrapper::CheckForUpdates"; const string LOG_IDENT = "Bootstrapper::CheckForUpdates";
// don't update if there's another instance running (likely running in the background) // don't update if there's another instance running (likely running in the background)
if (Process.GetProcessesByName(App.ProjectName).Count() > 1) // i don't like this, but there isn't much better way of doing it /shrug
if (Process.GetProcessesByName(App.ProjectName).Length > 1)
{ {
App.Logger.WriteLine(LOG_IDENT, $"More than one Bloxstrap instance running, aborting update check"); App.Logger.WriteLine(LOG_IDENT, $"More than one Bloxstrap instance running, aborting update check");
return; return false;
} }
App.Logger.WriteLine(LOG_IDENT, $"Checking for updates..."); App.Logger.WriteLine(LOG_IDENT, "Checking for updates...");
GithubRelease? releaseInfo; #if !DEBUG_UPDATER
var releaseInfo = await App.GetLatestRelease();
try if (releaseInfo is null)
{ return false;
releaseInfo = await Http.GetJson<GithubRelease>($"https://api.github.com/repos/{App.ProjectRepository}/releases/latest");
}
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, $"Failed to fetch releases: {ex}");
return;
}
if (releaseInfo is null || releaseInfo.Assets is null)
{
App.Logger.WriteLine(LOG_IDENT, $"No updates found");
return;
}
var versionComparison = Utilities.CompareVersions(App.Version, releaseInfo.TagName); var 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 // check if we aren't using a deployed build, so we can update to one if a new version comes out
if (versionComparison == VersionComparison.Equal && App.IsProductionBuild || versionComparison == VersionComparison.GreaterThan) if (App.IsProductionBuild && versionComparison == VersionComparison.Equal || versionComparison == VersionComparison.GreaterThan)
{ {
App.Logger.WriteLine(LOG_IDENT, $"No updates found"); App.Logger.WriteLine(LOG_IDENT, "No updates found");
return; return false;
} }
string version = releaseInfo.TagName;
#else
string version = App.Version;
#endif
SetStatus(Strings.Bootstrapper_Status_UpgradingBloxstrap); SetStatus(Strings.Bootstrapper_Status_UpgradingBloxstrap);
try try
{ {
// 64-bit is always the first option #if DEBUG_UPDATER
GithubReleaseAsset asset = releaseInfo.Assets[0]; string downloadLocation = Path.Combine(Paths.TempUpdates, "Bloxstrap.exe");
string downloadLocation = Path.Combine(Paths.LocalAppData, "Temp", asset.Name);
Directory.CreateDirectory(Paths.TempUpdates);
File.Copy(Paths.Process, downloadLocation, true);
#else
var asset = releaseInfo.Assets![0];
string downloadLocation = Path.Combine(Paths.TempUpdates, asset.Name);
Directory.CreateDirectory(Paths.TempUpdates);
App.Logger.WriteLine(LOG_IDENT, $"Downloading {releaseInfo.TagName}..."); App.Logger.WriteLine(LOG_IDENT, $"Downloading {releaseInfo.TagName}...");
@ -500,25 +524,35 @@ namespace Bloxstrap
{ {
var response = await App.HttpClient.GetAsync(asset.BrowserDownloadUrl); var response = await App.HttpClient.GetAsync(asset.BrowserDownloadUrl);
await using var fileStream = new FileStream(downloadLocation, FileMode.CreateNew); await using var fileStream = new FileStream(downloadLocation, FileMode.OpenOrCreate, FileAccess.Write);
await response.Content.CopyToAsync(fileStream); await response.Content.CopyToAsync(fileStream);
} }
#endif
App.Logger.WriteLine(LOG_IDENT, $"Starting {releaseInfo.TagName}..."); App.Logger.WriteLine(LOG_IDENT, $"Starting {version}...");
ProcessStartInfo startInfo = new() ProcessStartInfo startInfo = new()
{ {
FileName = downloadLocation, FileName = downloadLocation,
}; };
startInfo.ArgumentList.Add("-upgrade");
foreach (string arg in App.LaunchSettings.Args) foreach (string arg in App.LaunchSettings.Args)
startInfo.ArgumentList.Add(arg); startInfo.ArgumentList.Add(arg);
if (_launchMode == LaunchMode.Player && !startInfo.ArgumentList.Contains("-player"))
startInfo.ArgumentList.Add("-player");
else if (_launchMode == LaunchMode.Studio && !startInfo.ArgumentList.Contains("-studio"))
startInfo.ArgumentList.Add("-studio");
App.Settings.Save(); App.Settings.Save();
new InterProcessLock("AutoUpdater");
Process.Start(startInfo); Process.Start(startInfo);
App.Terminate(); return true;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -526,10 +560,14 @@ namespace Bloxstrap
App.Logger.WriteException(LOG_IDENT, ex); App.Logger.WriteException(LOG_IDENT, ex);
Frontend.ShowMessageBox( Frontend.ShowMessageBox(
string.Format(Strings.Bootstrapper_AutoUpdateFailed, releaseInfo.TagName), string.Format(Strings.Bootstrapper_AutoUpdateFailed, version),
MessageBoxImage.Information MessageBoxImage.Information
); );
Utilities.ShellExecute(App.ProjectDownloadLink);
} }
return false;
} }
#endregion #endregion
@ -848,6 +886,7 @@ namespace Bloxstrap
foreach (FontFace fontFace in fontFamilyData.Faces) foreach (FontFace fontFace in fontFamilyData.Faces)
fontFace.AssetId = "rbxasset://fonts/CustomFont.ttf"; fontFace.AssetId = "rbxasset://fonts/CustomFont.ttf";
// TODO: writing on every launch is not necessary
File.WriteAllText(modFilepath, JsonSerializer.Serialize(fontFamilyData, new JsonSerializerOptions { WriteIndented = true })); File.WriteAllText(modFilepath, JsonSerializer.Serialize(fontFamilyData, new JsonSerializerOptions { WriteIndented = true }));
} }
@ -899,6 +938,8 @@ namespace Bloxstrap
// the manifest is primarily here to keep track of what files have been // the manifest is primarily here to keep track of what files have been
// deleted from the modifications folder, so that we know when to restore the original files from the downloaded packages // deleted from the modifications folder, so that we know when to restore the original files from the downloaded packages
// now check for files that have been deleted from the mod folder according to the manifest // now check for files that have been deleted from the mod folder according to the manifest
// TODO: this needs to extract the files from packages in bulk, this is way too slow
foreach (string fileLocation in App.State.Prop.ModManifest) foreach (string fileLocation in App.State.Prop.ModManifest)
{ {
if (modFolderFiles.Contains(fileLocation)) if (modFolderFiles.Contains(fileLocation))

View File

@ -1,9 +1,4 @@
using System.DirectoryServices; using System.Windows;
using System.Reflection;
using System.Reflection.Metadata.Ecma335;
using System.Windows;
using System.Windows.Media.Animation;
using Bloxstrap.Resources;
using Microsoft.Win32; using Microsoft.Win32;
namespace Bloxstrap namespace Bloxstrap
@ -50,12 +45,13 @@ namespace Bloxstrap
uninstallKey.SetValue("InstallLocation", Paths.Base); uninstallKey.SetValue("InstallLocation", Paths.Base);
uninstallKey.SetValue("NoRepair", 1); uninstallKey.SetValue("NoRepair", 1);
uninstallKey.SetValue("Publisher", "pizzaboxer"); uninstallKey.SetValue("Publisher", App.ProjectOwner);
uninstallKey.SetValue("ModifyPath", $"\"{Paths.Application}\" -settings"); uninstallKey.SetValue("ModifyPath", $"\"{Paths.Application}\" -settings");
uninstallKey.SetValue("QuietUninstallString", $"\"{Paths.Application}\" -uninstall -quiet"); uninstallKey.SetValue("QuietUninstallString", $"\"{Paths.Application}\" -uninstall -quiet");
uninstallKey.SetValue("UninstallString", $"\"{Paths.Application}\" -uninstall"); uninstallKey.SetValue("UninstallString", $"\"{Paths.Application}\" -uninstall");
uninstallKey.SetValue("URLInfoAbout", $"https://github.com/{App.ProjectRepository}"); uninstallKey.SetValue("HelpLink", App.ProjectHelpLink);
uninstallKey.SetValue("URLUpdateInfo", $"https://github.com/{App.ProjectRepository}/releases/latest"); uninstallKey.SetValue("URLInfoAbout", App.ProjectSupportLink);
uninstallKey.SetValue("URLUpdateInfo", App.ProjectDownloadLink);
} }
// only register player, for the scenario where the user installs bloxstrap, closes it, // only register player, for the scenario where the user installs bloxstrap, closes it,
@ -331,8 +327,9 @@ namespace Bloxstrap
return; return;
// 2.0.0 downloads updates to <BaseFolder>/Updates so lol // 2.0.0 downloads updates to <BaseFolder>/Updates so lol
// TODO: 2.8.0 will download them to <Temp>/Bloxstrap/Updates bool isAutoUpgrade = App.LaunchSettings.UpgradeFlag.Active
bool isAutoUpgrade = Paths.Process.StartsWith(Path.Combine(Paths.Base, "Updates")) || Paths.Process.StartsWith(Path.Combine(Paths.LocalAppData, "Temp")); || Paths.Process.StartsWith(Path.Combine(Paths.Base, "Updates"))
|| Paths.Process.StartsWith(Paths.Temp);
var existingVer = FileVersionInfo.GetVersionInfo(Paths.Application).ProductVersion; var existingVer = FileVersionInfo.GetVersionInfo(Paths.Application).ProductVersion;
var currentVer = FileVersionInfo.GetVersionInfo(Paths.Process).ProductVersion; var currentVer = FileVersionInfo.GetVersionInfo(Paths.Process).ProductVersion;
@ -353,7 +350,7 @@ namespace Bloxstrap
} }
// silently upgrade version if the command line flag is set or if we're launching from an auto update // silently upgrade version if the command line flag is set or if we're launching from an auto update
if (!App.LaunchSettings.UpgradeFlag.Active && !isAutoUpgrade) if (!isAutoUpgrade)
{ {
var result = Frontend.ShowMessageBox( var result = Frontend.ShowMessageBox(
Strings.InstallChecker_VersionDifferentThanInstalled, Strings.InstallChecker_VersionDifferentThanInstalled,
@ -365,41 +362,38 @@ namespace Bloxstrap
return; return;
} }
App.Logger.WriteLine(LOG_IDENT, "Doing upgrade");
Filesystem.AssertReadOnly(Paths.Application); Filesystem.AssertReadOnly(Paths.Application);
// TODO: make this use a mutex somehow using (var ipl = new InterProcessLock("AutoUpdater", TimeSpan.FromSeconds(5)))
// yes, this is EXTREMELY hacky, but the updater process that launched the
// new version may still be open and so we have to wait for it to close
int attempts = 0;
while (attempts < 10)
{ {
attempts++; if (!ipl.IsAcquired)
{
App.Logger.WriteLine(LOG_IDENT, "Failed to update! (Could not obtain singleton mutex)");
return;
}
}
try try
{ {
File.Delete(Paths.Application); File.Copy(Paths.Process, Paths.Application, true);
break;
} }
catch (Exception) catch (Exception ex)
{ {
if (attempts == 1) App.Logger.WriteLine(LOG_IDENT, "Failed to update! (Could not replace executable)");
App.Logger.WriteLine(LOG_IDENT, "Waiting for write permissions to update version"); App.Logger.WriteException(LOG_IDENT, ex);
Thread.Sleep(500);
}
}
if (attempts == 10)
{
App.Logger.WriteLine(LOG_IDENT, "Failed to update! (Could not get write permissions after 5 seconds)");
return; return;
} }
File.Copy(Paths.Process, Paths.Application);
using (var uninstallKey = Registry.CurrentUser.CreateSubKey(App.UninstallKey)) using (var uninstallKey = Registry.CurrentUser.CreateSubKey(App.UninstallKey))
{ {
uninstallKey.SetValue("DisplayVersion", App.Version); uninstallKey.SetValue("DisplayVersion", App.Version);
uninstallKey.SetValue("Publisher", App.ProjectOwner);
uninstallKey.SetValue("HelpLink", App.ProjectHelpLink);
uninstallKey.SetValue("URLInfoAbout", App.ProjectSupportLink);
uninstallKey.SetValue("URLUpdateInfo", App.ProjectDownloadLink);
} }
// update migrations // update migrations

View File

@ -1,4 +1,6 @@
namespace Bloxstrap.Integrations using System.Web;
namespace Bloxstrap.Integrations
{ {
public class ActivityWatcher : IDisposable public class ActivityWatcher : IDisposable
{ {
@ -46,6 +48,7 @@
public string ActivityMachineAddress = ""; public string ActivityMachineAddress = "";
public bool ActivityMachineUDMUX = false; public bool ActivityMachineUDMUX = false;
public bool ActivityIsTeleport = false; public bool ActivityIsTeleport = false;
public string ActivityLaunchData = "";
public ServerType ActivityServerType = ServerType.Public; public ServerType ActivityServerType = ServerType.Public;
public bool IsDisposed = false; public bool IsDisposed = false;
@ -122,6 +125,16 @@
} }
} }
public string GetActivityDeeplink()
{
string deeplink = $"roblox://experiences/start?placeId={ActivityPlaceId}&gameInstanceId={ActivityJobId}";
if (!String.IsNullOrEmpty(ActivityLaunchData))
deeplink += "&launchData=" + HttpUtility.UrlEncode(ActivityLaunchData);
return deeplink;
}
private void ReadLogEntry(string entry) private void ReadLogEntry(string entry)
{ {
const string LOG_IDENT = "ActivityWatcher::ReadLogEntry"; const string LOG_IDENT = "ActivityWatcher::ReadLogEntry";
@ -238,6 +251,7 @@
ActivityMachineAddress = ""; ActivityMachineAddress = "";
ActivityMachineUDMUX = false; ActivityMachineUDMUX = false;
ActivityIsTeleport = false; ActivityIsTeleport = false;
ActivityLaunchData = "";
ActivityServerType = ServerType.Public; ActivityServerType = ServerType.Public;
ActivityUserId = ""; ActivityUserId = "";
@ -297,6 +311,35 @@
return; return;
} }
if (message.Command == "SetLaunchData")
{
string? data;
try
{
data = message.Data.Deserialize<string>();
}
catch (Exception)
{
App.Logger.WriteLine(LOG_IDENT, "Failed to parse message! (JSON deserialization threw an exception)");
return;
}
if (data is null)
{
App.Logger.WriteLine(LOG_IDENT, "Failed to parse message! (JSON deserialization returned null)");
return;
}
if (data.Length > 200)
{
App.Logger.WriteLine(LOG_IDENT, "Data cannot be longer than 200 characters");
return;
}
ActivityLaunchData = data;
}
OnRPCMessage?.Invoke(this, message); OnRPCMessage?.Invoke(this, message);
LastRPCRequest = DateTime.Now; LastRPCRequest = DateTime.Now;

View File

@ -9,7 +9,7 @@ namespace Bloxstrap.Integrations
private DiscordRPC.RichPresence? _currentPresence; private DiscordRPC.RichPresence? _currentPresence;
private DiscordRPC.RichPresence? _currentPresenceCopy; private DiscordRPC.RichPresence? _currentPresenceCopy;
private Message? _stashedRPCMessage; private Queue<Message> _messageQueue = new();
private bool _visible = true; private bool _visible = true;
private long _currentUniverseId; private long _currentUniverseId;
@ -17,7 +17,7 @@ namespace Bloxstrap.Integrations
public DiscordRichPresence(ActivityWatcher activityWatcher) public DiscordRichPresence(ActivityWatcher activityWatcher)
{ {
const string LOG_IDENT = "DiscordRichPresence::DiscordRichPresence"; const string LOG_IDENT = "DiscordRichPresence";
_activityWatcher = activityWatcher; _activityWatcher = activityWatcher;
@ -47,30 +47,35 @@ namespace Bloxstrap.Integrations
_rpcClient.Initialize(); _rpcClient.Initialize();
} }
public void ProcessRPCMessage(Message message) public void ProcessRPCMessage(Message message, bool implicitUpdate = true)
{ {
const string LOG_IDENT = "DiscordRichPresence::ProcessRPCMessage"; const string LOG_IDENT = "DiscordRichPresence::ProcessRPCMessage";
if (message.Command != "SetRichPresence") if (message.Command != "SetRichPresence" && message.Command != "SetLaunchData")
return; return;
if (_currentPresence is null || _currentPresenceCopy is null) if (_currentPresence is null || _currentPresenceCopy is null)
{ {
if (_activityWatcher.ActivityInGame) App.Logger.WriteLine(LOG_IDENT, "Presence is not set, enqueuing message");
{ _messageQueue.Enqueue(message);
App.Logger.WriteLine(LOG_IDENT, "Presence is not yet set, but is currently in game, stashing presence set request");
_stashedRPCMessage = message;
return; return;
} }
App.Logger.WriteLine(LOG_IDENT, "Presence is not set, aborting");
return;
}
Models.BloxstrapRPC.RichPresence? presenceData;
// a lot of repeated code here, could this somehow be cleaned up? // a lot of repeated code here, could this somehow be cleaned up?
if (message.Command == "SetLaunchData")
{
var buttonQuery = _currentPresence.Buttons.Where(x => x.Label == "Join server");
if (!buttonQuery.Any())
return;
buttonQuery.First().Url = _activityWatcher.GetActivityDeeplink();
}
else if (message.Command == "SetRichPresence")
{
Models.BloxstrapRPC.RichPresence? presenceData;
try try
{ {
presenceData = message.Data.Deserialize<Models.BloxstrapRPC.RichPresence>(); presenceData = message.Data.Deserialize<Models.BloxstrapRPC.RichPresence>();
@ -158,7 +163,9 @@ namespace Bloxstrap.Integrations
_currentPresence.Assets.LargeImageText = presenceData.LargeImage.HoverText; _currentPresence.Assets.LargeImageText = presenceData.LargeImage.HoverText;
} }
} }
}
if (implicitUpdate)
UpdatePresence(); UpdatePresence();
} }
@ -183,7 +190,7 @@ namespace Bloxstrap.Integrations
App.Logger.WriteLine(LOG_IDENT, "Not in game, clearing presence"); App.Logger.WriteLine(LOG_IDENT, "Not in game, clearing presence");
_currentPresence = _currentPresenceCopy = null; _currentPresence = _currentPresenceCopy = null;
_stashedRPCMessage = null; _messageQueue.Clear();
UpdatePresence(); UpdatePresence();
return true; return true;
@ -314,17 +321,13 @@ namespace Bloxstrap.Integrations
// this is used for configuration from BloxstrapRPC // this is used for configuration from BloxstrapRPC
_currentPresenceCopy = _currentPresence.Clone(); _currentPresenceCopy = _currentPresence.Clone();
// TODO: use queue for stashing messages if (_messageQueue.Any())
if (_stashedRPCMessage is not null)
{ {
App.Logger.WriteLine(LOG_IDENT, "Found stashed RPC message, invoking presence set command now"); App.Logger.WriteLine(LOG_IDENT, "Processing queued messages");
ProcessRPCMessage(_stashedRPCMessage); ProcessRPCMessage(_messageQueue.Dequeue(), false);
_stashedRPCMessage = null;
} }
else
{
UpdatePresence(); UpdatePresence();
}
return true; return true;
} }

View File

@ -28,6 +28,8 @@ namespace Bloxstrap
public LaunchFlag StudioFlag { get; } = new("studio"); public LaunchFlag StudioFlag { get; } = new("studio");
public bool BypassUpdateCheck => UninstallFlag.Active || WatcherFlag.Active;
public LaunchMode RobloxLaunchMode { get; set; } = LaunchMode.None; public LaunchMode RobloxLaunchMode { get; set; } = LaunchMode.None;
public string RobloxLaunchArgs { get; private set; } = ""; public string RobloxLaunchArgs { get; private set; } = "";
@ -37,7 +39,7 @@ namespace Bloxstrap
/// </summary> /// </summary>
public string[] Args { get; private set; } public string[] Args { get; private set; }
private Dictionary<string, LaunchFlag> _flagMap = new(); private readonly Dictionary<string, LaunchFlag> _flagMap = new();
public LaunchSettings(string[] args) public LaunchSettings(string[] args)
{ {
@ -68,7 +70,7 @@ namespace Bloxstrap
string identifier = arg[1..]; string identifier = arg[1..];
if (_flagMap[identifier] is not LaunchFlag flag) if (!_flagMap.TryGetValue(identifier, out LaunchFlag? flag) || flag is null)
continue; continue;
flag.Active = true; flag.Active = true;

View File

@ -4,6 +4,7 @@
{ {
// note that these are directories that aren't tethered to the basedirectory // note that these are directories that aren't tethered to the basedirectory
// so these can safely be called before initialization // so these can safely be called before initialization
public static string Temp => Path.Combine(Path.GetTempPath(), App.ProjectName);
public static string UserProfile => Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); public static string UserProfile => Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
public static string LocalAppData => Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); public static string LocalAppData => Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
public static string Desktop => Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); public static string Desktop => Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
@ -12,6 +13,9 @@
public static string Process => Environment.ProcessPath!; public static string Process => Environment.ProcessPath!;
public static string TempUpdates => Path.Combine(Temp, "Updates");
public static string TempLogs => Path.Combine(Temp, "Logs");
public static string Base { get; private set; } = ""; public static string Base { get; private set; } = "";
public static string Downloads { get; private set; } = ""; public static string Downloads { get; private set; } = "";
public static string Logs { get; private set; } = ""; public static string Logs { get; private set; } = "";

View File

@ -106,7 +106,7 @@ namespace Bloxstrap.Resources {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Bloxstrap was unable to auto-update to {0}. Please update it manually by downloading and running the latest release from the GitHub page.. /// Looks up a localized string similar to Bloxstrap was unable to automatically update to version {0}. Please update it manually by downloading and running it from the website..
/// </summary> /// </summary>
public static string Bootstrapper_AutoUpdateFailed { public static string Bootstrapper_AutoUpdateFailed {
get { get {
@ -886,6 +886,33 @@ namespace Bloxstrap.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Roblox has crashed..
/// </summary>
public static string Dialog_PlayerError_Crash {
get {
return ResourceManager.GetString("Dialog.PlayerError.Crash", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Roblox failed to launch..
/// </summary>
public static string Dialog_PlayerError_FailedLaunch {
get {
return ResourceManager.GetString("Dialog.PlayerError.FailedLaunch", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please read the following help information, which will open in your web browser when you close this dialog..
/// </summary>
public static string Dialog_PlayerError_HelpInformation {
get {
return ResourceManager.GetString("Dialog.PlayerError.HelpInformation", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Early 2015. /// Looks up a localized string similar to Early 2015.
/// </summary> /// </summary>
@ -2519,15 +2546,6 @@ namespace Bloxstrap.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to e.g. C:\Windows\System32\cmd.exe.
/// </summary>
public static string Menu_Integrations_Custom_AppLocation_Placeholder {
get {
return ResourceManager.GetString("Menu.Integrations.Custom.AppLocation.Placeholder", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Auto close when Roblox closes. /// Looks up a localized string similar to Auto close when Roblox closes.
/// </summary> /// </summary>
@ -2556,7 +2574,7 @@ namespace Bloxstrap.Resources {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to e.g. /k echo Roblox is running!. /// Looks up a localized string similar to Roblox is running!.
/// </summary> /// </summary>
public static string Menu_Integrations_Custom_LaunchArgs_Placeholder { public static string Menu_Integrations_Custom_LaunchArgs_Placeholder {
get { get {

View File

@ -124,7 +124,7 @@
<value>lookup failed</value> <value>lookup failed</value>
</data> </data>
<data name="Bootstrapper.AutoUpdateFailed" xml:space="preserve"> <data name="Bootstrapper.AutoUpdateFailed" xml:space="preserve">
<value>Bloxstrap was unable to auto-update to {0}. Please update it manually by downloading and running the latest release from the GitHub page.</value> <value>Bloxstrap was unable to automatically update to version {0}. Please update it manually by downloading and running it from the website.</value>
</data> </data>
<data name="Bootstrapper.ConfirmLaunch" xml:space="preserve"> <data name="Bootstrapper.ConfirmLaunch" xml:space="preserve">
<value>Roblox is currently running, and launching another instance will close it. Are you sure you want to continue launching?</value> <value>Roblox is currently running, and launching another instance will close it. Are you sure you want to continue launching?</value>
@ -733,9 +733,6 @@ Selecting 'No' will ignore this warning and continue installation.</value>
<data name="Menu.Integrations.Custom.AppLocation" xml:space="preserve"> <data name="Menu.Integrations.Custom.AppLocation" xml:space="preserve">
<value>Application Location</value> <value>Application Location</value>
</data> </data>
<data name="Menu.Integrations.Custom.AppLocation.Placeholder" xml:space="preserve">
<value>e.g. C:\Windows\System32\cmd.exe</value>
</data>
<data name="Menu.Integrations.Custom.AutoClose" xml:space="preserve"> <data name="Menu.Integrations.Custom.AutoClose" xml:space="preserve">
<value>Auto close when Roblox closes</value> <value>Auto close when Roblox closes</value>
</data> </data>
@ -746,7 +743,7 @@ Selecting 'No' will ignore this warning and continue installation.</value>
<value>Launch Arguments</value> <value>Launch Arguments</value>
</data> </data>
<data name="Menu.Integrations.Custom.LaunchArgs.Placeholder" xml:space="preserve"> <data name="Menu.Integrations.Custom.LaunchArgs.Placeholder" xml:space="preserve">
<value>e.g. /k echo Roblox is running!</value> <value>Roblox is running!</value>
</data> </data>
<data name="Menu.Integrations.Custom.NewIntegration" xml:space="preserve"> <data name="Menu.Integrations.Custom.NewIntegration" xml:space="preserve">
<value>New Integration</value> <value>New Integration</value>
@ -1150,4 +1147,13 @@ If not, then please report this exception to the maintainers of this fork. Do NO
Issues may occur and your settings may be altered. A reinstall is recommended. Issues may occur and your settings may be altered. A reinstall is recommended.
Are you sure you want to continue?</value> Are you sure you want to continue?</value>
</data> </data>
<data name="Dialog.PlayerError.FailedLaunch" xml:space="preserve">
<value>Roblox failed to launch.</value>
</data>
<data name="Dialog.PlayerError.Crash" xml:space="preserve">
<value>Roblox has crashed.</value>
</data>
<data name="Dialog.PlayerError.HelpInformation" xml:space="preserve">
<value>Please read the following help information, which will open in your web browser when you close this dialog.</value>
</data>
</root> </root>

View File

@ -104,7 +104,7 @@ namespace Bloxstrap.UI.Elements.ContextMenu
private void RichPresenceMenuItem_Click(object sender, RoutedEventArgs e) => _watcher.RichPresence?.SetVisibility(((MenuItem)sender).IsChecked); private void RichPresenceMenuItem_Click(object sender, RoutedEventArgs e) => _watcher.RichPresence?.SetVisibility(((MenuItem)sender).IsChecked);
private void InviteDeeplinkMenuItem_Click(object sender, RoutedEventArgs e) => Clipboard.SetDataObject($"roblox://experiences/start?placeId={_activityWatcher?.ActivityPlaceId}&gameInstanceId={_activityWatcher?.ActivityJobId}"); private void InviteDeeplinkMenuItem_Click(object sender, RoutedEventArgs e) => Clipboard.SetDataObject(_activityWatcher?.GetActivityDeeplink());
private void ServerDetailsMenuItem_Click(object sender, RoutedEventArgs e) => ShowServerInformationWindow(); private void ServerDetailsMenuItem_Click(object sender, RoutedEventArgs e) => ShowServerInformationWindow();

View File

@ -41,7 +41,7 @@ namespace Bloxstrap.UI.Elements.Dialogs
case MessageBoxImage.Warning: case MessageBoxImage.Warning:
iconFilename = "Warning"; iconFilename = "Warning";
sound = SystemSounds.Asterisk; sound = SystemSounds.Exclamation;
break; break;
case MessageBoxImage.Information: case MessageBoxImage.Information:

View File

@ -102,11 +102,11 @@
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<ui:TextBox Grid.Column="0" Margin="0,0,0,0" PlaceholderText="{x:Static resources:Strings.Menu_Integrations_Custom_AppLocation_Placeholder}" Text="{Binding SelectedCustomIntegration.Location}" /> <ui:TextBox Grid.Column="0" Margin="0,0,0,0" PlaceholderText="C:\Windows\System32\cmd.exe" Text="{Binding SelectedCustomIntegration.Location}" />
<ui:Button Grid.Column="1" Margin="8,0,0,0" Height="34" Icon="Folder24" Content="{x:Static resources:Strings.Common_Browse}" Command="{Binding BrowseIntegrationLocationCommand}" /> <ui:Button Grid.Column="1" Margin="8,0,0,0" Height="34" Icon="Folder24" Content="{x:Static resources:Strings.Common_Browse}" Command="{Binding BrowseIntegrationLocationCommand}" />
</Grid> </Grid>
<TextBlock Margin="0,8,0,0" Text="{x:Static resources:Strings.Menu_Integrations_Custom_LaunchArgs}" Foreground="{DynamicResource TextFillColorSecondaryBrush}" /> <TextBlock Margin="0,8,0,0" Text="{x:Static resources:Strings.Menu_Integrations_Custom_LaunchArgs}" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<ui:TextBox Margin="0,4,0,0" PlaceholderText="{x:Static resources:Strings.Menu_Integrations_Custom_LaunchArgs_Placeholder}" Text="{Binding SelectedCustomIntegration.LaunchArgs}" TextWrapping="Wrap" AcceptsReturn="True" AcceptsTab="True" /> <ui:TextBox Margin="0,4,0,0" PlaceholderText="{Binding Source='/k echo {0}', Converter={StaticResource StringFormatConverter}, ConverterParameter={x:Static resources:Strings.Menu_Integrations_Custom_LaunchArgs_Placeholder}}" Text="{Binding SelectedCustomIntegration.LaunchArgs}" TextWrapping="Wrap" AcceptsReturn="True" AcceptsTab="True" />
<CheckBox Margin="0,8,0,0" Content="{x:Static resources:Strings.Menu_Integrations_Custom_AutoClose}" IsChecked="{Binding SelectedCustomIntegration.AutoClose}" /> <CheckBox Margin="0,8,0,0" Content="{x:Static resources:Strings.Menu_Integrations_Custom_AutoClose}" IsChecked="{Binding SelectedCustomIntegration.AutoClose}" />
</StackPanel> </StackPanel>
<TextBlock Grid.Row="0" Grid.RowSpan="2" Grid.Column="1" Text="{x:Static resources:Strings.Menu_Integrations_Custom_NoneSelected}" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Center"> <TextBlock Grid.Row="0" Grid.RowSpan="2" Grid.Column="1" Text="{x:Static resources:Strings.Menu_Integrations_Custom_NoneSelected}" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Center">

View File

@ -33,6 +33,20 @@ namespace Bloxstrap.UI
} }
} }
public static void ShowPlayerErrorDialog(bool crash = false)
{
if (App.LaunchSettings.QuietFlag.Active)
return;
string topLine = Strings.Dialog_PlayerError_FailedLaunch;
if (crash)
topLine = Strings.Dialog_PlayerError_Crash;
ShowMessageBox($"{topLine}\n\n{Strings.Dialog_PlayerError_HelpInformation}", MessageBoxImage.Error);
Utilities.ShellExecute($"https://github.com/{App.ProjectRepository}/wiki/Roblox-crashes-or-does-not-launch");
}
public static void ShowExceptionDialog(Exception exception) public static void ShowExceptionDialog(Exception exception)
{ {
Application.Current.Dispatcher.Invoke(() => Application.Current.Dispatcher.Invoke(() =>

View File

@ -5,7 +5,6 @@ using Bloxstrap.UI.Elements.About;
namespace Bloxstrap.UI.ViewModels.Installer namespace Bloxstrap.UI.ViewModels.Installer
{ {
// TODO: have it so it shows "Launch Roblox"/"Install and Launch Roblox" depending on state of /App/ folder
public class LaunchMenuViewModel public class LaunchMenuViewModel
{ {
public string Version => string.Format(Strings.Menu_About_Version, App.Version); public string Version => string.Format(Strings.Menu_About_Version, App.Version);

View File

@ -19,33 +19,16 @@
// called by codebehind on page load // called by codebehind on page load
public async void DoChecks() public async void DoChecks()
{ {
const string LOG_IDENT = "WelcomeViewModel::DoChecks"; var releaseInfo = await App.GetLatestRelease();
// TODO: move into unified function that bootstrapper can use too if (releaseInfo is not null)
GithubRelease? releaseInfo = null;
try
{
releaseInfo = await Http.GetJson<GithubRelease>($"https://api.github.com/repos/{App.ProjectRepository}/releases/latest");
if (releaseInfo is null || releaseInfo.Assets is null)
{
App.Logger.WriteLine(LOG_IDENT, $"Encountered invalid data when fetching GitHub releases");
}
else
{ {
if (Utilities.CompareVersions(App.Version, releaseInfo.TagName) == VersionComparison.LessThan) if (Utilities.CompareVersions(App.Version, releaseInfo.TagName) == VersionComparison.LessThan)
{ {
VersionNotice = String.Format(Resources.Strings.Installer_Welcome_UpdateNotice, App.Version, releaseInfo.TagName.Replace("v", "")); VersionNotice = String.Format(Strings.Installer_Welcome_UpdateNotice, App.Version, releaseInfo.TagName.Replace("v", ""));
OnPropertyChanged(nameof(VersionNotice)); OnPropertyChanged(nameof(VersionNotice));
} }
} }
}
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, $"Error occurred when fetching GitHub releases");
App.Logger.WriteException(LOG_IDENT, ex);
}
CanContinue = true; CanContinue = true;
OnPropertyChanged(nameof(CanContinue)); OnPropertyChanged(nameof(CanContinue));

View File

@ -1,43 +0,0 @@
/*
* Roblox Studio Mod Manager (ProjectSrc/Utility/SystemEvent.cs)
* MIT License
* Copyright (c) 2015-present MaximumADHD
*/
namespace Bloxstrap.Utility
{
public class SystemEvent : EventWaitHandle
{
public string Name { get; private set; }
public SystemEvent(string name, bool init = false, EventResetMode mode = EventResetMode.AutoReset) : base(init, mode, name)
{
if (init)
Reset();
else
Set();
Name = name;
}
public override string ToString()
{
return Name;
}
public Task<bool> WaitForEvent()
{
return Task.Run(WaitOne);
}
public Task<bool> WaitForEvent(TimeSpan timeout, bool exitContext = false)
{
return Task.Run(() => WaitOne(timeout, exitContext));
}
public Task<bool> WaitForEvent(int millisecondsTimeout, bool exitContext = false)
{
return Task.Run(() => WaitOne(millisecondsTimeout, exitContext));
}
}
}

View File

@ -1,6 +1,4 @@
using Bloxstrap.Integrations; using Bloxstrap.Integrations;
using System.CodeDom;
using System.Security.Permissions;
namespace Bloxstrap namespace Bloxstrap
{ {
@ -54,7 +52,7 @@ namespace Bloxstrap
if (split.Length >= 2) if (split.Length >= 2)
{ {
foreach (string strPid in split[0].Split(';')) foreach (string strPid in split[1].Split(','))
{ {
if (int.TryParse(strPid, out int pid) && pid != 0) if (int.TryParse(strPid, out int pid) && pid != 0)
_autoclosePids.Add(pid); _autoclosePids.Add(pid);
@ -86,38 +84,33 @@ namespace Bloxstrap
_notifyIcon = new(this); _notifyIcon = new(this);
} }
public void KillRobloxProcess() => KillProcess(_gameClientPid); public void KillRobloxProcess() => CloseProcess(_gameClientPid, true);
public void KillProcess(int pid) public void CloseProcess(int pid, bool force = false)
{
const string LOG_IDENT = "Watcher::CloseProcess";
try
{ {
using var process = Process.GetProcessById(pid); using var process = Process.GetProcessById(pid);
App.Logger.WriteLine("Watcher::KillProcess", $"Killing process '{process.ProcessName}' (PID {process.Id})"); App.Logger.WriteLine(LOG_IDENT, $"Killing process '{process.ProcessName}' (pid={pid}, force={force})");
if (process.HasExited) if (process.HasExited)
{ {
App.Logger.WriteLine("Watcher::KillProcess", $"PID {process.Id} has already exited"); App.Logger.WriteLine(LOG_IDENT, $"PID {pid} has already exited");
return; return;
} }
if (force)
process.Kill(); process.Kill();
process.Close(); else
}
public void CloseProcess(int pid)
{
using var process = Process.GetProcessById(pid);
App.Logger.WriteLine("Watcher::CloseProcess", $"Closing process '{process.ProcessName}' (PID {process.Id})");
if (process.HasExited)
{
App.Logger.WriteLine("Watcher::CloseProcess", $"PID {process.Id} has already exited");
return;
}
process.CloseMainWindow(); process.CloseMainWindow();
process.Close(); }
catch (Exception ex)
{
App.Logger.WriteLine(LOG_IDENT, $"PID {pid} could not be closed");
App.Logger.WriteException(LOG_IDENT, ex);
}
} }
public async Task Run() public async Task Run()