Skip to content
This repository was archived by the owner on Nov 8, 2023. It is now read-only.

Commit 5392317

Browse files
committed
Merge branch 'devel'
2 parents ed7d24c + a7914d6 commit 5392317

File tree

14 files changed

+184
-20
lines changed

14 files changed

+184
-20
lines changed

.github/workflows/todo.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@ jobs:
2727
- name: Run Issue Bot
2828
uses: derjuulsn/todo-issue@main
2929
with:
30-
label: ["automated (todo :spiral_notepad:)"]
30+
label: ["automated", "automated (todo :spiral_notepad:)"]
3131
env:
3232
GITHUB_TOKEN: ${{ secrets.TODO_TOKEN }}

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010

1111
## Goals/Planned Features
1212

13-
* [ ] Full feature parity with the proprietary launcher:
13+
* [x] Full feature parity with the proprietary launcher:
1414
* [x] launcher auto-updating;
15-
* [ ] game auto-updating.
16-
* [ ] Easy, accessible API for registering multiple *games* under the launcher:
17-
* [ ] unified system that takes a collection of `IGame` objects which describe how UI should be rendered and how profile management and game launching should be done.
15+
* [x] game auto-updating.
16+
* [x] Easy, accessible API for registering multiple *games* under the launcher:
17+
* [x] unified system that takes a collection of `Game` objects which describe how UI should be rendered and how profile management and game launching should be done.
1818
* [ ] Mod-centric features:
1919
* [ ] profile management, allowing people to configure a save directory as well as manage what patches should be applied to the game;
2020
* [ ] an eventual custom build of HoloCure that is downloaded separately, instead of applied as patches (will be a separate `IGame` object).

src/HoloCure.Launcher.Base/Games/GameProvider.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ public class GameProvider
1515

1616
public virtual Bindable<Game?> SelectedGame { get; } = new();
1717

18+
// THIS IS TEMPORARY; EXISTS FOR DRPC IN DESKTOP IMPL UNTIL WE REWRITE LAUNCHING
19+
public virtual Bindable<Game?> PlayingGame { get; } = new();
20+
1821
public GameProvider()
1922
{
2023
Games = new Lazy<List<Game>>(() => GetGames().ToList());

src/HoloCure.Launcher.Base/Games/HoloCure/HoloCureGame.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public override async Task InstallOrPlayGameAsync(Action<GameAlert> onAlert, Sto
5858
RedirectStandardError = true,
5959
WorkingDirectory = storage.GetFullPath(hcDir)
6060
});
61+
onAlert(GameAlert.GameStarted);
6162
proc?.WaitForExit();
6263
onAlert(GameAlert.GameExited);
6364
}

src/HoloCure.Launcher.Base/LauncherBase.BuildInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace HoloCure.Launcher.Base;
77

88
partial class LauncherBase
99
{
10-
protected abstract IBuildInfo BuildInfo { get; }
10+
public abstract IBuildInfo BuildInfo { get; }
1111
}
1212

1313
public interface IBuildInfo

src/HoloCure.Launcher.Base/Rendering/Graphics/UserInterface/Screens/GameLauncherScreen.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public GameLauncherScreen(Game game)
3232
}
3333

