mirror of
https://github.com/bloxstraplabs/bloxstrap.git
synced 2025-04-21 18:11:27 -07:00
Add full Discord RP support for desktop app
Unlike how it worked before, this should now follow the player's game activity even with in-app game switching. (#24)
This commit is contained in:
parent
01a9cff6e1
commit
1c14a0a3fa
@ -212,21 +212,25 @@ namespace Bloxstrap
|
||||
// i might be able to experiment with reading from the latest log file in realtime to circumvent this,
|
||||
// but i have no idea how reliable it will be. todo?
|
||||
if (Program.Settings.UseDiscordRichPresence)
|
||||
{
|
||||
// probably not the most ideal way to do this
|
||||
string? placeId = Utilities.GetKeyValue(LaunchCommandLine, "placeId=", '&');
|
||||
|
||||
if (placeId is not null)
|
||||
{
|
||||
richPresence = new DiscordRichPresence();
|
||||
bool presenceSet = await richPresence.SetPresence(placeId);
|
||||
richPresence.MonitorGameActivity();
|
||||
|
||||
if (presenceSet)
|
||||
shouldWait = true;
|
||||
else
|
||||
richPresence.Dispose();
|
||||
}
|
||||
|
||||
// probably not the most ideal way to do this
|
||||
//string? placeId = Utilities.GetKeyValue(LaunchCommandLine, "placeId=", '&');
|
||||
|
||||
//if (placeId is not null)
|
||||
//{
|
||||
// richPresence = new DiscordRichPresence();
|
||||
// bool presenceSet = await richPresence.SetPresence(placeId);
|
||||
|
||||
// if (presenceSet)
|
||||
// shouldWait = true;
|
||||
// else
|
||||
// richPresence.Dispose();
|
||||
//}
|
||||
}
|
||||
|
||||
if (!shouldWait)
|
||||
|
@ -20,7 +20,8 @@ namespace Bloxstrap.Helpers
|
||||
"LIVE",
|
||||
"ZAvatarTeam",
|
||||
"ZCanary",
|
||||
"ZFeatureHarmony",
|
||||
//"ZFeatureHarmony", last updated 9/20, shouldn't be here anymore
|
||||
"ZFlag",
|
||||
"ZIntegration",
|
||||
"ZLive",
|
||||
"ZNext",
|
||||
@ -61,6 +62,7 @@ namespace Bloxstrap.Helpers
|
||||
"ZFeatureSubsystemHttpClient",
|
||||
"ZFeatureTelemLife",
|
||||
"ZFeatureUse-New-RapidJson-In-Flag-Loading",
|
||||
"ZFlag",
|
||||
"ZIntegration",
|
||||
"ZIntegration1",
|
||||
"ZLang",
|
||||
|
@ -1,22 +1,172 @@
|
||||
using Bloxstrap.Models;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Bloxstrap.Models;
|
||||
|
||||
using DiscordRPC;
|
||||
|
||||
namespace Bloxstrap.Helpers.Integrations
|
||||
{
|
||||
internal class DiscordRichPresence : IDisposable
|
||||
class DiscordRichPresence : IDisposable
|
||||
{
|
||||
readonly DiscordRpcClient RichPresence = new("1005469189907173486");
|
||||
|
||||
public async Task<bool> SetPresence(string placeId)
|
||||
const string GameJoiningEntry = "[FLog::Output] ! Joining game";
|
||||
const string GameJoinedEntry = "[FLog::Network] serverId:";
|
||||
const string GameDisconnectedEntry = "[FLog::Network] Client:Disconnect";
|
||||
|
||||
const string GameJoiningEntryPattern = @"! Joining game '([0-9a-f\-]{36})' place ([0-9]+) at ([0-9\.]+)";
|
||||
const string GameJoinedEntryPattern = @"serverId: ([0-9\.]+)\|([0-9]+)";
|
||||
|
||||
// these are values to use assuming the player isn't currently in a game
|
||||
bool ActivityInGame = false;
|
||||
long ActivityPlaceId = 0;
|
||||
string ActivityJobId = "";
|
||||
string ActivityMachineAddress = ""; // we're only really using this to confirm a place join
|
||||
|
||||
public DiscordRichPresence()
|
||||
{
|
||||
RichPresence.Initialize();
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetLog()
|
||||
{
|
||||
Debug.WriteLine("[DiscordRichPresence] Reading log file...");
|
||||
|
||||
string logDirectory = Path.Combine(Program.LocalAppData, "Roblox\\logs");
|
||||
|
||||
if (!Directory.Exists(logDirectory))
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
FileInfo logFileInfo = new DirectoryInfo(logDirectory).GetFiles().OrderByDescending(f => f.LastWriteTime).First();
|
||||
List<string> log = new();
|
||||
|
||||
// we just want to read the last 3500 lines of the log file
|
||||
// this should typically more than cover the last 30 seconds of logs
|
||||
// it has to be last 3500 lines (~360KB) because voice chat outputs a loooot of logs :')
|
||||
|
||||
ReverseLineReader rlr = new(() => logFileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
|
||||
log = rlr.Take(3500).ToList();
|
||||
|
||||
Debug.WriteLine("[DiscordRichPresence] Finished reading log file");
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
private async Task ExamineLog(List<string> log)
|
||||
{
|
||||
Debug.WriteLine("[DiscordRichPresence] Examining log file...");
|
||||
|
||||
foreach (string entry in log)
|
||||
{
|
||||
if (entry.Contains(GameJoiningEntry) && !ActivityInGame && ActivityPlaceId == 0)
|
||||
{
|
||||
Match match = Regex.Match(entry, GameJoiningEntryPattern);
|
||||
|
||||
if (match.Groups.Count != 4)
|
||||
continue;
|
||||
|
||||
ActivityInGame = false;
|
||||
ActivityPlaceId = Int64.Parse(match.Groups[2].Value);
|
||||
ActivityJobId = match.Groups[1].Value;
|
||||
ActivityMachineAddress = match.Groups[3].Value;
|
||||
|
||||
Debug.WriteLine($"[DiscordRichPresence] Joining Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
|
||||
|
||||
// examine log again to check for immediate changes
|
||||
await Task.Delay(1000);
|
||||
MonitorGameActivity(false);
|
||||
break;
|
||||
}
|
||||
else if (entry.Contains(GameJoinedEntry) && !ActivityInGame && ActivityPlaceId != 0)
|
||||
{
|
||||
Match match = Regex.Match(entry, GameJoinedEntryPattern);
|
||||
|
||||
if (match.Groups.Count != 3 || match.Groups[1].Value != ActivityMachineAddress)
|
||||
continue;
|
||||
|
||||
Debug.WriteLine($"[DiscordRichPresence] Joined Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
|
||||
|
||||
ActivityInGame = true;
|
||||
await SetPresence();
|
||||
|
||||
// examine log again to check for immediate changes
|
||||
await Task.Delay(1000);
|
||||
MonitorGameActivity(false);
|
||||
break;
|
||||
}
|
||||
//else if (entry.Contains(GameDisconnectedEntry) && ActivityInGame && ActivityPlaceId != 0)
|
||||
else if (entry.Contains(GameDisconnectedEntry))
|
||||
{
|
||||
// for this one, we want to break as soon as we see this entry
|
||||
// or else it'll match a game join entry and think we're joining again
|
||||
if (ActivityInGame && ActivityPlaceId != 0)
|
||||
{
|
||||
Debug.WriteLine($"[DiscordRichPresence] Disconnected from Game ({ActivityPlaceId}/{ActivityJobId}/{ActivityMachineAddress})");
|
||||
|
||||
ActivityInGame = false;
|
||||
ActivityPlaceId = 0;
|
||||
ActivityJobId = "";
|
||||
ActivityMachineAddress = "";
|
||||
await SetPresence();
|
||||
|
||||
// examine log again to check for immediate changes
|
||||
await Task.Delay(1000);
|
||||
MonitorGameActivity(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.WriteLine("[DiscordRichPresence] Finished examining log file");
|
||||
}
|
||||
|
||||
public async void MonitorGameActivity(bool loop = true)
|
||||
{
|
||||
// okay, here's the process:
|
||||
//
|
||||
// - read the latest log file from %localappdata%\roblox\logs approx every 30 sec or so
|
||||
// - check for specific lines to determine player's game activity as shown below:
|
||||
//
|
||||
// - get the place id, job id and machine address from '! Joining game '{{JOBID}}' place {{PLACEID}} at {{MACHINEADDRESS}}' entry
|
||||
// - confirm place join with 'serverId: {{MACHINEADDRESS}}|{{MACHINEPORT}}' entry
|
||||
// - check for leaves/disconnects with 'Client:Disconnect' entry
|
||||
//
|
||||
// we'll read the log file from bottom-to-top and find which line meets the criteria
|
||||
// the processes for reading and examining the log files are separated since the log may have to be examined multiple times
|
||||
|
||||
// read log file
|
||||
List<string> log = GetLog().ToList();
|
||||
|
||||
// and now let's get the current status from the log
|
||||
await ExamineLog(log);
|
||||
|
||||
if (!loop)
|
||||
return;
|
||||
|
||||
await Task.Delay(ActivityInGame ? 30000 : 10000);
|
||||
MonitorGameActivity();
|
||||
}
|
||||
|
||||
public async Task<bool> SetPresence()
|
||||
{
|
||||
if (!ActivityInGame)
|
||||
{
|
||||
RichPresence.ClearPresence();
|
||||
return true;
|
||||
}
|
||||
|
||||
string placeThumbnail = "roblox";
|
||||
|
||||
var placeInfo = await Utilities.GetJson<RobloxAsset>($"https://economy.roblox.com/v2/assets/{placeId}/details");
|
||||
var placeInfo = await Utilities.GetJson<RobloxAsset>($"https://economy.roblox.com/v2/assets/{ActivityPlaceId}/details");
|
||||
|
||||
if (placeInfo is null || placeInfo.Creator is null)
|
||||
return false;
|
||||
|
||||
var thumbnailInfo = await Utilities.GetJson<RobloxThumbnails>($"https://thumbnails.roblox.com/v1/places/gameicons?placeIds={placeId}&returnPolicy=PlaceHolder&size=512x512&format=Png&isCircular=false");
|
||||
var thumbnailInfo = await Utilities.GetJson<RobloxThumbnails>($"https://thumbnails.roblox.com/v1/places/gameicons?placeIds={ActivityPlaceId}&returnPolicy=PlaceHolder&size=512x512&format=Png&isCircular=false");
|
||||
|
||||
if (thumbnailInfo is not null)
|
||||
placeThumbnail = thumbnailInfo.Data![0].ImageUrl!;
|
||||
@ -29,20 +179,18 @@ namespace Bloxstrap.Helpers.Integrations
|
||||
{
|
||||
new DiscordRPC.Button()
|
||||
{
|
||||
Label = "Play",
|
||||
Url = $"https://www.roblox.com/games/start?placeId={placeId}&launchData=%7B%7D"
|
||||
Label = "Join",
|
||||
Url = $"https://www.roblox.com/games/start?placeId={ActivityPlaceId}&gameInstanceId={ActivityJobId}&launchData=%7B%7D"
|
||||
},
|
||||
|
||||
new DiscordRPC.Button()
|
||||
{
|
||||
Label = "View Details",
|
||||
Url = $"https://www.roblox.com/games/{placeId}"
|
||||
Label = "See Details",
|
||||
Url = $"https://www.roblox.com/games/{ActivityPlaceId}"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
RichPresence.Initialize();
|
||||
|
||||
RichPresence.SetPresence(new RichPresence()
|
||||
{
|
||||
Details = placeInfo.Name,
|
||||
|
288
Bloxstrap/Helpers/ReverseLineReader.cs
Normal file
288
Bloxstrap/Helpers/ReverseLineReader.cs
Normal file
@ -0,0 +1,288 @@
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
// taken from MiscUtil: https://github.com/loory/MiscUtil
|
||||
// the proper usage of MiscUtil nowadays is to *not* use the library and rather copy the code you need so lol
|
||||
|
||||
namespace Bloxstrap.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Takes an encoding (defaulting to UTF-8) and a function which produces a seekable stream
|
||||
/// (or a filename for convenience) and yields lines from the end of the stream backwards.
|
||||
/// Only single byte encodings, and UTF-8 and Unicode, are supported. The stream
|
||||
/// returned by the function must be seekable.
|
||||
/// </summary>
|
||||
public sealed class ReverseLineReader : IEnumerable<string>
|
||||
{
|
||||
/// <summary>
|
||||
/// Buffer size to use by default. Classes with internal access can specify
|
||||
/// a different buffer size - this is useful for testing.
|
||||
/// </summary>
|
||||
private const int DefaultBufferSize = 4096;
|
||||
|
||||
/// <summary>
|
||||
/// Means of creating a Stream to read from.
|
||||
/// </summary>
|
||||
private readonly Func<Stream> streamSource;
|
||||
|
||||
/// <summary>
|
||||
/// Encoding to use when converting bytes to text
|
||||
/// </summary>
|
||||
private readonly Encoding encoding;
|
||||
|
||||
/// <summary>
|
||||
/// Size of buffer (in bytes) to read each time we read from the
|
||||
/// stream. This must be at least as big as the maximum number of
|
||||
/// bytes for a single character.
|
||||
/// </summary>
|
||||
private readonly int bufferSize;
|
||||
|
||||
/// <summary>
|
||||
/// Function which, when given a position within a file and a byte, states whether
|
||||
/// or not the byte represents the start of a character.
|
||||
/// </summary>
|
||||
private Func<long, byte, bool> characterStartDetector;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a LineReader from a stream source. The delegate is only
|
||||
/// called when the enumerator is fetched. UTF-8 is used to decode
|
||||
/// the stream into text.
|
||||
/// </summary>
|
||||
/// <param name="streamSource">Data source</param>
|
||||
public ReverseLineReader(Func<Stream> streamSource)
|
||||
: this(streamSource, Encoding.UTF8)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a LineReader from a filename. The file is only opened
|
||||
/// (or even checked for existence) when the enumerator is fetched.
|
||||
/// UTF8 is used to decode the file into text.
|
||||
/// </summary>
|
||||
/// <param name="filename">File to read from</param>
|
||||
public ReverseLineReader(string filename)
|
||||
: this(filename, Encoding.UTF8)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a LineReader from a filename. The file is only opened
|
||||
/// (or even checked for existence) when the enumerator is fetched.
|
||||
/// </summary>
|
||||
/// <param name="filename">File to read from</param>
|
||||
/// <param name="encoding">Encoding to use to decode the file into text</param>
|
||||
public ReverseLineReader(string filename, Encoding encoding)
|
||||
: this(() => File.OpenRead(filename), encoding)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a LineReader from a stream source. The delegate is only
|
||||
/// called when the enumerator is fetched.
|
||||
/// </summary>
|
||||
/// <param name="streamSource">Data source</param>
|
||||
/// <param name="encoding">Encoding to use to decode the stream into text</param>
|
||||
public ReverseLineReader(Func<Stream> streamSource, Encoding encoding)
|
||||
: this(streamSource, encoding, DefaultBufferSize)
|
||||
{
|
||||
}
|
||||
|
||||
internal ReverseLineReader(Func<Stream> streamSource, Encoding encoding, int bufferSize)
|
||||
{
|
||||
this.streamSource = streamSource;
|
||||
this.encoding = encoding;
|
||||
this.bufferSize = bufferSize;
|
||||
if (encoding.IsSingleByte)
|
||||
{
|
||||
// For a single byte encoding, every byte is the start (and end) of a character
|
||||
characterStartDetector = (pos, data) => true;
|
||||
}
|
||||
else if (encoding is UnicodeEncoding)
|
||||
{
|
||||
// For UTF-16, even-numbered positions are the start of a character.
|
||||
// TODO: This assumes no surrogate pairs. More work required
|
||||
// to handle that.
|
||||
characterStartDetector = (pos, data) => (pos & 1) == 0;
|
||||
}
|
||||
else if (encoding is UTF8Encoding)
|
||||
{
|
||||
// For UTF-8, bytes with the top bit clear or the second bit set are the start of a character
|
||||
// See http://www.cl.cam.ac.uk/~mgk25/unicode.html
|
||||
characterStartDetector = (pos, data) => (data & 0x80) == 0 || (data & 0x40) != 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Only single byte, UTF-8 and Unicode encodings are permitted");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the enumerator reading strings backwards. If this method discovers that
|
||||
/// the returned stream is either unreadable or unseekable, a NotSupportedException is thrown.
|
||||
/// </summary>
|
||||
public IEnumerator<string> GetEnumerator()
|
||||
{
|
||||
Stream stream = streamSource();
|
||||
if (!stream.CanSeek)
|
||||
{
|
||||
stream.Dispose();
|
||||
throw new NotSupportedException("Unable to seek within stream");
|
||||
}
|
||||
if (!stream.CanRead)
|
||||
{
|
||||
stream.Dispose();
|
||||
throw new NotSupportedException("Unable to read within stream");
|
||||
}
|
||||
return GetEnumeratorImpl(stream);
|
||||
}
|
||||
|
||||
private IEnumerator<string> GetEnumeratorImpl(Stream stream)
|
||||
{
|
||||
try
|
||||
{
|
||||
long position = stream.Length;
|
||||
|
||||
if (encoding is UnicodeEncoding && (position & 1) != 0)
|
||||
{
|
||||
throw new InvalidDataException("UTF-16 encoding provided, but stream has odd length.");
|
||||
}
|
||||
|
||||
// Allow up to two bytes for data from the start of the previous
|
||||
// read which didn't quite make it as full characters
|
||||
byte[] buffer = new byte[bufferSize + 2];
|
||||
char[] charBuffer = new char[encoding.GetMaxCharCount(buffer.Length)];
|
||||
int leftOverData = 0;
|
||||
String? previousEnd = null;
|
||||
// TextReader doesn't return an empty string if there's line break at the end
|
||||
// of the data. Therefore we don't return an empty string if it's our *first*
|
||||
// return.
|
||||
bool firstYield = true;
|
||||
|
||||
// A line-feed at the start of the previous buffer means we need to swallow
|
||||
// the carriage-return at the end of this buffer - hence this needs declaring
|
||||
// way up here!
|
||||
bool swallowCarriageReturn = false;
|
||||
|
||||
while (position > 0)
|
||||
{
|
||||
int bytesToRead = Math.Min(position > int.MaxValue ? bufferSize : (int)position, bufferSize);
|
||||
|
||||
position -= bytesToRead;
|
||||
stream.Position = position;
|
||||
StreamUtil.ReadExactly(stream, buffer, bytesToRead);
|
||||
// If we haven't read a full buffer, but we had bytes left
|
||||
// over from before, copy them to the end of the buffer
|
||||
if (leftOverData > 0 && bytesToRead != bufferSize)
|
||||
{
|
||||
// Buffer.BlockCopy doesn't document its behaviour with respect
|
||||
// to overlapping data: we *might* just have read 7 bytes instead of
|
||||
// 8, and have two bytes to copy...
|
||||
Array.Copy(buffer, bufferSize, buffer, bytesToRead, leftOverData);
|
||||
}
|
||||
// We've now *effectively* read this much data.
|
||||
bytesToRead += leftOverData;
|
||||
|
||||
int firstCharPosition = 0;
|
||||
while (!characterStartDetector(position + firstCharPosition, buffer[firstCharPosition]))
|
||||
{
|
||||
firstCharPosition++;
|
||||
// Bad UTF-8 sequences could trigger this. For UTF-8 we should always
|
||||
// see a valid character start in every 3 bytes, and if this is the start of the file
|
||||
// so we've done a short read, we should have the character start
|
||||
// somewhere in the usable buffer.
|
||||
if (firstCharPosition == 3 || firstCharPosition == bytesToRead)
|
||||
{
|
||||
throw new InvalidDataException("Invalid UTF-8 data");
|
||||
}
|
||||
}
|
||||
leftOverData = firstCharPosition;
|
||||
|
||||
int charsRead = encoding.GetChars(buffer, firstCharPosition, bytesToRead - firstCharPosition, charBuffer, 0);
|
||||
int endExclusive = charsRead;
|
||||
|
||||
for (int i = charsRead - 1; i >= 0; i--)
|
||||
{
|
||||
char lookingAt = charBuffer[i];
|
||||
if (swallowCarriageReturn)
|
||||
{
|
||||
swallowCarriageReturn = false;
|
||||
if (lookingAt == '\r')
|
||||
{
|
||||
endExclusive--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Anything non-line-breaking, just keep looking backwards
|
||||
if (lookingAt != '\n' && lookingAt != '\r')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// End of CRLF? Swallow the preceding CR
|
||||
if (lookingAt == '\n')
|
||||
{
|
||||
swallowCarriageReturn = true;
|
||||
}
|
||||
int start = i + 1;
|
||||
string bufferContents = new string(charBuffer, start, endExclusive - start);
|
||||
endExclusive = i;
|
||||
string stringToYield = previousEnd == null ? bufferContents : bufferContents + previousEnd;
|
||||
if (!firstYield || stringToYield.Length != 0)
|
||||
{
|
||||
yield return stringToYield;
|
||||
}
|
||||
firstYield = false;
|
||||
previousEnd = null;
|
||||
}
|
||||
|
||||
previousEnd = endExclusive == 0 ? null : (new string(charBuffer, 0, endExclusive) + previousEnd);
|
||||
|
||||
// If we didn't decode the start of the array, put it at the end for next time
|
||||
if (leftOverData != 0)
|
||||
{
|
||||
Buffer.BlockCopy(buffer, 0, buffer, bufferSize, leftOverData);
|
||||
}
|
||||
}
|
||||
if (leftOverData != 0)
|
||||
{
|
||||
// At the start of the final buffer, we had the end of another character.
|
||||
throw new InvalidDataException("Invalid UTF-8 data at start of stream");
|
||||
}
|
||||
if (firstYield && string.IsNullOrEmpty(previousEnd))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
yield return previousEnd ?? "";
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
public static class StreamUtil
|
||||
{
|
||||
public static void ReadExactly(Stream input, byte[] buffer, int bytesToRead)
|
||||
{
|
||||
int index = 0;
|
||||
while (index < bytesToRead)
|
||||
{
|
||||
int read = input.Read(buffer, index, bytesToRead - index);
|
||||
if (read == 0)
|
||||
{
|
||||
throw new EndOfStreamException
|
||||
(String.Format("End of stream reached with {0} byte{1} left to read.",
|
||||
bytesToRead - index,
|
||||
bytesToRead - index == 1 ? "s" : ""));
|
||||
}
|
||||
index += read;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ using System.Text.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using Bloxstrap.Models;
|
||||
using Bloxstrap.Dialogs;
|
||||
|
||||
namespace Bloxstrap.Helpers
|
||||
{
|
||||
@ -39,6 +40,8 @@ namespace Bloxstrap.Helpers
|
||||
MessageBoxButtons.OK
|
||||
);
|
||||
|
||||
new Preferences().ShowDialog();
|
||||
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
@ -71,13 +71,13 @@ namespace Bloxstrap
|
||||
}
|
||||
else
|
||||
{
|
||||
BaseDirectory = (string?)registryKey.GetValue("InstallLocation");
|
||||
BaseDirectory = (string)registryKey.GetValue("InstallLocation")!;
|
||||
registryKey.Close();
|
||||
}
|
||||
|
||||
// preferences dialog was closed, and so base directory was never set
|
||||
// (this doesnt account for the registry value not existing but thats basically never gonna happen)
|
||||
if (BaseDirectory is null)
|
||||
if (String.IsNullOrEmpty(BaseDirectory))
|
||||
return;
|
||||
|
||||
Directories.Initialize(BaseDirectory);
|
||||
|
Loading…
Reference in New Issue
Block a user