Skip to content

Commit

Permalink
Game finders overhaul (#2121)
Browse files Browse the repository at this point in the history
* Modified namespace to be file-scoped by default in editorconfig

* Reworked installation finders

* Refactored GameFinders to use GameFinderResult

* Added NotFound/Ok result to GameFinderResult type

* Added GetUniqueNonCombinatoryFlags extension for Enums

---------

Co-authored-by: Measurity <measuring.infinity@gmail.com>
  • Loading branch information
dartasen and Measurity authored Feb 29, 2024
1 parent 171f23b commit 26179d0
Show file tree
Hide file tree
Showing 39 changed files with 827 additions and 358 deletions.
83 changes: 82 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,42 @@ resharper_braces_redundant = false
resharper_keep_existing_attribute_arrangement = true
resharper_wrap_object_and_collection_initializer_style = chop_if_long

[*.{proj, csproj, targets}]
[*.{proj,csproj,props,targets}]
indent_size = 4
insert_final_newline = true
tab_width = 4
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_readonly_field = true:suggestion
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_object_initializer = true:suggestion
dotnet_style_prefer_collection_expression = true:suggestion
dotnet_style_namespace_match_folder = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_simplified_interpolation = true:suggestion
dotnet_style_allow_multiple_blank_lines_experimental = true:silent
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
dotnet_code_quality_unused_parameters = all:suggestion
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_property = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_event = false:silent
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent

[App.config]
indent_size = 4
Expand All @@ -25,6 +58,54 @@ insert_final_newline = true
indent_size = 4
insert_final_newline = true
charset = utf-8
csharp_space_around_binary_operators = before_and_after
csharp_indent_labels = one_less_than_current
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_prefer_switch_expression = true:suggestion
csharp_style_prefer_pattern_matching = true:silent
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_prefer_not_pattern = true:suggestion
csharp_style_prefer_extended_property_pattern = true:suggestion
csharp_prefer_simple_using_statement = true:suggestion
csharp_prefer_braces = true:silent
csharp_style_namespace_declarations = file_scoped:suggestion
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_primary_constructors = true:suggestion
csharp_prefer_static_local_function = true:suggestion
csharp_style_prefer_readonly_struct = true:suggestion
csharp_style_prefer_readonly_struct_member = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_prefer_null_check_over_type_check = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_prefer_local_over_anonymous_function = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_range_operator = true:suggestion
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
csharp_style_prefer_tuple_swap = true:suggestion
csharp_style_prefer_utf8_string_literals = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent
csharp_style_var_for_built_in_types = false:silent
csharp_style_var_when_type_is_apparent = false:silent
csharp_style_var_elsewhere = false:silent
csharp_style_conditional_delegate_call = true:suggestion
csharp_using_directive_placement = outside_namespace:silent

[*.g.cs]
generated_code = true
Expand Down
38 changes: 32 additions & 6 deletions Nitrox.Test/Model/ExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace NitroxModel;
namespace NitroxModel;

[TestClass]
public class ExtensionsTest
Expand Down Expand Up @@ -71,4 +66,35 @@ public void RemoveAllFast_CanRemoveItemsWithExtraParameterInPredicate()
list.RemoveAllFast(3, static (item, length) => item.Length == length);
list.Should().BeEquivalentTo("three", "four");
}

[TestMethod]
public void GetUniqueNonCombinatoryFlags_ShouldReturnUniqueNonCombinatoryFlags()
{
TestEnumFlags.ALL.GetUniqueNonCombinatoryFlags().Should().BeEquivalentTo([TestEnumFlags.A, TestEnumFlags.B, TestEnumFlags.C, TestEnumFlags.D, TestEnumFlags.E, TestEnumFlags.F]);
TestEnumFlags.CDEF.GetUniqueNonCombinatoryFlags().Should().BeEquivalentTo([TestEnumFlags.C, TestEnumFlags.D, TestEnumFlags.E, TestEnumFlags.F]);
TestEnumFlags.E.GetUniqueNonCombinatoryFlags().Should().BeEquivalentTo([TestEnumFlags.E]);
TestEnumFlags.NONE.GetUniqueNonCombinatoryFlags().Should().BeEmpty();
}

[TestMethod]
public void GetUniqueNonCombinatorFlags_ShouldReturnAllUniquesWhenAllBitsSet()
{
((TestEnumFlags)int.MaxValue).GetUniqueNonCombinatoryFlags().Should().BeEquivalentTo([TestEnumFlags.A, TestEnumFlags.B, TestEnumFlags.C, TestEnumFlags.D, TestEnumFlags.E, TestEnumFlags.F]);
}

