diff --git a/NexusMods.App.sln b/NexusMods.App.sln index 97afbf89a..e5cf3acc1 100644 --- a/NexusMods.App.sln +++ b/NexusMods.App.sln @@ -253,8 +253,11 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Collections", "src\NexusMods.Collections\NexusMods.Collections.csproj", "{A9FD538A-E101-4AEA-A98E-35DCED950AEE}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Collections.Tests", "tests\NexusMods.Collections.Tests\NexusMods.Collections.Tests.csproj", "{8C817874-7A88-450E-B216-851A1B03684C}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Abstractions.Media", "src\Abstractions\NexusMods.Abstractions.Media\NexusMods.Abstractions.Media.csproj", "{5CB6D02C-07D0-4C0D-BF5C-4E2E958A0612}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Games.Larian", "src\Games\NexusMods.Games.Larian\NexusMods.Games.Larian.csproj", "{2A35EBB5-1CA6-4F5D-8CE8-352146C82C28}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -661,6 +664,10 @@ Global {5CB6D02C-07D0-4C0D-BF5C-4E2E958A0612}.Debug|Any CPU.Build.0 = Debug|Any CPU {5CB6D02C-07D0-4C0D-BF5C-4E2E958A0612}.Release|Any CPU.ActiveCfg = Release|Any CPU {5CB6D02C-07D0-4C0D-BF5C-4E2E958A0612}.Release|Any CPU.Build.0 = Release|Any CPU + {2A35EBB5-1CA6-4F5D-8CE8-352146C82C28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A35EBB5-1CA6-4F5D-8CE8-352146C82C28}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A35EBB5-1CA6-4F5D-8CE8-352146C82C28}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A35EBB5-1CA6-4F5D-8CE8-352146C82C28}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -779,6 +786,7 @@ Global {A9FD538A-E101-4AEA-A98E-35DCED950AEE} = {E7BAE287-D505-4D6D-A090-665A64309B2D} {8C817874-7A88-450E-B216-851A1B03684C} = {52AF9D62-7D5B-4AD0-BA12-86F2AA67428B} {5CB6D02C-07D0-4C0D-BF5C-4E2E958A0612} = {0CB73565-1207-4A56-A79F-6A8E9BBD795C} + {2A35EBB5-1CA6-4F5D-8CE8-352146C82C28} = {70D38D24-79AE-4600-8E83-17F3C11BA81F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9F9F8352-34DD-42C0-8564-EE9AF34A3501} diff --git a/src/Games/NexusMods.Games.Larian/BaldursGate3/BaldursGate3.cs b/src/Games/NexusMods.Games.Larian/BaldursGate3/BaldursGate3.cs new file mode 100644 index 000000000..351ca7346 --- /dev/null +++ b/src/Games/NexusMods.Games.Larian/BaldursGate3/BaldursGate3.cs @@ -0,0 +1,70 @@ +using Microsoft.Extensions.DependencyInjection; +using NexusMods.Abstractions.GameLocators; +using NexusMods.Abstractions.GameLocators.GameCapabilities; +using NexusMods.Abstractions.GameLocators.Stores.GOG; +using NexusMods.Abstractions.GameLocators.Stores.Steam; +using NexusMods.Abstractions.Games; +using NexusMods.Abstractions.Games.DTO; +using NexusMods.Abstractions.IO; +using NexusMods.Abstractions.IO.StreamFactories; +using NexusMods.Abstractions.Loadouts.Synchronizers; +using NexusMods.Paths; + +namespace NexusMods.Games.Larian.BaldursGate3; + +public class BaldursGate3 : AGame, ISteamGame, IGogGame +{ + private readonly IServiceProvider _serviceProvider; + private readonly IOSInformation _osInformation; + public override string Name => "Baldur's Gate 3"; + + public IEnumerable SteamIds => [1086940u]; + public IEnumerable GogIds => [1456460669]; + + public static GameDomain GameDomain => GameDomain.From("baldursgate3"); + public override GameDomain Domain => GameDomain; + + public BaldursGate3(IServiceProvider provider) : base(provider) + { + _serviceProvider = provider; + _osInformation = provider.GetRequiredService(); + } + + public override GamePath GetPrimaryFile(GameStore store) + { + if (_osInformation.IsOSX) + return new GamePath(LocationId.Game, "Contents/MacOS/Baldur's Gate 3"); + return new GamePath(LocationId.Game, "bin/bg3.exe"); + } + + protected override IReadOnlyDictionary GetLocations(IFileSystem fileSystem, GameLocatorResult installation) + { + var result = new Dictionary() + { + { LocationId.Game, installation.Path }, + { LocationId.From("Mods"), fileSystem.GetKnownPath(KnownPath.HomeDirectory).Combine("Larian Studios/Baldur's Gate 3/Mods") }, + { LocationId.From("PlayerProfiles"), fileSystem.GetKnownPath(KnownPath.HomeDirectory).Combine("Larian Studios/Baldur's Gate 3/PlayerProfiles/Public") }, + { LocationId.From("ScriptExtenderConfig"), fileSystem.GetKnownPath(KnownPath.HomeDirectory).Combine("Larian Studios/Baldur's Gate 3/ScriptExtender") }, + }; + return result; + } + + /// + public override List GetInstallDestinations(IReadOnlyDictionary locations) + { + // TODO: fill this in for Generic installer + return []; + } + + protected override ILoadoutSynchronizer MakeSynchronizer(IServiceProvider provider) + { + return new BaldursGate3Synchronizer(provider); + } + + // TODO: We are using Icon for both Spine and GameWidget and GameImage is unused. We should use GameImage for the GameWidget, but need to update all the games to have better images. + public override IStreamFactory Icon => + new EmbededResourceStreamFactory("NexusMods.Games.Larian.Resources.BaldursGate3.icon.png"); + + public override IStreamFactory GameImage => + new EmbededResourceStreamFactory("NexusMods.Games.Larian.Resources.BaldursGate3.icon.png"); +} diff --git a/src/Games/NexusMods.Games.Larian/BaldursGate3/BaldursGate3Settings.cs b/src/Games/NexusMods.Games.Larian/BaldursGate3/BaldursGate3Settings.cs new file mode 100644 index 000000000..f534b4eee --- /dev/null +++ b/src/Games/NexusMods.Games.Larian/BaldursGate3/BaldursGate3Settings.cs @@ -0,0 +1,27 @@ +using NexusMods.Abstractions.Settings; + +namespace NexusMods.Games.Larian.BaldursGate3; + +public class BaldursGate3Settings : ISettings +{ + + /// + /// If true, the contents of the game folder will be backed up. If the game updates + /// the loadout may become invalid. If mods are installed into this folder via the app they + /// will still be backed up as needed + /// + public bool DoFullGameBackup { get; set; } = false; + + public static ISettingsBuilder Configure(ISettingsBuilder settingsBuilder) + { + return settingsBuilder.AddToUI(builder => builder + .AddPropertyToUI(x => x.DoFullGameBackup, propertyBuilder => propertyBuilder + .AddToSection(Sections.Experimental) + .WithDisplayName("Full game backup: Baldur's Gate 3") + .WithDescription("Backup all game folders, this will greatly increase disk space usage. Should only be changed before managing the game.") + .UseBooleanContainer() + ) + ); + } + +} diff --git a/src/Games/NexusMods.Games.Larian/BaldursGate3/BaldursGate3Synchronizer.cs b/src/Games/NexusMods.Games.Larian/BaldursGate3/BaldursGate3Synchronizer.cs new file mode 100644 index 000000000..7ae1bee6a --- /dev/null +++ b/src/Games/NexusMods.Games.Larian/BaldursGate3/BaldursGate3Synchronizer.cs @@ -0,0 +1,38 @@ +using Microsoft.Extensions.DependencyInjection; +using NexusMods.Abstractions.GameLocators; +using NexusMods.Abstractions.Loadouts.Synchronizers; +using NexusMods.Abstractions.Settings; + +namespace NexusMods.Games.Larian.BaldursGate3; + +public class BaldursGate3Synchronizer : ALoadoutSynchronizer +{ + private readonly BaldursGate3Settings _settings; + + private static GamePath GameFolder => new(LocationId.Game, ""); + private static GamePath PublicPlayerProfiles => new(LocationId.From("PlayerProfiles"), ""); + + private static GamePath ModSettingsFile => new(LocationId.From("PlayerProfiles"), "modsettings.lsx"); + + + public BaldursGate3Synchronizer(IServiceProvider provider) : base(provider) + { + var settingsManager = provider.GetRequiredService(); + _settings = settingsManager.Get(); + } + + public override bool IsIgnoredPath(GamePath path) + { + // Always ignore all PlayerProfile files except the modsettings file. + return path.InFolder(PublicPlayerProfiles) && path.Path != ModSettingsFile.Path; + } + + public override bool IsIgnoredBackupPath(GamePath path) + { + if (_settings.DoFullGameBackup) return false; + + // Optionally ignore all game folder files for size reasons + return path.InFolder(GameFolder) || + (path.InFolder(PublicPlayerProfiles) && path.Path != ModSettingsFile.Path); + } +} diff --git a/src/Games/NexusMods.Games.Larian/BaldursGate3/Services.cs b/src/Games/NexusMods.Games.Larian/BaldursGate3/Services.cs new file mode 100644 index 000000000..94f8761f4 --- /dev/null +++ b/src/Games/NexusMods.Games.Larian/BaldursGate3/Services.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.DependencyInjection; +using NexusMods.Abstractions.Games; +using NexusMods.Abstractions.Settings; + +namespace NexusMods.Games.Larian.BaldursGate3; + +public static class Services +{ + public static IServiceCollection AddBaldursGate3(this IServiceCollection services) + { + services + .AddGame() + .AddSettings(); + + return services; + } +} diff --git a/src/Games/NexusMods.Games.Larian/NexusMods.Games.Larian.csproj b/src/Games/NexusMods.Games.Larian/NexusMods.Games.Larian.csproj new file mode 100644 index 000000000..a87de8aaa --- /dev/null +++ b/src/Games/NexusMods.Games.Larian/NexusMods.Games.Larian.csproj @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/Games/NexusMods.Games.Larian/Resources/BaldursGate3/game_image.jpg b/src/Games/NexusMods.Games.Larian/Resources/BaldursGate3/game_image.jpg new file mode 100644 index 000000000..5df9a241f Binary files /dev/null and b/src/Games/NexusMods.Games.Larian/Resources/BaldursGate3/game_image.jpg differ diff --git a/src/Games/NexusMods.Games.Larian/Resources/BaldursGate3/icon.png b/src/Games/NexusMods.Games.Larian/Resources/BaldursGate3/icon.png new file mode 100644 index 000000000..40b04608d Binary files /dev/null and b/src/Games/NexusMods.Games.Larian/Resources/BaldursGate3/icon.png differ diff --git a/src/NexusMods.App/NexusMods.App.csproj b/src/NexusMods.App/NexusMods.App.csproj index 50e3391b5..d1969868e 100644 --- a/src/NexusMods.App/NexusMods.App.csproj +++ b/src/NexusMods.App/NexusMods.App.csproj @@ -15,6 +15,7 @@ + diff --git a/src/NexusMods.App/Services.cs b/src/NexusMods.App/Services.cs index 40bde7e96..5745bf6d6 100644 --- a/src/NexusMods.App/Services.cs +++ b/src/NexusMods.App/Services.cs @@ -21,6 +21,7 @@ using NexusMods.Games.FOMOD; using NexusMods.Games.FOMOD.UI; using NexusMods.Games.Generic; +using NexusMods.Games.Larian.BaldursGate3; using NexusMods.Games.TestHarness; using NexusMods.Jobs; using NexusMods.Library; @@ -125,6 +126,7 @@ private static IServiceCollection AddSupportedGames(this IServiceCollection serv { if (experimentalSettings is { EnableAllGames: true }) { + services.AddBaldursGate3(); } Games.RedEngine.Services.AddRedEngineGames(services);