3434
[BackgroundDependencyLoader]
35-
private void load(TextureStore textures, Storage storage)
35+
private void load(TextureStore textures, Storage storage, GameProvider gameProvider)
3636
{
3737
InternalChildren = new Drawable[]
3838
{
@@ -77,12 +77,14 @@ void handleAlert(GameAlert alert)
7777

7878
case GameAlert.GameStarted:
7979
playButton.UpdateText("Game started!");
80+
gameProvider.PlayingGame.Value = game; /* TEMPORARY */
8081
break;
8182

8283
case GameAlert.GameExited:
8384
playButton.UpdateText("Play Game");
8485
playButton.Enabled.Value = true;
8586
updateButton.Enabled.Value = true;
87+
gameProvider.PlayingGame.Value = null; /* TEMPORARY */
8688
break;
8789

8890
case GameAlert.CheckingForUpdates:
Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
using DiscordRPC;
1+
using System;
2+
using DiscordRPC;
23
using DiscordRPC.Message;
4+
using HoloCure.Launcher.Base.Games;
35
using osu.Framework.Allocation;
46
using osu.Framework.Graphics;
57
using osu.Framework.Logging;
@@ -13,28 +15,28 @@ internal class DRPComponent : Component
1315

1416
private DiscordRpcClient client = null!;
1517

16-
private readonly RichPresence presence = new RichPresence
18+
private readonly RichPresence presence = new()
1719
{
1820
Assets = new Assets { LargeImageKey = large_image_key }
1921
};
2022

2123
// see: https://github.com/ppy/osu/blob/master/osu.Desktop/DiscordRichPresence.cs#L48
2224
[BackgroundDependencyLoader]
23-
private void load()
25+
private void load(GameProvider gameProvider)
2426
{
2527
client = new DiscordRpcClient(client_id)
2628
{
2729
SkipIdenticalPresence = false // https://github.com/ppy/osu/blob/master/osu.Desktop/DiscordRichPresence.cs#L52
2830
};
2931

3032
client.OnReady += onReady;
31-
32-
// https://github.com/ppy/osu/blob/master/osu.Desktop/DiscordRichPresence.cs#L57
33-
client.OnConnectionFailed += (_, _) => client.Deinitialize();
34-
33+
client.OnConnectionFailed += (_, _) => client.Deinitialize(); // https://github.com/ppy/osu/blob/master/osu.Desktop/DiscordRichPresence.cs#L57
3534
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
36-
3735
client.Initialize();
36+
37+
gameProvider.SelectedGame.ValueChanged += e => { updatePresence(e.NewValue, gameProvider.PlayingGame.Value); };
38+
gameProvider.PlayingGame.ValueChanged += e => { updatePresence(gameProvider.SelectedGame.Value, e.NewValue); };
39+
updatePresence(gameProvider.SelectedGame.Value, gameProvider.PlayingGame.Value); // ensure current selected game when this component is loaded is displayed
3840
}
3941

4042
private void onReady(object _, ReadyMessage __)
@@ -43,6 +45,19 @@ private void onReady(object _, ReadyMessage __)
4345
updateStatus();
4446
}
4547

48+
private void updatePresence(Base.Games.Game? browsingGame, Base.Games.Game? playingGame)
49+
{
50+
presence.Details = playingGame is null ? "Browsing games..." : "Playing game!";
51+
presence.State = playingGame is null ? browsingGame is null ? "" : $"Looking at {browsingGame.GameTitle}" : $"Playing {playingGame.GameTitle}";
52+
53+
presence.Timestamps = new Timestamps
54+
{
55+
Start = playingGame is not null ? DateTime.UtcNow : null
56+
};
57+
58+
updateStatus();
59+
}
60+
4661
private void updateStatus()
4762
{
4863
if (!client.IsInitialized) return;
@@ -52,7 +67,7 @@ private void updateStatus()
5267

5368
protected override void Dispose(bool isDisposing)
5469
{
55-
client.Dispose();
5670
base.Dispose(isDisposing);
71+
client.Dispose();
5772
}
5873
}

src/HoloCure.Launcher.Desktop/HoloCure.Launcher.Desktop.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
<ItemGroup Label="Package References">
1818
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
19+
<PackageReference Include="Sentry" Version="3.24.0" />
1920
</ItemGroup>
2021

2122
<ItemGroup Label="Resources">

src/HoloCure.Launcher.Desktop/LauncherGameDesktop.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Drawing;
33
using System.IO;
44
using HoloCure.Launcher.Desktop.Components;
5+
using HoloCure.Launcher.Desktop.Utils;
56
using HoloCure.Launcher.Game;
67
using osu.Framework.Allocation;
78
using osu.Framework.Configuration;
@@ -21,6 +22,13 @@ public class LauncherGameDesktop : LauncherGame
2122

2223
private DependencyContainer dependencies = null!;
2324

25+
private SentryLogger sentryLogger;
26+
27+
public LauncherGameDesktop()
28+
{
29+
sentryLogger = new SentryLogger(this);
30+
}
31+
2432
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
2533

2634
protected override IDictionary<FrameworkSetting, object> GetFrameworkConfigDefaults()
@@ -52,4 +60,10 @@ protected override void LoadComplete()
5260
LoadComponentAsync(new DRPComponent());
5361
LoadComponentAsync(new UpdaterComponent());
5462
}
63+
64+
protected override void Dispose(bool isDisposing)
65+
{
66+
base.Dispose(isDisposing);
67+
sentryLogger.Dispose();
68+
}
5569
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright (c) Tomat. Licensed under the GPL v3 License.
2+
// See the LICENSE-GPL file in the repository root for full license text.
3+
4+
using System;
5+
using System.IO;
6+
using HoloCure.Launcher.Base;
7+
using osu.Framework;
8+
using osu.Framework.Logging;
9+
using Sentry;
10+
using Sentry.Protocol;
11+
12+
namespace HoloCure.Launcher.Desktop.Utils;
13+
14+
public class SentryLogger : IDisposable
15+
{
16+
private LauncherBase game;
17+
private readonly IDisposable? sentrySession;
18+
19+
public SentryLogger(LauncherBase game)
20+
{
21+
this.game = game;
22+
sentrySession = SentrySdk.Init(options =>
23+
{
24+
if (game.BuildInfo.IsDeployedBuild) options.Dsn = "https://265de30d478a413db48e350e3d36a515@sentry.tomat.dev/3";
25+
options.AutoSessionTracking = true;
26+
options.IsEnvironmentUser = false; // ensure user isn't tracked; try to scrub away more information if any exists?
27+
options.Release = $"{LauncherBase.GAME_NAME}@{game.BuildInfo.AssemblyVersion.ToString()}-{game.BuildInfo.ReleaseChannel}";
28+
});
29+
30+
Logger.NewEntry += processLogEntry;
31+
}
32+
33+
private void processLogEntry(LogEntry entry)
34+
{
35+
if (entry.Level < LogLevel.Verbose) return;
36+
37+
if (entry.Exception is { } ex)
38+
{
39+
if (!shouldSubmitException(ex)) return;
40+
41+
// framework does some weird exception redirection which means sentry does not see unhandled exceptions using its automatic methods.
42+
// but all unhandled exceptions still arrive via this pathway. we just need to mark them as unhandled for tagging purposes.
43+
// easiest solution is to check the message matches what the framework logs this as.
44+
// see https://github.com/ppy/osu-framework/blob/f932f8df053f0011d755c95ad9a2ed61b94d136b/osu.Framework/Platform/GameHost.cs#L336
45+
bool wasUnhandled = entry.Message == @"An unhandled error has occurred.";
46+
bool wasUnobserved = entry.Message == @"An unobserved error has occurred.";
47+
48+
// see https://github.com/getsentry/sentry-dotnet/blob/c6a660b1affc894441c63df2695a995701671744/src/Sentry/Integrations/TaskUnobservedTaskExceptionIntegration.cs#L39
49+
if (wasUnobserved) ex.Data[Mechanism.MechanismKey] = @"UnobservedTaskException";
50+
51+
// see https://github.com/getsentry/sentry-dotnet/blob/main/src/Sentry/Integrations/AppDomainUnhandledExceptionIntegration.cs#L38-L39
52+
if (wasUnhandled) ex.Data[Mechanism.MechanismKey] = @"AppDomain.UnhandledException";
53+
54+
ex.Data[Mechanism.HandledKey] = !wasUnhandled;
55+
56+
SentrySdk.CaptureEvent(
57+
new SentryEvent(ex)
58+
{
59+
Message = entry.Message,
60+
Level = getSentryLevel(entry.Level)
61+
},
62+
scope =>
63+
{
64+
// add scope contexts eventually too (running game (if any), etc.)
65+
scope.SetTag(@"os", $"{RuntimeInfo.OS} ({Environment.OSVersion})");
66+
scope.SetTag(@"processor count", Environment.ProcessorCount.ToString());
67+
}
68+
);
69+
}
70+
else
71+
SentrySdk.AddBreadcrumb(entry.Message, entry.Target.ToString(), "navigation", level: getBreadcrumbLevel(entry.Level));
72+
}
73+
74+
private BreadcrumbLevel getBreadcrumbLevel(LogLevel entryLevel) =>
75+
entryLevel switch
76+
{
77+
LogLevel.Debug => BreadcrumbLevel.Debug,
78+
LogLevel.Verbose => BreadcrumbLevel.Info,
79+
LogLevel.Important => BreadcrumbLevel.Warning,
80+
LogLevel.Error => BreadcrumbLevel.Error,
81+
_ => throw new ArgumentOutOfRangeException(nameof(entryLevel), entryLevel, null)
82+
};
83+
84+
private SentryLevel getSentryLevel(LogLevel entryLevel) =>
85+
entryLevel switch
86+
{
87+
LogLevel.Debug => SentryLevel.Debug,
88+
LogLevel.Verbose => SentryLevel.Info,
89+
LogLevel.Important => SentryLevel.Warning,
90+
LogLevel.Error => SentryLevel.Error,
91+
_ => throw new ArgumentOutOfRangeException(nameof(entryLevel), entryLevel, null)
92+
};
93+
94+
private bool shouldSubmitException(Exception exception)
95+
{
96+
switch (exception)
97+
{
98+
case IOException ioe:
99+
// disk full exceptions, see https://stackoverflow.com/a/9294382
100+
const int hr_error_handle_disk_full = unchecked((int)0x80070027);
101+
const int hr_error_disk_full = unchecked((int)0x80070070);
102+
103+
if (ioe.HResult == hr_error_handle_disk_full || ioe.HResult == hr_error_disk_full) return false;
104+
105+
break;
106+
}
107+
108+
return true;
109+
}
110+
111+
#region IDisposable Impl
112+
113+
~SentryLogger() => Dispose(false);
114+
115+
public void Dispose()
116+
{
117+
Dispose(true);
118+
GC.SuppressFinalize(this);
119+
}
120+
121+
protected virtual void Dispose(bool isDisposing)
122+
{
123+
Logger.NewEntry -= processLogEntry;
124+
sentrySession?.Dispose();
125+
}
126+
127+
#endregion
128+
}

0 commit comments

Comments
 (0)