Refactor arg parser + channel handling

this commit changes way too many things to be stable. be warned of bugs if using this
This commit is contained in:
pizzaboxer 2024-08-19 15:37:56 +01:00
parent 943acd78e8
commit 765cccf89e
No known key found for this signature in database
GPG Key ID: 59D4A1DBAD0F2BA8
14 changed files with 202 additions and 356 deletions

View File

@ -86,7 +86,7 @@ namespace Bloxstrap
_showingExceptionDialog = true; _showingExceptionDialog = true;
if (!LaunchSettings.IsQuiet) if (!LaunchSettings.QuietFlag.Active)
Frontend.ShowExceptionDialog(exception); Frontend.ShowExceptionDialog(exception);
Terminate(ErrorCode.ERROR_INSTALL_FAILURE); Terminate(ErrorCode.ERROR_INSTALL_FAILURE);
@ -190,7 +190,7 @@ namespace Bloxstrap
if (Paths.Process != Paths.Application && !File.Exists(Paths.Application)) if (Paths.Process != Paths.Application && !File.Exists(Paths.Application))
File.Copy(Paths.Process, Paths.Application); File.Copy(Paths.Process, Paths.Application);
Logger.Initialize(LaunchSettings.IsUninstall); Logger.Initialize(LaunchSettings.UninstallFlag.Active);
if (!Logger.Initialized && !Logger.NoWriteMode) if (!Logger.Initialized && !Logger.NoWriteMode)
{ {
@ -204,7 +204,7 @@ namespace Bloxstrap
// we can only parse them now as settings need // we can only parse them now as settings need
// to be loaded first to know what our channel is // to be loaded first to know what our channel is
LaunchSettings.ParseRoblox(); // LaunchSettings.ParseRoblox();
if (!Locale.SupportedLocales.ContainsKey(Settings.Prop.Locale)) if (!Locale.SupportedLocales.ContainsKey(Settings.Prop.Locale))
{ {
@ -214,7 +214,7 @@ namespace Bloxstrap
Locale.Set(Settings.Prop.Locale); Locale.Set(Settings.Prop.Locale);
if (!LaunchSettings.IsUninstall) if (!LaunchSettings.UninstallFlag.Active)
Installer.HandleUpgrade(); Installer.HandleUpgrade();
LaunchHandler.ProcessLaunchArgs(); LaunchHandler.ProcessLaunchArgs();

View File

@ -27,8 +27,8 @@ namespace Bloxstrap
private string _playerFileName => _launchMode == LaunchMode.Player ? "RobloxPlayerBeta.exe" : "RobloxStudioBeta.exe"; private string _playerFileName => _launchMode == LaunchMode.Player ? "RobloxPlayerBeta.exe" : "RobloxStudioBeta.exe";
private string _playerLocation => Path.Combine(_versionFolder, _playerFileName); private string _playerLocation => Path.Combine(_versionFolder, _playerFileName);
private string _launchCommandLine; private string _launchCommandLine = App.LaunchSettings.RobloxLaunchArgs;
private LaunchMode _launchMode; private LaunchMode _launchMode = App.LaunchSettings.RobloxLaunchMode;
private bool _installWebView2; private bool _installWebView2;
private string _versionGuid private string _versionGuid
@ -81,10 +81,8 @@ namespace Bloxstrap
#endregion #endregion
#region Core #region Core
public Bootstrapper(string launchCommandLine, LaunchMode launchMode, bool installWebView2) public Bootstrapper(bool installWebView2)
{ {
_launchCommandLine = launchCommandLine;
_launchMode = launchMode;
_installWebView2 = installWebView2; _installWebView2 = installWebView2;
_packageDirectories = _launchMode == LaunchMode.Player ? PackageMap.Player : PackageMap.Studio; _packageDirectories = _launchMode == LaunchMode.Player ? PackageMap.Player : PackageMap.Studio;
@ -217,7 +215,7 @@ namespace Bloxstrap
await mutex.ReleaseAsync(); await mutex.ReleaseAsync();
if (!App.LaunchSettings.IsNoLaunch && !_cancelFired) if (!App.LaunchSettings.NoLaunchFlag.Active && !_cancelFired)
await StartRoblox(); await StartRoblox();
} }
@ -225,32 +223,59 @@ namespace Bloxstrap
{ {
const string LOG_IDENT = "Bootstrapper::CheckLatestVersion"; const string LOG_IDENT = "Bootstrapper::CheckLatestVersion";
// before we do anything, we need to query our channel
// if it's set in the launch uri, we need to use it and set the registry key for it
// else, check if the registry key for it exists, and use it
string channel = "production";
string keyPath = _launchMode == LaunchMode.Player ? "RobloxPlayer" : "RobloxStudio";
using var key = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\ROBLOX Corporation\\Environments\\{keyPath}\\Channel");
var match = Regex.Match(App.LaunchSettings.RobloxLaunchArgs, "channel:([a-zA-Z0-9-_]+)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
if (match.Groups.Count == 2)
{
channel = match.Groups[1].Value.ToLowerInvariant();
}
else if (key.GetValue("www.roblox.com") is string value)
{
channel = value;
}
ClientVersion clientVersion; ClientVersion clientVersion;
string binaryType = _launchMode == LaunchMode.Player ? "WindowsPlayer" : "WindowsStudio64"; string binaryType = _launchMode == LaunchMode.Player ? "WindowsPlayer" : "WindowsStudio64";
try try
{ {
clientVersion = await RobloxDeployment.GetInfo(App.Settings.Prop.Channel, binaryType: binaryType); clientVersion = await RobloxDeployment.GetInfo(channel, binaryType);
} }
catch (HttpResponseException ex) catch (HttpResponseException ex)
{ {
if (ex.ResponseMessage.StatusCode is not HttpStatusCode.Unauthorized and not HttpStatusCode.Forbidden and not HttpStatusCode.NotFound) if (ex.ResponseMessage.StatusCode
is not HttpStatusCode.Unauthorized
and not HttpStatusCode.Forbidden
and not HttpStatusCode.NotFound)
throw; throw;
App.Logger.WriteLine(LOG_IDENT, $"Reverting enrolled channel to {RobloxDeployment.DefaultChannel} because HTTP {(int)ex.ResponseMessage.StatusCode}"); App.Logger.WriteLine(LOG_IDENT, $"Changing channel from {channel} to {RobloxDeployment.DefaultChannel} because HTTP {(int)ex.ResponseMessage.StatusCode}");
App.Settings.Prop.Channel = RobloxDeployment.DefaultChannel;
clientVersion = await RobloxDeployment.GetInfo(App.Settings.Prop.Channel, binaryType: binaryType); channel = RobloxDeployment.DefaultChannel;
clientVersion = await RobloxDeployment.GetInfo(channel, binaryType);
} }
if (clientVersion.IsBehindDefaultChannel) if (clientVersion.IsBehindDefaultChannel)
{ {
App.Logger.WriteLine(LOG_IDENT, $"Changed Roblox channel from {App.Settings.Prop.Channel} to {RobloxDeployment.DefaultChannel}"); App.Logger.WriteLine(LOG_IDENT, $"Changing channel from {channel} to {RobloxDeployment.DefaultChannel} because channel is behind production");
App.Settings.Prop.Channel = RobloxDeployment.DefaultChannel; channel = RobloxDeployment.DefaultChannel;
clientVersion = await RobloxDeployment.GetInfo(App.Settings.Prop.Channel, binaryType: binaryType); clientVersion = await RobloxDeployment.GetInfo(channel, binaryType);
} }
key.SetValue("www.roblox.com", channel);
_latestVersionGuid = clientVersion.VersionGuid; _latestVersionGuid = clientVersion.VersionGuid;
_versionFolder = Path.Combine(Paths.Versions, _latestVersionGuid); _versionFolder = Path.Combine(Paths.Versions, _latestVersionGuid);
_versionPackageManifest = await PackageManifest.Get(_latestVersionGuid); _versionPackageManifest = await PackageManifest.Get(_latestVersionGuid);
@ -262,28 +287,12 @@ namespace Bloxstrap
SetStatus(Strings.Bootstrapper_Status_Starting); SetStatus(Strings.Bootstrapper_Status_Starting);
if (_launchMode != LaunchMode.StudioAuth)
{
_launchCommandLine = _launchCommandLine.Replace("LAUNCHTIMEPLACEHOLDER", DateTimeOffset.Now.ToUnixTimeMilliseconds().ToString());
if (_launchCommandLine.StartsWith("roblox-player:1"))
_launchCommandLine += "+channel:";
else
_launchCommandLine += " -channel ";
if (App.Settings.Prop.Channel.ToLowerInvariant() == RobloxDeployment.DefaultChannel.ToLowerInvariant())
_launchCommandLine += "production";
else
_launchCommandLine += App.Settings.Prop.Channel.ToLowerInvariant();
if (App.Settings.Prop.ForceRobloxLanguage) if (App.Settings.Prop.ForceRobloxLanguage)
{ {
var match = Regex.Match(_launchCommandLine, "gameLocale:([a-z_]+)"); var match = Regex.Match(_launchCommandLine, "gameLocale:([a-z_]+)", RegexOptions.CultureInvariant);
if (match.Groups.Count == 2) if (match.Groups.Count == 2)
_launchCommandLine = _launchCommandLine.Replace("robloxLocale:en_us", $"robloxLocale:{match.Groups[1].Value}"); _launchCommandLine = _launchCommandLine.Replace("robloxLocale:en_us", $"robloxLocale:{match.Groups[1].Value}", StringComparison.InvariantCultureIgnoreCase);
}
} }
// whether we should wait for roblox to exit to handle stuff in the background or clean up after roblox closes // whether we should wait for roblox to exit to handle stuff in the background or clean up after roblox closes
@ -479,8 +488,8 @@ namespace Bloxstrap
// this doesn't go under register, so we check every launch // this doesn't go under register, so we check every launch
// just in case the stock bootstrapper changes it back // just in case the stock bootstrapper changes it back
ProtocolHandler.Register("roblox", "Roblox", Paths.Application); ProtocolHandler.Register("roblox", "Roblox", Paths.Application, "-player \"%1\"");
ProtocolHandler.Register("roblox-player", "Roblox", Paths.Application); ProtocolHandler.Register("roblox-player", "Roblox", Paths.Application, "-player \"%1\"");
#if STUDIO_FEATURES #if STUDIO_FEATURES
ProtocolHandler.Register("roblox-studio", "Roblox", Paths.Application); ProtocolHandler.Register("roblox-studio", "Roblox", Paths.Application);
ProtocolHandler.Register("roblox-studio-auth", "Roblox", Paths.Application); ProtocolHandler.Register("roblox-studio-auth", "Roblox", Paths.Application);

View File

@ -2,6 +2,7 @@
{ {
public enum LaunchMode public enum LaunchMode
{ {
None,
Player, Player,
Studio, Studio,
StudioAuth StudioAuth

View File

@ -61,8 +61,8 @@ namespace Bloxstrap
// 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,
// and then launches from the website expecting it to work // and then launches from the website expecting it to work
// studio can be implicitly registered when it's first launched manually // studio can be implicitly registered when it's first launched manually
ProtocolHandler.Register("roblox", "Roblox", Paths.Application); ProtocolHandler.Register("roblox", "Roblox", Paths.Application, "-player \"%1\"");
ProtocolHandler.Register("roblox-player", "Roblox", Paths.Application); ProtocolHandler.Register("roblox-player", "Roblox", Paths.Application, "-player \"%1\"");
// TODO: implicit installation needs to reregister studio // TODO: implicit installation needs to reregister studio
@ -167,17 +167,15 @@ namespace Bloxstrap
// prompt to shutdown roblox if its currently running // prompt to shutdown roblox if its currently running
if (processes.Any()) if (processes.Any())
{ {
if (!App.LaunchSettings.IsQuiet) var result = Frontend.ShowMessageBox(
{
MessageBoxResult result = Frontend.ShowMessageBox(
Strings.Bootstrapper_Uninstall_RobloxRunning, Strings.Bootstrapper_Uninstall_RobloxRunning,
MessageBoxImage.Information, MessageBoxImage.Information,
MessageBoxButton.OKCancel MessageBoxButton.OKCancel,
MessageBoxResult.OK
); );
if (result != MessageBoxResult.OK) if (result != MessageBoxResult.OK)
App.Terminate(ErrorCode.ERROR_CANCELLED); App.Terminate(ErrorCode.ERROR_CANCELLED);
}
try try
{ {
@ -343,7 +341,7 @@ namespace Bloxstrap
return; return;
// 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.IsUpgrade && !isAutoUpgrade) if (!App.LaunchSettings.UpgradeFlag.Active && !isAutoUpgrade)
{ {
var result = Frontend.ShowMessageBox( var result = Frontend.ShowMessageBox(
Strings.InstallChecker_VersionDifferentThanInstalled, Strings.InstallChecker_VersionDifferentThanInstalled,
@ -459,6 +457,9 @@ namespace Bloxstrap
} }
Registry.CurrentUser.DeleteSubKeyTree("Software\\Bloxstrap", false); Registry.CurrentUser.DeleteSubKeyTree("Software\\Bloxstrap", false);
ProtocolHandler.Register("roblox", "Roblox", Paths.Application, "-player \"%1\"");
ProtocolHandler.Register("roblox-player", "Roblox", Paths.Application, "-player \"%1\"");
} }
App.Settings.Save(); App.Settings.Save();
@ -469,7 +470,7 @@ namespace Bloxstrap
{ {
Utilities.ShellExecute($"https://github.com/{App.ProjectRepository}/wiki/Release-notes-for-Bloxstrap-v{currentVer}"); Utilities.ShellExecute($"https://github.com/{App.ProjectRepository}/wiki/Release-notes-for-Bloxstrap-v{currentVer}");
} }
else if (!App.LaunchSettings.IsQuiet) else
{ {
Frontend.ShowMessageBox( Frontend.ShowMessageBox(
string.Format(Strings.InstallChecker_Updated, currentVer), string.Format(Strings.InstallChecker_Updated, currentVer),

View File

@ -32,13 +32,15 @@ namespace Bloxstrap
{ {
// this order is specific // this order is specific
if (App.LaunchSettings.IsUninstall) if (App.LaunchSettings.UninstallFlag.Active)
LaunchUninstaller(); LaunchUninstaller();
else if (App.LaunchSettings.IsMenuLaunch) else if (App.LaunchSettings.MenuFlag.Active)
LaunchSettings(); LaunchSettings();
else if (App.LaunchSettings.IsRobloxLaunch) else if (App.LaunchSettings.WatcherFlag.Active)
LaunchWatcher();
else if (App.LaunchSettings.RobloxLaunchMode != LaunchMode.None)
LaunchRoblox(); LaunchRoblox();
else if (!App.LaunchSettings.IsQuiet) else if (!App.LaunchSettings.QuietFlag.Active)
LaunchMenu(); LaunchMenu();
} }
@ -52,14 +54,14 @@ namespace Bloxstrap
return; return;
} }
if (App.LaunchSettings.IsUninstall) if (App.LaunchSettings.UninstallFlag.Active)
{ {
Frontend.ShowMessageBox(Strings.Bootstrapper_FirstRunUninstall, MessageBoxImage.Error); Frontend.ShowMessageBox(Strings.Bootstrapper_FirstRunUninstall, MessageBoxImage.Error);
App.Terminate(ErrorCode.ERROR_INVALID_FUNCTION); App.Terminate(ErrorCode.ERROR_INVALID_FUNCTION);
return; return;
} }
if (App.LaunchSettings.IsQuiet) if (App.LaunchSettings.QuietFlag.Active)
{ {
var installer = new Installer(); var installer = new Installer();
@ -99,7 +101,7 @@ namespace Bloxstrap
bool confirmed = false; bool confirmed = false;
bool keepData = true; bool keepData = true;
if (App.LaunchSettings.IsQuiet) if (App.LaunchSettings.QuietFlag.Active)
{ {
confirmed = true; confirmed = true;
} }
@ -154,18 +156,18 @@ namespace Bloxstrap
{ {
const string LOG_IDENT = "LaunchHandler::LaunchRoblox"; const string LOG_IDENT = "LaunchHandler::LaunchRoblox";
bool installWebView2 = false;
if (!File.Exists(Path.Combine(Paths.System, "mfplat.dll"))) if (!File.Exists(Path.Combine(Paths.System, "mfplat.dll")))
{ {
Frontend.ShowMessageBox(Strings.Bootstrapper_WMFNotFound, MessageBoxImage.Error); Frontend.ShowMessageBox(Strings.Bootstrapper_WMFNotFound, MessageBoxImage.Error);
if (!App.LaunchSettings.IsQuiet) if (!App.LaunchSettings.QuietFlag.Active)
Utilities.ShellExecute("https://support.microsoft.com/en-us/topic/media-feature-pack-list-for-windows-n-editions-c1c6fffa-d052-8338-7a79-a4bb980a700a"); Utilities.ShellExecute("https://support.microsoft.com/en-us/topic/media-feature-pack-list-for-windows-n-editions-c1c6fffa-d052-8338-7a79-a4bb980a700a");
App.Terminate(ErrorCode.ERROR_FILE_NOT_FOUND); App.Terminate(ErrorCode.ERROR_FILE_NOT_FOUND);
} }
bool installWebView2 = false;
{ {
using var hklmKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"); using var hklmKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}");
using var hkcuKey = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"); using var hkcuKey = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}");
@ -193,10 +195,10 @@ namespace Bloxstrap
// start bootstrapper and show the bootstrapper modal if we're not running silently // start bootstrapper and show the bootstrapper modal if we're not running silently
App.Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper"); App.Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper");
var bootstrapper = new Bootstrapper(App.LaunchSettings.RobloxLaunchArgs, App.LaunchSettings.RobloxLaunchMode, installWebView2); var bootstrapper = new Bootstrapper(installWebView2);
IBootstrapperDialog? dialog = null; IBootstrapperDialog? dialog = null;
if (!App.LaunchSettings.IsQuiet) if (!App.LaunchSettings.QuietFlag.Active)
{ {
App.Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper dialog"); App.Logger.WriteLine(LOG_IDENT, "Initializing bootstrapper dialog");
dialog = App.Settings.Prop.BootstrapperStyle.GetNew(); dialog = App.Settings.Prop.BootstrapperStyle.GetNew();
@ -232,12 +234,17 @@ namespace Bloxstrap
// this ordering is very important as all wpf windows are shown as modal dialogs, mess it up and you'll end up blocking input to one of them // this ordering is very important as all wpf windows are shown as modal dialogs, mess it up and you'll end up blocking input to one of them
dialog?.ShowBootstrapper(); dialog?.ShowBootstrapper();
if (!App.LaunchSettings.IsNoLaunch && App.Settings.Prop.EnableActivityTracking) if (!App.LaunchSettings.NoLaunchFlag.Active && App.Settings.Prop.EnableActivityTracking)
App.NotifyIcon?.InitializeContextMenu(); App.NotifyIcon?.InitializeContextMenu();
App.Logger.WriteLine(LOG_IDENT, "Waiting for bootstrapper task to finish"); App.Logger.WriteLine(LOG_IDENT, "Waiting for bootstrapper task to finish");
bootstrapperTask.Wait(); bootstrapperTask.Wait();
} }
public static void LaunchWatcher()
{
}
} }
} }

View File

@ -12,182 +12,97 @@ namespace Bloxstrap
{ {
public class LaunchSettings public class LaunchSettings
{ {
[LaunchFlag(new[] { "-preferences", "-menu", "-settings" })] public LaunchFlag MenuFlag { get; } = new("preferences,menu,settings");
public bool IsMenuLaunch { get; set; } = false;
[LaunchFlag(new[] { "-player", "-studio" })] public LaunchFlag WatcherFlag { get; } = new("watcher");
public bool IsRobloxLaunch { get; set; } = false;
[LaunchFlag("-quiet")] public LaunchFlag QuietFlag { get; } = new("quiet");
public bool IsQuiet { get; private set; } = false;
[LaunchFlag("-uninstall")] public LaunchFlag UninstallFlag { get; } = new("uninstall");
public bool IsUninstall { get; private set; } = false;
[LaunchFlag("-nolaunch")] public LaunchFlag NoLaunchFlag { get; } = new("nolaunch");
public bool IsNoLaunch { get; private set; } = false;
[LaunchFlag("-upgrade")] public LaunchFlag UpgradeFlag { get; } = new("upgrade");
public bool IsUpgrade { get; private set; } = false;
public LaunchMode RobloxLaunchMode { get; private set; } = LaunchMode.Player; public LaunchFlag PlayerFlag { get; } = new("player");
public string RobloxLaunchArgs { get; private set; } = "--app"; public LaunchFlag StudioFlag { get; } = new("studio");
public LaunchMode RobloxLaunchMode { get; private set; } = LaunchMode.None;
public string RobloxLaunchArgs { get; private set; } = "";
/// <summary> /// <summary>
/// Original launch arguments /// Original launch arguments
/// </summary> /// </summary>
public string[] Args { get; private set; } public string[] Args { get; private set; }
private Dictionary<string, PropertyInfo>? _flagMap; private Dictionary<string, LaunchFlag> _flagMap = new();
private string? _robloxArg;
// pizzaboxer wanted this
private void ParseLaunchFlagProps()
{
_flagMap = new Dictionary<string, PropertyInfo>();
foreach (var prop in typeof(LaunchSettings).GetProperties())
{
var attr = prop.GetCustomAttribute<LaunchFlagAttribute>();
if (attr == null)
continue;
if (!string.IsNullOrEmpty(attr.Name))
{
_flagMap[attr.Name] = prop;
}
else
{
foreach (var name in attr.Names!)
_flagMap[name] = prop;
}
}
}
private void ParseFlag(string arg)
{
const string LOG_IDENT = "LaunchSettings::ParseFlag";
arg = arg.ToLowerInvariant();
if (_flagMap!.ContainsKey(arg))
{
var prop = _flagMap[arg];
prop.SetValue(this, true);
App.Logger.WriteLine(LOG_IDENT, $"Started with {prop.Name} flag");
}
}
// private void ParseRoblox(string arg, ref int i)
public void ParseRoblox()
{
string? arg = _robloxArg;
if (arg is null)
return;
if (arg.StartsWith("roblox-player:"))
{
IsRobloxLaunch = true;
RobloxLaunchArgs = ProtocolHandler.ParseUri(arg);
RobloxLaunchMode = LaunchMode.Player;
}
else if (arg.StartsWith("roblox:"))
{
IsRobloxLaunch = true;
RobloxLaunchArgs = $"--app --deeplink {arg}";
RobloxLaunchMode = LaunchMode.Player;
}
#if STUDIO_FEATURES
else if (arg.StartsWith("roblox-studio:"))
{
RobloxLaunchArgs = ProtocolHandler.ParseUri(arg);
if (!RobloxLaunchArgs.Contains("-startEvent"))
RobloxLaunchArgs += " -startEvent www.roblox.com/robloxQTStudioStartedEvent";
RobloxLaunchMode = LaunchMode.Studio;
}
else if (arg.StartsWith("roblox-studio-auth:"))
{
RobloxLaunchArgs = HttpUtility.UrlDecode(arg);
RobloxLaunchMode = LaunchMode.StudioAuth;
}
else if (arg == "-ide")
{
RobloxLaunchMode = LaunchMode.Studio;
if (Args.Length >= 2)
{
string pathArg = Args[i + 1];
if (pathArg.StartsWith('-'))
return; // likely a launch flag, ignore it.
i++; // path arg
RobloxLaunchArgs = $"-task EditFile -localPlaceFile \"{pathArg}\"";
}
}
#endif
}
private void Parse()
{
const string LOG_IDENT = "LaunchSettings::Parse";
App.Logger.WriteLine(LOG_IDENT, "Parsing launch arguments");
#if DEBUG
App.Logger.WriteLine(LOG_IDENT, $"Launch arguments: {string.Join(' ', Args)}");
#endif
if (Args.Length == 0)
{
App.Logger.WriteLine(LOG_IDENT, "No launch arguments to parse");
return;
}
int idx = 0;
string firstArg = Args[0];
// check & handle roblox arg
if (!firstArg.StartsWith('-') || firstArg == "-ide")
{
// ParseRoblox(firstArg, ref idx);
_robloxArg = firstArg;
idx++; // roblox arg
}
// check if there are any launch flags
if (idx > Args.Length - 1)
return;
App.Logger.WriteLine(LOG_IDENT, "Parsing launch flags");
// map out launch flags
ParseLaunchFlagProps();
// parse any launch flags
for (int i = idx; i < Args.Length; i++)
ParseFlag(Args[i]);
// cleanup flag map
_flagMap!.Clear();
_flagMap = null;
}
public LaunchSettings(string[] args) public LaunchSettings(string[] args)
{ {
const string LOG_IDENT = "LaunchSettings";
Args = args; Args = args;
Parse();
// build flag map
foreach (var prop in this.GetType().GetProperties())
{
if (prop.PropertyType != typeof(LaunchFlag))
continue;
if (prop.GetValue(this) is not LaunchFlag flag)
continue;
foreach (string identifier in flag.Identifiers.Split(','))
_flagMap.Add(identifier, flag);
}
// parse
for (int i = 0; i < Args.Length; i++)
{
string arg = Args[i];
if (!arg.StartsWith('-'))
continue;
string identifier = arg[1..];
if (_flagMap[identifier] is not LaunchFlag flag)
continue;
flag.Active = true;
if (i < Args.Length - 1 && Args[i+1] is string nextArg && !nextArg.StartsWith('-'))
{
flag.Data = nextArg;
App.Logger.WriteLine(LOG_IDENT, $"Identifier '{identifier}' is active with data");
}
else
{
App.Logger.WriteLine(LOG_IDENT, $"Identifier '{identifier}' is active");
}
}
if (PlayerFlag.Active)
ParsePlayer(PlayerFlag.Data);
else if (StudioFlag.Active)
ParseStudio(StudioFlag.Data);
}
private void ParsePlayer(string? data)
{
RobloxLaunchMode = LaunchMode.Player;
if (!String.IsNullOrEmpty(data))
RobloxLaunchArgs = data;
}
private void ParseStudio(string? data)
{
RobloxLaunchMode = LaunchMode.Studio;
// TODO: do this later
} }
} }
} }

View File

@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bloxstrap.Models.Attributes
{
public class LaunchFlagAttribute : Attribute
{
public string? Name { get; private set; }
public string[]? Names { get; private set; }
public LaunchFlagAttribute(string name)
{
Name = name;
}
public LaunchFlagAttribute(string[] names)
{
Names = names;
}
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bloxstrap.Models
{
public class LaunchFlag
{
public string Identifiers { get; private set; }
public bool Active = false;
public string? Data;
public LaunchFlag(string identifiers)
{
Identifiers = identifiers;
}
}
}

View File

@ -16,9 +16,6 @@ namespace Bloxstrap.Models
public bool ForceRobloxLanguage { get; set; } = false; public bool ForceRobloxLanguage { get; set; } = false;
public bool UseFastFlagManager { get; set; } = true; public bool UseFastFlagManager { get; set; } = true;
// channel configuration
public string Channel { get; set; } = RobloxDeployment.DefaultChannel;
// integration configuration // integration configuration
public bool EnableActivityTracking { get; set; } = true; public bool EnableActivityTracking { get; set; } = true;
public bool UseDiscordRichPresence { get; set; } = true; public bool UseDiscordRichPresence { get; set; } = true;

View File

@ -21,11 +21,11 @@
}, },
"Bloxstrap (Deeplink)": { "Bloxstrap (Deeplink)": {
"commandName": "Project", "commandName": "Project",
"commandLineArgs": "roblox://experiences/start?placeId=13700835620" "commandLineArgs": "-player \"roblox://experiences/start?placeId=13700835620\""
}, },
"Bloxstrap (Studio Launch)": { "Bloxstrap (Studio Launch)": {
"commandName": "Project", "commandName": "Project",
"commandLineArgs": "-ide" "commandLineArgs": "-studio"
} }
} }
} }

View File

@ -9,69 +9,9 @@ namespace Bloxstrap
{ {
private const string RobloxPlaceKey = "Roblox.Place"; private const string RobloxPlaceKey = "Roblox.Place";
public static string ParseUri(string protocol) public static void Register(string key, string name, string handler, string handlerParam = "%1")
{ {
var args = new Dictionary<string, string>(); string handlerArgs = $"\"{handler}\" {handlerParam}";
bool channelArgPresent = false;
foreach (var parameter in protocol.Split('+'))
{
if (!parameter.Contains(':'))
continue;
var kv = parameter.Split(':');
string key = kv[0];
string val = kv[1];
// we'll set this before launching because for some reason roblox just refuses to launch if its like a few minutes old so ???
if (key == "launchtime")
val = "LAUNCHTIMEPLACEHOLDER";
if (key == "channel" && !String.IsNullOrEmpty(val))
{
channelArgPresent = true;
EnrollChannel(val);
// we'll set the arg when launching
continue;
}
args.Add(key, val);
}
if (!channelArgPresent)
EnrollChannel(RobloxDeployment.DefaultChannel);
var pairs = args.Select(x => x.Key + ":" + x.Value).ToArray();
return String.Join("+", pairs);
}
public static void ChangeChannel(string channel)
{
if (channel.ToLowerInvariant() == App.Settings.Prop.Channel.ToLowerInvariant())
return;
// don't change if roblox is already running
if (Process.GetProcessesByName("RobloxPlayerBeta").Any())
{
App.Logger.WriteLine("ProtocolHandler::ChangeChannel", $"Ignored channel change from {App.Settings.Prop.Channel} to {channel} because Roblox is already running");
}
else
{
App.Logger.WriteLine("ProtocolHandler::ChangeChannel", $"Changed Roblox channel from {App.Settings.Prop.Channel} to {channel}");
App.Settings.Prop.Channel = channel;
}
}
public static void EnrollChannel(string channel)
{
ChangeChannel(channel);
App.State.Save();
}
public static void Register(string key, string name, string handler)
{
string handlerArgs = $"\"{handler}\" %1";
using var uriKey = Registry.CurrentUser.CreateSubKey($@"Software\Classes\{key}"); using var uriKey = Registry.CurrentUser.CreateSubKey($@"Software\Classes\{key}");
using var uriIconKey = uriKey.CreateSubKey("DefaultIcon"); using var uriIconKey = uriKey.CreateSubKey("DefaultIcon");

View File

@ -1,9 +1,8 @@
namespace Bloxstrap namespace Bloxstrap
{ {
// TODO: this is a mess and desperately needs refactoring
public static class RobloxDeployment public static class RobloxDeployment
{ {
public const string DefaultChannel = "LIVE"; public const string DefaultChannel = "production";
public static string BaseUrl { get; private set; } = null!; public static string BaseUrl { get; private set; } = null!;
@ -88,14 +87,11 @@
return null; return null;
} }
public static string GetLocation(string resource, string? channel = null) public static string GetLocation(string resource, string channel = DefaultChannel)
{ {
if (string.IsNullOrEmpty(channel))
channel = App.Settings.Prop.Channel;
string location = BaseUrl; string location = BaseUrl;
if (channel.ToLowerInvariant() != DefaultChannel.ToLowerInvariant()) if (String.Compare(channel, DefaultChannel, StringComparison.InvariantCultureIgnoreCase) != 0)
{ {
string channelName; string channelName;
@ -112,11 +108,11 @@
return location; return location;
} }
public static async Task<ClientVersion> GetInfo(string channel, bool extraInformation = false, string binaryType = "WindowsPlayer") public static async Task<ClientVersion> GetInfo(string channel, string binaryType = "WindowsPlayer")
{ {
const string LOG_IDENT = "RobloxDeployment::GetInfo"; const string LOG_IDENT = "RobloxDeployment::GetInfo";
App.Logger.WriteLine(LOG_IDENT, $"Getting deploy info for channel {channel} (extraInformation={extraInformation})"); App.Logger.WriteLine(LOG_IDENT, $"Getting deploy info for channel {channel}");
string cacheKey = $"{channel}-{binaryType}"; string cacheKey = $"{channel}-{binaryType}";
ClientVersion clientVersion; ClientVersion clientVersion;
@ -128,7 +124,11 @@
} }
else else
{ {
string path = $"/v2/client-version/{binaryType}/channel/{channel}"; string path = $"/v2/client-version/{binaryType}";
if (String.Compare(channel, DefaultChannel, StringComparison.InvariantCultureIgnoreCase) != 0)
path = $"/v2/client-version/{binaryType}/channel/{channel}";
HttpResponseMessage deployInfoResponse; HttpResponseMessage deployInfoResponse;
try try
@ -173,24 +173,6 @@
clientVersion.IsBehindDefaultChannel = true; clientVersion.IsBehindDefaultChannel = true;
} }
// for preferences
if (extraInformation && clientVersion.Timestamp is null)
{
App.Logger.WriteLine(LOG_IDENT, "Getting extra information...");
string manifestUrl = GetLocation($"/{clientVersion.VersionGuid}-rbxPkgManifest.txt", channel);
// get an approximate deploy time from rbxpkgmanifest's last modified date
HttpResponseMessage pkgResponse = await App.HttpClient.GetAsync(manifestUrl);
if (pkgResponse.Content.Headers.TryGetValues("last-modified", out var values))
{
string lastModified = values.First();
App.Logger.WriteLine(LOG_IDENT, $"{manifestUrl} - Last-Modified: {lastModified}");
clientVersion.Timestamp = DateTime.Parse(lastModified).ToLocalTime();
}
}
ClientVersionCache[cacheKey] = clientVersion; ClientVersionCache[cacheKey] = clientVersion;
return clientVersion; return clientVersion;

View File

@ -114,27 +114,23 @@ namespace Bloxstrap
public static RobloxFastFlags PCDesktopClient { get; } = GetSettings("PCDesktopClient"); public static RobloxFastFlags PCDesktopClient { get; } = GetSettings("PCDesktopClient");
public static RobloxFastFlags PCClientBootstrapper { get; } = GetSettings("PCClientBootstrapper"); public static RobloxFastFlags PCClientBootstrapper { get; } = GetSettings("PCClientBootstrapper");
public static RobloxFastFlags GetSettings(string applicationName, string? channelName = null, bool shouldCache = true) public static RobloxFastFlags GetSettings(string applicationName, string channelName = RobloxDeployment.DefaultChannel, bool shouldCache = true)
{ {
string channelNameLower; channelName = channelName.ToLowerInvariant();
if (!string.IsNullOrEmpty(channelName))
channelNameLower = channelName.ToLowerInvariant();
else
channelNameLower = App.Settings.Prop.Channel.ToLowerInvariant();
lock (_cache) lock (_cache)
{ {
if (_cache.ContainsKey(applicationName) && _cache[applicationName].ContainsKey(channelNameLower)) if (_cache.ContainsKey(applicationName) && _cache[applicationName].ContainsKey(channelName))
return _cache[applicationName][channelNameLower]; return _cache[applicationName][channelName];
var flags = new RobloxFastFlags(applicationName, channelNameLower); var flags = new RobloxFastFlags(applicationName, channelName);
if (shouldCache) if (shouldCache)
{ {
if (!_cache.ContainsKey(applicationName)) if (!_cache.ContainsKey(applicationName))
_cache[applicationName] = new(); _cache[applicationName] = new();
_cache[applicationName][channelNameLower] = flags; _cache[applicationName][channelName] = flags;
} }
return flags; return flags;

View File

@ -14,10 +14,10 @@ namespace Bloxstrap.UI
{ {
App.Logger.WriteLine("Frontend::ShowMessageBox", message); App.Logger.WriteLine("Frontend::ShowMessageBox", message);
if (App.LaunchSettings.IsQuiet) if (App.LaunchSettings.QuietFlag.Active)
return defaultResult; return defaultResult;
if (!App.LaunchSettings.IsRobloxLaunch) if (App.LaunchSettings.RobloxLaunchMode != LaunchMode.None)
return ShowFluentMessageBox(message, icon, buttons); return ShowFluentMessageBox(message, icon, buttons);
switch (App.Settings.Prop.BootstrapperStyle) switch (App.Settings.Prop.BootstrapperStyle)