diff --git a/dagger.json b/dagger.json
index a24defb..cb24932 100644
--- a/dagger.json
+++ b/dagger.json
@@ -12,5 +12,5 @@
}
],
"source": "dagger",
- "engineVersion": "v0.13.3"
+ "engineVersion": "v0.13.5"
}
diff --git a/src/Dota2Helper.Desktop/Dota2Helper.Desktop.csproj b/src/Dota2Helper.Desktop/Dota2Helper.Desktop.csproj
index afcdc8a..dbfdb35 100644
--- a/src/Dota2Helper.Desktop/Dota2Helper.Desktop.csproj
+++ b/src/Dota2Helper.Desktop/Dota2Helper.Desktop.csproj
@@ -33,5 +33,6 @@
Always
+
diff --git a/src/Dota2Helper.Desktop/gamestate_integration_timers.cfg b/src/Dota2Helper.Desktop/gamestate_integration_timers.cfg
new file mode 100644
index 0000000..31ec37a
--- /dev/null
+++ b/src/Dota2Helper.Desktop/gamestate_integration_timers.cfg
@@ -0,0 +1,17 @@
+"Dota 2 Integration Configuration"
+{
+ "uri" "http://localhost:4001/"
+ "timeout" "5.0"
+ "buffer" "0.1"
+ "throttle" "0.1"
+ "heartbeat" "1.0"
+ "data"
+ {
+ "provider" "0"
+ "map" "1"
+ "player" "0"
+ "hero" "0"
+ "abilities" "0"
+ "items" "0"
+ }
+}
\ No newline at end of file
diff --git a/src/Dota2Helper/App.axaml.cs b/src/Dota2Helper/App.axaml.cs
index d16af1d..8657e94 100644
--- a/src/Dota2Helper/App.axaml.cs
+++ b/src/Dota2Helper/App.axaml.cs
@@ -68,6 +68,7 @@ private static IHost CreateHost()
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
builder.Logging.ClearProviders();
builder.Logging.AddDebug();
diff --git a/src/Dota2Helper/Core/Gsi/GameState.cs b/src/Dota2Helper/Core/Gsi/GameState.cs
index 44044ab..1b8a23d 100644
--- a/src/Dota2Helper/Core/Gsi/GameState.cs
+++ b/src/Dota2Helper/Core/Gsi/GameState.cs
@@ -7,11 +7,11 @@ public class GameState
{
[JsonIgnore]
// ReSharper disable once InconsistentNaming
- private const string DOTA_GAMERULES_STATE_PRE_GAME = nameof(DOTA_GAMERULES_STATE_PRE_GAME);
+ const string DOTA_GAMERULES_STATE_PRE_GAME = nameof(DOTA_GAMERULES_STATE_PRE_GAME);
[JsonIgnore]
// ReSharper disable once InconsistentNaming
- private const string DOTA_GAMERULES_STATE_GAME_IN_PROGRESS = nameof(DOTA_GAMERULES_STATE_GAME_IN_PROGRESS);
+ const string DOTA_GAMERULES_STATE_GAME_IN_PROGRESS = nameof(DOTA_GAMERULES_STATE_GAME_IN_PROGRESS);
[JsonPropertyName("map")]
public Map? Map { get; set; }
diff --git a/src/Dota2Helper/Core/Gsi/GameStateHolder.cs b/src/Dota2Helper/Core/Gsi/GameStateHolder.cs
index af37f9f..0c3b2bc 100644
--- a/src/Dota2Helper/Core/Gsi/GameStateHolder.cs
+++ b/src/Dota2Helper/Core/Gsi/GameStateHolder.cs
@@ -4,10 +4,9 @@ namespace Dota2Helper.Core.Gsi;
public class GameStateHolder
{
-
- private static readonly object Lock = new();
-
- private GameState? _state;
+ static readonly object Lock = new();
+
+ GameState? _state;
public GameState? State
{
diff --git a/src/Dota2Helper/Core/Gsi/SteamLibraryService.cs b/src/Dota2Helper/Core/Gsi/SteamLibraryService.cs
new file mode 100644
index 0000000..0b9c5db
--- /dev/null
+++ b/src/Dota2Helper/Core/Gsi/SteamLibraryService.cs
@@ -0,0 +1,127 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+using System.Text.RegularExpressions;
+using Microsoft.Extensions.Logging;
+using Microsoft.Win32;
+
+namespace Dota2Helper.Core.Gsi;
+
+public partial class SteamLibraryService(ILogger logger)
+{
+ const string ConfigFile = "gamestate_integration_timers.cfg";
+
+ string? FindGameStateIntegrationPath()
+ {
+ var dota2FolderPath = FindDota2InstallationPath();
+
+ if (dota2FolderPath == null) return null;
+
+ var gameStateIntegrationPath = Path.Combine(dota2FolderPath, "game", "dota", "cfg", "gamestate_integration");
+ return Directory.Exists(gameStateIntegrationPath) ? gameStateIntegrationPath : null;
+ }
+
+ public bool IsIntegrationInstalled()
+ {
+ var gameStateIntegrationPath = FindGameStateIntegrationPath();
+
+ if (gameStateIntegrationPath == null) return false;
+
+ var configFileFullPath = Path.Combine(gameStateIntegrationPath, ConfigFile);
+ return File.Exists(configFileFullPath);
+ }
+
+ string? FindDota2InstallationPath()
+ {
+ var libraryFoldersPath = GetLibraryFoldersPath();
+
+ if (string.IsNullOrEmpty(libraryFoldersPath) || !File.Exists(libraryFoldersPath))
+ {
+ logger.LogError("libraryfolders.vdf not found");
+ return null;
+ }
+
+ var steamInstallPath = GetSteamInstallationPath();
+
+ if (string.IsNullOrEmpty(steamInstallPath))
+ {
+ logger.LogError("Steam installation path not found");
+ return null;
+ }
+
+ var steamLibraryPaths = GetSteamLibraryPaths(libraryFoldersPath);
+
+ foreach (var steamLibraryPath in steamLibraryPaths)
+ {
+ var dota2FolderPath = Path.Combine(steamLibraryPath, "steamapps", "common", "dota 2 beta");
+
+ if (Directory.Exists(dota2FolderPath)) return dota2FolderPath;
+ }
+
+ return null;
+ }
+
+ string? GetSteamInstallationPath() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? GetWindowsInstall() :
+ RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? GetMacInstall() : null;
+
+ string? GetLibraryFoldersPath()
+ {
+ var steamInstallPath = GetSteamInstallationPath();
+ return steamInstallPath == null ? null : Path.Combine(steamInstallPath, "steamapps", "libraryfolders.vdf");
+ }
+
+ [SupportedOSPlatform("windows")]
+ string? GetWindowsInstall()
+ {
+ const string steamRegistryKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Valve\Steam";
+ const string steamRegistryValue = "InstallPath";
+
+ try
+ {
+ var installPath = Registry.GetValue(steamRegistryKey, steamRegistryValue, null);
+
+ if (installPath != null) return installPath.ToString();
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Failed to read Steam installation path from registry");
+ }
+
+ return null;
+ }
+
+ [SupportedOSPlatform("macos")]
+ string? GetMacInstall() => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support", "Steam");
+
+ string[] GetSteamLibraryPaths(string libraryFoldersVdfPath)
+ {
+ var pathRegex = LibraryLocations();
+ var fileContent = File.ReadAllText(libraryFoldersVdfPath);
+ var matches = pathRegex.Matches(fileContent);
+ var libraryPaths = matches.Select(m => m.Groups[1].Value.Replace("\\\\", "\\")).ToList();
+ return libraryPaths.Where(Directory.Exists).ToArray();
+ }
+
+ public void InstallIntegration()
+ {
+ var gameStateIntegrationPath = FindGameStateIntegrationPath();
+
+ if (gameStateIntegrationPath == null)
+ {
+ logger.LogError("gamestate_integration folder not found");
+ return;
+ }
+
+ var configFileFullPath = Path.Combine(gameStateIntegrationPath, ConfigFile);
+ var configFileSourcePath = Path.Combine(Directory.GetCurrentDirectory(), ConfigFile);
+
+ if (File.Exists(configFileFullPath)) File.Delete(configFileFullPath);
+
+ File.Copy(configFileSourcePath, configFileFullPath);
+ }
+
+ [GeneratedRegex(@"\s*\""path\""\s*\""(.+?)\""", RegexOptions.Compiled)]
+ private static partial Regex LibraryLocations();
+}
\ No newline at end of file
diff --git a/src/Dota2Helper/Dota2Helper.csproj b/src/Dota2Helper/Dota2Helper.csproj
index 3c2a07c..61fcc73 100644
--- a/src/Dota2Helper/Dota2Helper.csproj
+++ b/src/Dota2Helper/Dota2Helper.csproj
@@ -8,7 +8,7 @@
-
+
diff --git a/src/Dota2Helper/ViewModels/SettingsViewModel.cs b/src/Dota2Helper/ViewModels/SettingsViewModel.cs
index 7c779ee..d8ffb74 100644
--- a/src/Dota2Helper/ViewModels/SettingsViewModel.cs
+++ b/src/Dota2Helper/ViewModels/SettingsViewModel.cs
@@ -7,6 +7,7 @@
using Avalonia.Styling;
using Dota2Helper.Core.Audio;
using Dota2Helper.Core.Configuration;
+using Dota2Helper.Core.Gsi;
using Dota2Helper.Core.Timers;
using DynamicData.Binding;
using ReactiveUI;
@@ -15,10 +16,10 @@ namespace Dota2Helper.ViewModels;
public class SettingsViewModel : ViewModelBase
{
- static readonly object WriterLock = new();
-
+ readonly static object WriterLock = new();
+ readonly SteamLibraryService _steamLibraryService;
readonly AudioPlayer _audioPlayer;
public DotaTimers Timers { get; }
@@ -34,8 +35,9 @@ public double Volume
get => _audioPlayer.Volume;
}
- public SettingsViewModel(AudioPlayer audioPlayer, DotaTimers timers)
+ public SettingsViewModel(SteamLibraryService steamLibraryService, AudioPlayer audioPlayer, DotaTimers timers)
{
+ _steamLibraryService = steamLibraryService;
_audioPlayer = audioPlayer;
Timers = timers;
@@ -60,6 +62,14 @@ public SettingsViewModel(AudioPlayer audioPlayer, DotaTimers timers)
nameof(DotaTimer.Reminder)
];
+ public void Install()
+ {
+ _steamLibraryService.InstallIntegration();
+ this.RaisePropertyChanged(nameof(IsIntegrated));
+ }
+
+ public bool IsIntegrated => _steamLibraryService.IsIntegrationInstalled();
+
public void ToggleTheme()
{
var toggle = Application.Current!.RequestedThemeVariant switch
diff --git a/src/Dota2Helper/ViewModels/WindowExtensions.cs b/src/Dota2Helper/ViewModels/WindowExtensions.cs
index 379d203..b002930 100644
--- a/src/Dota2Helper/ViewModels/WindowExtensions.cs
+++ b/src/Dota2Helper/ViewModels/WindowExtensions.cs
@@ -35,7 +35,6 @@ internal static void HideMinimizeAndMaximizeButtons(this Window window)
SetWindowLong(hwnd, GWL_STYLE, (currentStyle & ~WS_MAXIMIZEBOX & ~WS_MINIMIZEBOX));
SetWindowLong(hwnd, GWL_EXSTYLE, (currentExStyle | WS_EX_DLGMODALFRAME));
-
SetWindowPos(hwnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED);
}
}
diff --git a/src/Dota2Helper/Views/SettingsView.axaml b/src/Dota2Helper/Views/SettingsView.axaml
index 67e2432..b06c0a7 100644
--- a/src/Dota2Helper/Views/SettingsView.axaml
+++ b/src/Dota2Helper/Views/SettingsView.axaml
@@ -23,42 +23,62 @@
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
-
+
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file