[Flags]
private enum TestEnumFlags
{
NONE = 0,
F = 1 << 5,
A = 1 << 0,
B = 1 << 1,
C = 1 << 2,
D = 1 << 3,
E = 1 << 4,
AB = A | B,
CD = C | D,
CDEF = CD | E | F,
ALL = AB | CDEF
}
}
6 changes: 0 additions & 6 deletions Nitrox.Test/Server/Serialization/WorldPersistenceTest.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Nitrox.Test;
using Nitrox.Test.Helper.Faker;
using NitroxModel.Core;
Expand Down
Binary file removed NitroxLauncher/Assets/Images/store-icons/pirated-2x.png
Binary file not shown.
15 changes: 9 additions & 6 deletions NitroxLauncher/LauncherLogic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,16 @@ public void Dispose()
{
Application.Current.MainWindow?.Hide();

try
{
nitroxEntryPatch.Remove();
}
catch (Exception ex)
if (nitroxEntryPatch?.IsApplied == true)
{
Log.Error(ex, "Error while disposing the launcher");
try
{
nitroxEntryPatch.Remove();
}
catch (Exception ex)
{
Log.Error(ex, "Error while disposing the launcher");
}
}

gameProcess?.Dispose();
Expand Down
3 changes: 2 additions & 1 deletion NitroxLauncher/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using NitroxLauncher.Models.Events;
using NitroxLauncher.Models.Properties;
using NitroxLauncher.Pages;
using NitroxModel;
using NitroxModel.Discovery;
using NitroxModel.Helper;
using NitroxModel.Platforms.OS.Windows;
Expand Down Expand Up @@ -115,7 +116,7 @@ public MainWindow()
logic.SetTargetedSubnauticaPath(NitroxUser.GamePath)
.ContinueWith(task =>
{
if (GameInstallationFinder.IsSubnauticaDirectory(task.Result))
if (GameInstallationHelper.HasGameExecutable(task.Result, GameInfo.Subnautica))
{
LauncherLogic.Instance.NavigateTo<LaunchGamePage>();
}
Expand Down
5 changes: 2 additions & 3 deletions NitroxLauncher/Models/Converters/PlatformToIconConverter.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System;
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Markup;
using NitroxModel.Discovery;
using NitroxModel.Discovery.Models;

namespace NitroxLauncher.Models.Converters
{
Expand All @@ -21,7 +21,6 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
Platform.EPIC => "pack://application:,,,/Assets/Images/store-icons/epic-2x.png",
Platform.STEAM => "pack://application:,,,/Assets/Images/store-icons/steam-2x.png",
Platform.MICROSOFT => "pack://application:,,,/Assets/Images/store-icons/xbox-2x.png",
Platform.PIRATED => "pack://application:,,,/Assets/Images/store-icons/pirated-2x.png",
Platform.DISCORD => "pack://application:,,,/Assets/Images/store-icons/discord-2x.png",
_ => "pack://application:,,,/Assets/Images/store-icons/missing-2x.png",
};
Expand Down
2 changes: 1 addition & 1 deletion NitroxLauncher/Pages/LaunchGamePage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System.Windows;
using NitroxLauncher.Models;
using NitroxModel;
using NitroxModel.Discovery;
using NitroxModel.Discovery.Models;
using NitroxModel.Helper;

namespace NitroxLauncher.Pages
Expand Down
6 changes: 4 additions & 2 deletions NitroxLauncher/Pages/OptionPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System.ComponentModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Windows;
using Microsoft.WindowsAPICodePack.Dialogs;
using NitroxLauncher.Models;
using NitroxModel;
using NitroxModel.Discovery;
using NitroxModel.Discovery.Models;
using NitroxModel.Helper;
using NitroxServer.Serialization.World;

Expand Down Expand Up @@ -60,7 +62,7 @@ private async void OnChangePath_Click(object sender, RoutedEventArgs e)
selectedDirectory = Path.GetFullPath(dialog.FileName);
}

if (!GameInstallationFinder.IsSubnauticaDirectory(selectedDirectory))
if (!GameInstallationHelper.HasGameExecutable(selectedDirectory, GameInfo.Subnautica))
{
LauncherNotifier.Error("Invalid subnautica directory");
return;
Expand Down
2 changes: 1 addition & 1 deletion NitroxModel/Discovery/GameInstallData.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Linq;
using System.Text;
Expand Down
80 changes: 38 additions & 42 deletions NitroxModel/Discovery/GameInstallationFinder.cs
Original file line number Diff line number Diff line change
@@ -1,59 +1,55 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NitroxModel.Discovery.InstallationFinders;
using NitroxModel.Discovery.InstallationFinders.Core;
using NitroxModel.Discovery.Models;

namespace NitroxModel.Discovery
namespace NitroxModel.Discovery;

/// <summary>
/// Main game installation finder that will use all available methods of detection to find the game installation directory
/// </summary>
public sealed class GameInstallationFinder
{
private static readonly Lazy<GameInstallationFinder> instance = new(() => new GameInstallationFinder());
public static GameInstallationFinder Instance => instance.Value;

private readonly Dictionary<GameLibraries, IGameFinder> finders = new()
{
{ GameLibraries.STEAM, new SteamFinder() },
{ GameLibraries.EPIC, new EpicGamesFinder() },
{ GameLibraries.DISCORD, new DiscordFinder() },
{ GameLibraries.MICROSOFT, new MicrosoftFinder() },
{ GameLibraries.ENVIRONMENT, new EnvironmentFinder() },
{ GameLibraries.CONFIG, new ConfigFinder() }
};

/// <summary>
/// Main game installation finder that will use all available methods of detection to find the Subnautica installation
/// directory.
/// Searches for the game install directory given its <see cref="GameInfo"/>.
/// </summary>
public class GameInstallationFinder : IFindGameInstallation
/// <param name="gameInfo">Info object of a game.</param>
/// <param name="gameLibraries">Known game libraries to search through</param>
/// <returns>Positive and negative results from the search</returns>
public IEnumerable<GameFinderResult> FindGame(GameInfo gameInfo, GameLibraries gameLibraries = GameLibraries.ALL)
{
private static readonly Lazy<GameInstallationFinder> instance = new(() => new GameInstallationFinder());
public static GameInstallationFinder Instance => instance.Value;

/// <summary>
/// The order of these finders is VERY important. Only change if you know what you're doing.
/// </summary>
private readonly IFindGameInstallation[] finders = {
new GameInCurrentDirectoryFinder(),
new ConfigGameFinder(),
new SteamGameRegistryFinder(),
new EpicGamesInstallationFinder(),
new DiscordGameFinder(),
new EnvironmentGameFinder()
};

public string FindGame(IList<string> errors = null)
if (gameInfo is null || !gameLibraries.IsDefined())
{
errors ??= new List<string>();
foreach (IFindGameInstallation finder in finders)
{
string path = finder.FindGame(errors);
if (path == null)
{
continue;
}

errors.Clear();
return Path.GetFullPath(path);
}

return null;
yield break;
}

public static bool IsSubnauticaDirectory(string directory)
foreach (GameLibraries wantedFinder in gameLibraries.GetUniqueNonCombinatoryFlags())
{
if (string.IsNullOrWhiteSpace(directory))
if (!finders.TryGetValue(wantedFinder, out IGameFinder finder))
{
return false;
continue;
}

return Directory.EnumerateFiles(directory, "*.exe")
.Any(file => Path.GetFileName(file)?.Equals("subnautica.exe", StringComparison.OrdinalIgnoreCase) ?? false);
GameFinderResult result = finder.FindGame(gameInfo);
if (!result.IsOk && string.IsNullOrWhiteSpace(result.ErrorMessage))
{
result = result with { ErrorMessage = $"It appears you don't have {gameInfo.Name} installed" };
}
yield return result;
}
}
}
37 changes: 37 additions & 0 deletions NitroxModel/Discovery/GameInstallationHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.IO;

namespace NitroxModel.Discovery;

public static class GameInstallationHelper
{
public static bool HasGameExecutable(string path, GameInfo gameInfo)
{
if (string.IsNullOrWhiteSpace(path))
{
return false;
}
return File.Exists(Path.Combine(path, gameInfo.ExeName));
}

public static bool HasValidGameFolder(string path, GameInfo gameInfo)
{
if (string.IsNullOrWhiteSpace(path))
{
return false;
}
if (!Directory.Exists(path))
{
return false;
}
if (!HasGameExecutable(path, gameInfo))
{
return false;
}
if (!Directory.Exists(Path.Combine(path, gameInfo.DataFolder, "Managed")))
{
return false;
}

return true;
}
}
Loading

0 comments on commit 26179d0

Please sign in to comment.