mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2025-10-14 21:22:49 +00:00
Add Sentry Error Tracking (#2670)
* Add Sentry Error Tracking * Omit personally identifiable information and add additional sentry context * Remove debug message * Make sentry opt-out and add related notices and strings Also add Minecraft Version to error context * Update build to send release info to sentry * Adjust sentry error tracking - Send the user-friendly Minecraft Version in the error logs - Capture exceptions in more parts of the application We now capture exceptions from the following locations: - Protocol18 (1.8+) Packet errors - Errors during client initialization phase (When client is about to start, session keys are NEVER sent to sentry) * Make Sentry DSN configurable and repository-specific The Sentry DSN will automatically be filled out on the main repository through the Github Actions build. * Update build-and-release.yml Update sed command * style: change variable name nitpick, just to make it a little bit more descriptive * Add Sentry branding in README. * remove old code (merge conflict)
This commit is contained in:
parent
8756ff5b3c
commit
08551097c6
11 changed files with 148 additions and 24 deletions
12
.github/workflows/build-and-release.yml
vendored
12
.github/workflows/build-and-release.yml
vendored
|
|
@ -61,6 +61,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
echo '' >> ${{ env.assembly-info }}
|
echo '' >> ${{ env.assembly-info }}
|
||||||
echo "[assembly: AssemblyConfiguration(\"GitHub build ${{ github.run_number }}, built on ${{ env.date_dashed }} from commit ${{ env.commit }}\")]" >> ${{ env.assembly-info }}
|
echo "[assembly: AssemblyConfiguration(\"GitHub build ${{ github.run_number }}, built on ${{ env.date_dashed }} from commit ${{ env.commit }}\")]" >> ${{ env.assembly-info }}
|
||||||
|
sed -i -e 's|SentryDSN = "";|SentryDSN = "${{ secrets.SENTRY_DSN }}";|g' ${{ env.project-path }}/Program.cs
|
||||||
|
|
||||||
- name: Build Target
|
- name: Build Target
|
||||||
run: dotnet publish ${{ env.project-path }}.sln -f ${{ env.target-version }} -r ${{ matrix.target }} ${{ env.compile-flags }}
|
run: dotnet publish ${{ env.project-path }}.sln -f ${{ env.target-version }} -r ${{ matrix.target }} ${{ env.compile-flags }}
|
||||||
|
|
@ -177,6 +178,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
echo '' >> ${{ env.assembly-info }}
|
echo '' >> ${{ env.assembly-info }}
|
||||||
echo "[assembly: AssemblyConfiguration(\"GitHub build ${{ github.run_number }}, built on ${{ env.date_dashed }} from commit ${{ env.commit }}\")]" >> ${{ env.assembly-info }}
|
echo "[assembly: AssemblyConfiguration(\"GitHub build ${{ github.run_number }}, built on ${{ env.date_dashed }} from commit ${{ env.commit }}\")]" >> ${{ env.assembly-info }}
|
||||||
|
sed -i -e 's|SentryDSN = "";|SentryDSN = "${{ secrets.SENTRY_DSN }}";|g' ${{ env.project-path }}/Program.cs
|
||||||
|
|
||||||
- name: Build Target
|
- name: Build Target
|
||||||
run: dotnet publish ${{ env.project-path }}.sln -f ${{ env.target-version }} -r ${{ matrix.target }} ${{ env.compile-flags }}
|
run: dotnet publish ${{ env.project-path }}.sln -f ${{ env.target-version }} -r ${{ matrix.target }} ${{ env.compile-flags }}
|
||||||
|
|
@ -196,6 +198,16 @@ jobs:
|
||||||
assetName: ${{ env.PROJECT }}-${{ (contains(matrix.target, 'linux-x64') && 'linux.zip') || (contains(matrix.target, 'win-x86') && 'windows-x86.zip') || (contains(matrix.target, 'win-x64') && 'windows-x64.zip') || (contains(matrix.target, 'linux-arm64') && 'linux-arm64.zip') || (contains(matrix.target, 'osx-x64') && 'osx.zip') }}
|
assetName: ${{ env.PROJECT }}-${{ (contains(matrix.target, 'linux-x64') && 'linux.zip') || (contains(matrix.target, 'win-x86') && 'windows-x86.zip') || (contains(matrix.target, 'win-x64') && 'windows-x64.zip') || (contains(matrix.target, 'linux-arm64') && 'linux-arm64.zip') || (contains(matrix.target, 'osx-x64') && 'osx.zip') }}
|
||||||
tag: ${{ format('{0}-{1}', env.date, github.run_number) }}
|
tag: ${{ format('{0}-{1}', env.date, github.run_number) }}
|
||||||
|
|
||||||
|
- name: Sentry Release
|
||||||
|
uses: getsentry/action-release@v1.7.0
|
||||||
|
env:
|
||||||
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||||
|
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||||
|
with:
|
||||||
|
environment: production
|
||||||
|
dist: ${{ format('{0}-{1}', env.date, github.run_number) }}
|
||||||
|
|
||||||
determine-build:
|
determine-build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ using MinecraftClient.Protocol.ProfileKey;
|
||||||
using MinecraftClient.Protocol.Session;
|
using MinecraftClient.Protocol.Session;
|
||||||
using MinecraftClient.Proxy;
|
using MinecraftClient.Proxy;
|
||||||
using MinecraftClient.Scripting;
|
using MinecraftClient.Scripting;
|
||||||
|
using Sentry;
|
||||||
using static MinecraftClient.Settings;
|
using static MinecraftClient.Settings;
|
||||||
|
|
||||||
namespace MinecraftClient
|
namespace MinecraftClient
|
||||||
|
|
@ -191,6 +192,33 @@ namespace MinecraftClient
|
||||||
Log.WarnEnabled = Config.Logging.WarningMessages;
|
Log.WarnEnabled = Config.Logging.WarningMessages;
|
||||||
Log.ErrorEnabled = Config.Logging.ErrorMessages;
|
Log.ErrorEnabled = Config.Logging.ErrorMessages;
|
||||||
|
|
||||||
|
// SENTRY: Send our client version and server version to Sentry
|
||||||
|
SentrySdk.ConfigureScope(scope =>
|
||||||
|
{
|
||||||
|
scope.SetTag("Protocol Version", protocolversion.ToString());
|
||||||
|
scope.SetTag("Minecraft Version", ProtocolHandler.ProtocolVersion2MCVer(protocolversion));
|
||||||
|
scope.SetTag("MCC Build", Program.BuildInfo == null ? "Debug" : Program.BuildInfo);
|
||||||
|
|
||||||
|
if (forgeInfo != null)
|
||||||
|
scope.SetTag("Forge Version", forgeInfo?.Version.ToString());
|
||||||
|
|
||||||
|
scope.Contexts["Server Information"] = new
|
||||||
|
{
|
||||||
|
ProtocolVersion = protocolversion,
|
||||||
|
MinecraftVersion = ProtocolHandler.ProtocolVersion2MCVer(protocolversion),
|
||||||
|
ForgeInfo = forgeInfo?.Version
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.Contexts["Client Configuration"] = new
|
||||||
|
{
|
||||||
|
TerrainAndMovementsEnabled = terrainAndMovementsEnabled,
|
||||||
|
InventoryHandlingEnabled = inventoryHandlingEnabled,
|
||||||
|
EntityHandlingEnabled = entityHandlingEnabled
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
SentrySdk.StartSession();
|
||||||
|
|
||||||
/* Load commands from Commands namespace */
|
/* Load commands from Commands namespace */
|
||||||
LoadCommands();
|
LoadCommands();
|
||||||
|
|
||||||
|
|
@ -588,6 +616,8 @@ namespace MinecraftClient
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SentrySdk.EndSession();
|
||||||
|
|
||||||
if (!will_restart)
|
if (!will_restart)
|
||||||
{
|
{
|
||||||
ConsoleInteractive.ConsoleReader.StopReadThread();
|
ConsoleInteractive.ConsoleReader.StopReadThread();
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@
|
||||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||||
<PackageReference Include="Microsoft.Windows.Compatibility" Version="7.0.3" />
|
<PackageReference Include="Microsoft.Windows.Compatibility" Version="7.0.3" />
|
||||||
<PackageReference Include="Samboy063.Tomlet" Version="5.0.1" />
|
<PackageReference Include="Samboy063.Tomlet" Version="5.0.1" />
|
||||||
|
<PackageReference Include="Sentry" Version="4.1.0" />
|
||||||
<PackageReference Include="SingleFileExtractor.Core" Version="1.0.1" />
|
<PackageReference Include="SingleFileExtractor.Core" Version="1.0.1" />
|
||||||
<PackageReference Include="starksoft.aspen" Version="1.1.8">
|
<PackageReference Include="starksoft.aspen" Version="1.1.8">
|
||||||
<NoWarn>NU1701</NoWarn>
|
<NoWarn>NU1701</NoWarn>
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ using MinecraftClient.Protocol.ProfileKey;
|
||||||
using MinecraftClient.Protocol.Session;
|
using MinecraftClient.Protocol.Session;
|
||||||
using MinecraftClient.Scripting;
|
using MinecraftClient.Scripting;
|
||||||
using MinecraftClient.WinAPI;
|
using MinecraftClient.WinAPI;
|
||||||
|
using Sentry;
|
||||||
using Tomlet;
|
using Tomlet;
|
||||||
using static MinecraftClient.Settings;
|
using static MinecraftClient.Settings;
|
||||||
using static MinecraftClient.Settings.ConsoleConfigHealper.ConsoleConfig;
|
using static MinecraftClient.Settings.ConsoleConfigHealper.ConsoleConfig;
|
||||||
|
|
@ -50,14 +51,31 @@ namespace MinecraftClient
|
||||||
public static readonly string? BuildInfo = null;
|
public static readonly string? BuildInfo = null;
|
||||||
|
|
||||||
private static Tuple<Thread, CancellationTokenSource>? offlinePrompt = null;
|
private static Tuple<Thread, CancellationTokenSource>? offlinePrompt = null;
|
||||||
|
private static IDisposable _sentrySdk;
|
||||||
private static bool useMcVersionOnce = false;
|
private static bool useMcVersionOnce = false;
|
||||||
private static string settingsIniPath = "MinecraftClient.ini";
|
private static string settingsIniPath = "MinecraftClient.ini";
|
||||||
|
|
||||||
|
// [SENTRY]
|
||||||
|
// Setting this string to an empty string will disable Sentry
|
||||||
|
private const string SentryDSN = "";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The main entry point of Minecraft Console Client
|
/// The main entry point of Minecraft Console Client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
// [SENTRY] Initialize Sentry SDK only if the DSN is not empty
|
||||||
|
if (SentryDSN != string.Empty) {
|
||||||
|
_sentrySdk = SentrySdk.Init(options =>
|
||||||
|
{
|
||||||
|
options.Dsn = SentryDSN;
|
||||||
|
options.AutoSessionTracking = true;
|
||||||
|
options.IsGlobalModeEnabled = true;
|
||||||
|
options.EnableTracing = true;
|
||||||
|
options.SendDefaultPii = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
// "ToLower" require "CultureInfo" to be initialized on first run, which can take a lot of time.
|
// "ToLower" require "CultureInfo" to be initialized on first run, which can take a lot of time.
|
||||||
|
|
@ -139,6 +157,12 @@ namespace MinecraftClient
|
||||||
if (newlyGenerated)
|
if (newlyGenerated)
|
||||||
ConsoleIO.WriteLineFormatted("§c" + Translations.mcc_settings_generated);
|
ConsoleIO.WriteLineFormatted("§c" + Translations.mcc_settings_generated);
|
||||||
ConsoleIO.WriteLine(Translations.mcc_run_with_default_settings);
|
ConsoleIO.WriteLine(Translations.mcc_run_with_default_settings);
|
||||||
|
|
||||||
|
// Only show the Sentry message if the DSN is not empty
|
||||||
|
// as Sentry will not be initialized if the DSN is empty
|
||||||
|
if (SentryDSN != string.Empty) {
|
||||||
|
ConsoleIO.WriteLine(Translations.mcc_sentry_logging);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (!loadSucceed)
|
else if (!loadSucceed)
|
||||||
{
|
{
|
||||||
|
|
@ -182,6 +206,9 @@ namespace MinecraftClient
|
||||||
ConsoleIO.WriteLine(string.Format(Translations.mcc_help_us_translate, Settings.TranslationProjectUrl));
|
ConsoleIO.WriteLine(string.Format(Translations.mcc_help_us_translate, Settings.TranslationProjectUrl));
|
||||||
WriteBackSettings(true); // format
|
WriteBackSettings(true); // format
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Config.Main.Advanced.EnableSentry)
|
||||||
|
_sentrySdk.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Other command-line arguments
|
//Other command-line arguments
|
||||||
|
|
@ -632,6 +659,9 @@ namespace MinecraftClient
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
// [SENTRY]
|
||||||
|
SentrySdk.CaptureException(e);
|
||||||
|
|
||||||
ConsoleIO.WriteLine(e.Message);
|
ConsoleIO.WriteLine(e.Message);
|
||||||
ConsoleIO.WriteLine(e.StackTrace ?? "");
|
ConsoleIO.WriteLine(e.StackTrace ?? "");
|
||||||
HandleFailure(); // Other error
|
HandleFailure(); // Other error
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ using MinecraftClient.Protocol.ProfileKey;
|
||||||
using MinecraftClient.Protocol.Session;
|
using MinecraftClient.Protocol.Session;
|
||||||
using MinecraftClient.Proxy;
|
using MinecraftClient.Proxy;
|
||||||
using MinecraftClient.Scripting;
|
using MinecraftClient.Scripting;
|
||||||
|
using Sentry;
|
||||||
using static MinecraftClient.Settings;
|
using static MinecraftClient.Settings;
|
||||||
using static MinecraftClient.Settings.MainConfigHelper.MainConfig.GeneralConfig;
|
using static MinecraftClient.Settings.MainConfigHelper.MainConfig.GeneralConfig;
|
||||||
|
|
||||||
|
|
@ -370,6 +371,10 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
/// <returns>TRUE if the packet was processed, FALSE if ignored or unknown</returns>
|
/// <returns>TRUE if the packet was processed, FALSE if ignored or unknown</returns>
|
||||||
internal bool HandlePacket(int packetId, Queue<byte> packetData)
|
internal bool HandlePacket(int packetId, Queue<byte> packetData)
|
||||||
{
|
{
|
||||||
|
// This copy is necessary because by the time we get to the catch block,
|
||||||
|
// the packetData queue will have been processed and the data will be lost
|
||||||
|
var _copy = packetData.ToArray();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
switch (currentState)
|
switch (currentState)
|
||||||
|
|
@ -461,7 +466,7 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
innerException.InnerException is SocketException)
|
innerException.InnerException is SocketException)
|
||||||
throw; //Thread abort or Connection lost rather than invalid data
|
throw; //Thread abort or Connection lost rather than invalid data
|
||||||
|
|
||||||
throw new System.IO.InvalidDataException(
|
var exception = new System.IO.InvalidDataException(
|
||||||
string.Format(Translations.exception_packet_process,
|
string.Format(Translations.exception_packet_process,
|
||||||
packetPalette.GetIncomingTypeById(packetId),
|
packetPalette.GetIncomingTypeById(packetId),
|
||||||
packetId,
|
packetId,
|
||||||
|
|
@ -469,6 +474,21 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
currentState == CurrentState.Login,
|
currentState == CurrentState.Login,
|
||||||
innerException.GetType()),
|
innerException.GetType()),
|
||||||
innerException);
|
innerException);
|
||||||
|
|
||||||
|
SentrySdk.AddBreadcrumb(new Breadcrumb("S -> C Packet", "network", new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ "Packet ID", packetId.ToString() },
|
||||||
|
{ "Packet Type ", packetPalette.GetIncomingTypeById(packetId).ToString() },
|
||||||
|
{ "Protocol Version", protocolVersion.ToString() },
|
||||||
|
{ "Minecraft Version", ProtocolHandler.ProtocolVersion2MCVer(protocolVersion) },
|
||||||
|
{ "Current State", currentState.ToString() },
|
||||||
|
{ "Packet Data", string.Join(" ", _copy.Select(b => b.ToString("X2"))) },
|
||||||
|
{ "Inner Exception", innerException.GetType().ToString() }
|
||||||
|
}, "packet", BreadcrumbLevel.Error));
|
||||||
|
|
||||||
|
SentrySdk.CaptureException(exception);
|
||||||
|
|
||||||
|
throw exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// <auto-generated>
|
// <auto-generated>
|
||||||
// This code was generated by a tool.
|
// This code was generated by a tool.
|
||||||
// Runtime Version:4.0.30319.42000
|
|
||||||
//
|
//
|
||||||
// Changes to this file may cause incorrect behavior and will be lost if
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
// the code is regenerated.
|
// the code is regenerated.
|
||||||
|
|
@ -799,8 +798,7 @@ namespace MinecraftClient {
|
||||||
///NOTE: This is an experimental feature, the bot can be slow at times, you need to walk with a normal speed and to sometimes stop for it to be able to keep up with you
|
///NOTE: This is an experimental feature, the bot can be slow at times, you need to walk with a normal speed and to sometimes stop for it to be able to keep up with you
|
||||||
///It's similar to making animals follow you when you're holding food in your hand.
|
///It's similar to making animals follow you when you're holding food in your hand.
|
||||||
///This is due to a slow pathfinding algorithm, we're working on getting a better one
|
///This is due to a slow pathfinding algorithm, we're working on getting a better one
|
||||||
///You can tweak the update limit and find what works best for you. (NOTE: Do not but a very low one, because you might achieve the opposite,
|
///You can tweak the update limit and find what works best for you. (NOTE: Do not but a very low one, because you might achieve the opposite,
/// [rest of string was truncated]";.
|
||||||
/// [rest of string was truncated]";.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string ChatBot_FollowPlayer {
|
internal static string ChatBot_FollowPlayer {
|
||||||
get {
|
get {
|
||||||
|
|
@ -1451,6 +1449,15 @@ namespace MinecraftClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Set to false to opt-out of Sentry error logging..
|
||||||
|
/// </summary>
|
||||||
|
internal static string Main_Advanced_enable_sentry {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Main.Advanced.enable_sentry", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Toggle entity handling..
|
/// Looks up a localized string similar to Toggle entity handling..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -852,4 +852,7 @@ If the connection to the Minecraft game server is blocked by the firewall, set E
|
||||||
<data name="Main.General.AuthlibServer" xml:space="preserve">
|
<data name="Main.General.AuthlibServer" xml:space="preserve">
|
||||||
<value>Yggdrasil authlib server domain name and port.</value>
|
<value>Yggdrasil authlib server domain name and port.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Main.Advanced.enable_sentry" xml:space="preserve">
|
||||||
|
<value>Set to false to opt-out of Sentry error logging.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// <auto-generated>
|
// <auto-generated>
|
||||||
// This code was generated by a tool.
|
// This code was generated by a tool.
|
||||||
// Runtime Version:4.0.30319.42000
|
|
||||||
//
|
//
|
||||||
// Changes to this file may cause incorrect behavior and will be lost if
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
// the code is regenerated.
|
// the code is regenerated.
|
||||||
|
|
@ -5782,6 +5781,15 @@ namespace MinecraftClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to MCC uses Sentry to log errors. You can opt-out by setting the EnableSentry option in the configuration file to false..
|
||||||
|
/// </summary>
|
||||||
|
internal static string mcc_sentry_logging {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("mcc.sentry_logging", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Server is in offline mode..
|
/// Looks up a localized string similar to Server is in offline mode..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -2130,4 +2130,7 @@ Logging in...</value>
|
||||||
<data name="mcc.select_profile" xml:space="preserve">
|
<data name="mcc.select_profile" xml:space="preserve">
|
||||||
<value>Select a profile from available profiles:</value>
|
<value>Select a profile from available profiles:</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="mcc.sentry_logging" xml:space="preserve">
|
||||||
|
<value>MCC uses Sentry to log errors. You can opt-out by setting the EnableSentry option in the configuration file to false.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -506,6 +506,9 @@ namespace MinecraftClient
|
||||||
[TomlDoNotInlineObject]
|
[TomlDoNotInlineObject]
|
||||||
public class AdvancedConfig
|
public class AdvancedConfig
|
||||||
{
|
{
|
||||||
|
[TomlInlineComment("$Main.Advanced.enable_sentry$")]
|
||||||
|
public bool EnableSentry = true;
|
||||||
|
|
||||||
[TomlInlineComment("$Main.Advanced.language$")]
|
[TomlInlineComment("$Main.Advanced.language$")]
|
||||||
public string Language = "en_us";
|
public string Language = "en_us";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,3 +74,10 @@ The main terms of the CDDL-1.0 license are basically the following:
|
||||||
|
|
||||||
More info at http://qstuff.blogspot.fr/2007/04/why-cddl.html
|
More info at http://qstuff.blogspot.fr/2007/04/why-cddl.html
|
||||||
Full license at http://opensource.org/licenses/CDDL-1.0
|
Full license at http://opensource.org/licenses/CDDL-1.0
|
||||||
|
|
||||||
|
## Uses technologies from
|
||||||
|
<div align="center">
|
||||||
|
<a href="https://sentry.io/welcome/">
|
||||||
|
<img src="https://github.com/breadbyte/Minecraft-Console-Client/assets/14045257/411e9a2f-cd9b-4bb5-b7e9-cd7529c76b88" alt="Sentry" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue