Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added initial framework for FalloutNV #2127

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions NexusMods.App.sln
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Abstractions.Reso
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Abstractions.Resources.Resilience", "src\Abstractions\NexusMods.Abstractions.Resources.Resilience\NexusMods.Abstractions.Resources.Resilience.csproj", "{04219A58-C99C-4C3B-A477-5E4B29D1F275}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Games.Obsidian", "src\Games\NexusMods.Games.Obsidian\NexusMods.Games.Obsidian.csproj", "{44AF0AAE-3924-4A2B-B85E-D9DE0F8B7779}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -716,6 +718,10 @@ Global
{04219A58-C99C-4C3B-A477-5E4B29D1F275}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04219A58-C99C-4C3B-A477-5E4B29D1F275}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04219A58-C99C-4C3B-A477-5E4B29D1F275}.Release|Any CPU.Build.0 = Release|Any CPU
{44AF0AAE-3924-4A2B-B85E-D9DE0F8B7779}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{44AF0AAE-3924-4A2B-B85E-D9DE0F8B7779}.Debug|Any CPU.Build.0 = Debug|Any CPU
{44AF0AAE-3924-4A2B-B85E-D9DE0F8B7779}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44AF0AAE-3924-4A2B-B85E-D9DE0F8B7779}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -843,6 +849,7 @@ Global
{BE8C17C4-E3B0-4D07-8CD0-0D15C3CCA9D5} = {0CB73565-1207-4A56-A79F-6A8E9BBD795C}
{D3BA5B5A-668A-443B-872C-3116CBB0BC0D} = {0CB73565-1207-4A56-A79F-6A8E9BBD795C}
{04219A58-C99C-4C3B-A477-5E4B29D1F275} = {0CB73565-1207-4A56-A79F-6A8E9BBD795C}
{44AF0AAE-3924-4A2B-B85E-D9DE0F8B7779} = {70D38D24-79AE-4600-8E83-17F3C11BA81F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9F9F8352-34DD-42C0-8564-EE9AF34A3501}
Expand Down
31 changes: 31 additions & 0 deletions src/Games/NexusMods.Games.Obsidian/FalloutNewVegas/Diagnostics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using JetBrains.Annotations;
using NexusMods.Abstractions.Diagnostics;
using NexusMods.Abstractions.Diagnostics.References;
using NexusMods.Abstractions.Diagnostics.Values;
using NexusMods.Abstractions.GameLocators;
using NexusMods.Generators.Diagnostics;
using NexusMods.Paths;

namespace NexusMods.Games.Obsidian.FalloutNewVegas;

public static partial class Diagnostics
{
public const string Source = "NexusMods.Games.Obsidian.FalloutNewVegas";

[DiagnosticTemplate]
[UsedImplicitly]
internal static IDiagnosticTemplate MissingNVSE = DiagnosticTemplateBuilder
.Start()
.WithId(new DiagnosticId(Source, number: 2))
.WithTitle("Missing NVSE")
.WithSeverity(DiagnosticSeverity.Suggestion)
.WithSummary("NVSE is not installed")
.WithDetails("""
The NVSE (New Vegas Script Extender) may be required for mods to function properly, but it is not installed. You can download the latest
version of xNVSE from the Nexus website {xNVSELink}.
""")
.WithMessageData(messageBuilder => messageBuilder
.AddValue<NamedLink>("xNVSELink")
)
.Finish();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using NexusMods.Abstractions.Diagnostics;
using NexusMods.Abstractions.Diagnostics.Emitters;
using NexusMods.Abstractions.Diagnostics.Values;
using NexusMods.Abstractions.Loadouts;
namespace NexusMods.Games.Obsidian.FalloutNewVegas.Emitters;

public class MissingNVSEEmitter : ILoadoutDiagnosticEmitter
{
public async IAsyncEnumerable<Diagnostic> Diagnose(Loadout.ReadOnly loadout, CancellationToken cancellationToken)
{
var install = loadout.InstallationInstance;
var locations = install.LocationsRegister;
var nvsePath = locations.GetResolvedPath(FalloutNewVegasConstants.NVSEPath);
if (nvsePath.FileExists)
yield break;

yield return Diagnostics.CreateMissingNVSE(new NamedLink("xNVSE", new Uri("https://www.nexusmods.com/newvegas/mods/67883")));
}
}
115 changes: 115 additions & 0 deletions src/Games/NexusMods.Games.Obsidian/FalloutNewVegas/FalloutNewVegas.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using NexusMods.Abstractions.Diagnostics.Emitters;
using NexusMods.Abstractions.GameLocators;
using NexusMods.Abstractions.GameLocators.GameCapabilities;
using NexusMods.Abstractions.GameLocators.Stores.EGS;
using NexusMods.Abstractions.GameLocators.Stores.GOG;
using NexusMods.Abstractions.GameLocators.Stores.Steam;
using NexusMods.Abstractions.GameLocators.Stores.Xbox;
using NexusMods.Abstractions.Games;
using NexusMods.Abstractions.Games.DTO;
using NexusMods.Abstractions.IO;
using NexusMods.Abstractions.IO.StreamFactories;
using NexusMods.Abstractions.Library.Installers;
using NexusMods.Abstractions.Loadouts.Synchronizers;
using NexusMods.Abstractions.NexusWebApi.Types.V2;
using NexusMods.Games.FOMOD;
using NexusMods.Games.Generic.Installers;
using NexusMods.Games.Obsidian.FalloutNewVegas.Emitters;
using NexusMods.Games.Obsidian.FalloutNewVegas.Installers;
using NexusMods.Paths;
using NexusMods.Paths.Utilities;

// The argument could be made that the package should be Bethesda not Obsidian... todo someone confirm preferred package name
namespace NexusMods.Games.Obsidian.FalloutNewVegas;

public class FalloutNewVegas : AGame, ISteamGame, IGogGame, IXboxGame, IEpicGame
{
private readonly IServiceProvider _serviceProvider;
private readonly IOSInformation _osInformation;

public FalloutNewVegas(IServiceProvider provider, IServiceProvider serviceProvider, IOSInformation osInformation) : base(provider)
{
_serviceProvider = serviceProvider;
_osInformation = osInformation;
}

private static readonly string _name = "Fallout New Vegas";

public static string GameName => _name; // used to statically reference the game name elsewhere... in case the name changes? idk.
public override string Name => _name;
public override GameId GameId => GameId.From(130);

#region File Information

protected override IReadOnlyDictionary<LocationId, AbsolutePath> GetLocations(IFileSystem fileSystem, GameLocatorResult installation)
{
var result = new Dictionary<LocationId, AbsolutePath>()
{
{ LocationId.Game, installation.Path },
};
return result;
}

public override GamePath GetPrimaryFile(GameStore store)
{
return store.ToString() switch
{
"Epic Games Store" => new GamePath(LocationId.Game, "/Fallout New Vegas English/FalloutNV.exe"), // todo going to need this to handle the language... somehow?
"Xbox Game Pass" => new GamePath(LocationId.Game, "/Fallout New Vegas English/FalloutNV.exe"), // todo going to need this to handle the language... somehow?
_ => new GamePath(LocationId.Game, "FalloutNV.exe"),
};
}

public override List<IModInstallDestination> GetInstallDestinations(IReadOnlyDictionary<LocationId, AbsolutePath> locations) => ModInstallDestinationHelpers.GetCommonLocations(locations);

#endregion

#region Game IDs

public IEnumerable<uint> SteamIds => new List<uint> { 22380u };
public IEnumerable<long> GogIds => new List<long> { 1207658921 }; //todo need correct ID. I don't own this.
public IEnumerable<string> XboxIds => new List<string> { "BethesdaSoftworks.FalloutNewVegas" };
public IEnumerable<string> EpicCatalogItemId => new List<string> { "dabb52e328834da7bbe99691e374cb84" };
#endregion

#region Images

public override IStreamFactory GameImage => new EmbededResourceStreamFactory<FalloutNewVegas>("NexusMods.Games.Obsidian.Resources.FalloutNewVegas.game_image.jpg");
public override IStreamFactory Icon => new EmbededResourceStreamFactory<FalloutNewVegas>("NexusMods.Games.Obsidian.Resources.FalloutNewVegas.icon.jpg");

#endregion


public override ILibraryItemInstaller[] LibraryItemInstallers =>
[
new NVSEInstaller(_serviceProvider),
new GenericPatternMatchInstaller(_serviceProvider)
{
InstallFolderTargets =
[
new InstallFolderTarget
{
DestinationGamePath = new GamePath(LocationId.Game, "Data"),
KnownSourceFolderNames = ["Data"],
Names = ["meshes", "textures", "sound", "nvse", "music", "video", "menus", "shaders"],
FileExtensionsToDiscard =
[
KnownExtensions.Txt, KnownExtensions.Md, KnownExtensions.Pdf, KnownExtensions.Png,
KnownExtensions.Json, new Extension(".lnk"),
],
},
],
},
FomodXmlInstaller.Create(_serviceProvider, new GamePath(LocationId.Game, "Data")),
];

public override IDiagnosticEmitter[] DiagnosticEmitters =>
[
new MissingNVSEEmitter(),
];

protected override ILoadoutSynchronizer MakeSynchronizer(IServiceProvider provider)
{
return new FalloutNewVegasSynchronizer(provider);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using NexusMods.Abstractions.GameLocators;
using NexusMods.Paths;
using NexusMods.Paths.Extensions;

namespace NexusMods.Games.Obsidian.FalloutNewVegas;

internal class FalloutNewVegasConstants
{
internal static readonly GamePath NVSEPath = new(LocationId.Game, "nvse_loader.exe");
public static readonly RelativePath DataFolder = "Data".ToRelativePath();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using NexusMods.Abstractions.Settings;

namespace NexusMods.Games.Obsidian.FalloutNewVegas;

public class FalloutNewVegasSettings : ISettings
{
/// <summary>
/// If true, the contents of the Content folder will not 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
/// </summary>
public bool DoFullGameBackup { get; set; } = false;


public static ISettingsBuilder Configure(ISettingsBuilder settingsBuilder)
{
return settingsBuilder.AddToUI<FalloutNewVegasSettings>(builder => builder
.AddPropertyToUI(x => x.DoFullGameBackup, propertyBuilder => propertyBuilder
.AddToSection(Sections.Experimental)
.WithDisplayName($"Full game backup: {FalloutNewVegas.GameName}")
.WithDescription("Backup all game folders, including the Content folder. This experimental setting is intended for developers testing the upcoming restore feature. Please note that this will increase disk space usage.")
.UseBooleanContainer()
)
);

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.Extensions.DependencyInjection;
using NexusMods.Abstractions.GameLocators;
using NexusMods.Abstractions.Loadouts.Synchronizers;
using NexusMods.Abstractions.Settings;

namespace NexusMods.Games.Obsidian.FalloutNewVegas;

public class FalloutNewVegasSynchronizer : ALoadoutSynchronizer
{
private FalloutNewVegasSettings _settings;


protected internal FalloutNewVegasSynchronizer(IServiceProvider provider) : base(provider)
{
var settingsManager = provider.GetRequiredService<ISettingsManager>();

_settings = settingsManager.Get<FalloutNewVegasSettings>();
settingsManager.GetChanges<FalloutNewVegasSettings>().Subscribe(value => _settings = value);

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NexusMods.Abstractions.GameLocators;
using NexusMods.Abstractions.Library.Installers;
using NexusMods.Abstractions.Library.Models;
using NexusMods.Abstractions.Loadouts;
using NexusMods.MnemonicDB.Abstractions;
using NexusMods.Paths;
using NexusMods.Paths.Trees.Traits;

namespace NexusMods.Games.Obsidian.FalloutNewVegas.Installers;

public class NVSEInstaller(IServiceProvider serviceProvider) : ALibraryArchiveInstaller(serviceProvider, serviceProvider.GetRequiredService<ILogger<NVSEInstaller>>())
{
public override ValueTask<InstallerResult> ExecuteAsync(
LibraryArchive.ReadOnly libraryArchive,
LoadoutItemGroup.New loadoutGroup,
ITransaction transaction,
Loadout.ReadOnly loadout,
CancellationToken cancellationToken)
{
var tree = libraryArchive.GetTree();
var keys = new[] { "nvse_1_4.dll", "nvse_editor_1_4.dll", "nvse_loader.exe", "nvse_steam_loader.dll" };


List<LoadoutFile.New> results = [];
foreach (var fileNode in tree.EnumerateFilesBfs())
{
var relativePath = new RelativePath(fileNode.Value.Item.Path.Name);
if (!keys.Contains(relativePath.Name.ToString()))
{
continue;
};

var loadoutFile = new LoadoutFile.New(transaction, out var id)
{
LoadoutItemWithTargetPath = new LoadoutItemWithTargetPath.New(transaction, id)
{
TargetPath = (loadout.Id, LocationId.Game, relativePath),
LoadoutItem = new LoadoutItem.New(transaction, id)
{
Name = relativePath.Name,
LoadoutId = loadout.Id,
ParentId = loadoutGroup.Id,
},
},
Hash = fileNode.Value.Item.LibraryFile.Value.Hash,
Size = fileNode.Value.Item.LibraryFile.Value.Size,
};
results.Add(loadoutFile);
}

return results.Count > 0
? ValueTask.FromResult<InstallerResult>(new Success())
: ValueTask.FromResult<InstallerResult>(new NotSupported());
}
}
21 changes: 21 additions & 0 deletions src/Games/NexusMods.Games.Obsidian/NexusMods.Games.Obsidian.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\..\Abstractions\NexusMods.Abstractions.Games.Diagnostics\NexusMods.Abstractions.Games.Diagnostics.csproj" />
<ProjectReference Include="..\..\Abstractions\NexusMods.Abstractions.Games\NexusMods.Abstractions.Games.csproj" />
<ProjectReference Include="..\..\NexusMods.App.Generators.Diagnostics\NexusMods.App.Generators.Diagnostics\NexusMods.App.Generators.Diagnostics.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\NexusMods.Games.FOMOD\NexusMods.Games.FOMOD.csproj" />
<ProjectReference Include="..\NexusMods.Games.Generic\NexusMods.Games.Generic.csproj" />
</ItemGroup>

<ItemGroup>
<None Remove="Resources\FalloutNewVegas\icon.jpg" />
<EmbeddedResource Include="Resources\FalloutNewVegas\icon.jpg" />
<None Remove="Resources\FalloutNewVegas\game_image.jpg" />
<EmbeddedResource Include="Resources\FalloutNewVegas\game_image.jpg" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="CliWrap" />
<PackageReference Include="NexusMods.MnemonicDB.SourceGenerator" />
</ItemGroup>
</Project>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions src/Games/NexusMods.Games.Obsidian/Services.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Microsoft.Extensions.DependencyInjection;
using NexusMods.Abstractions.Games;
using NexusMods.Abstractions.Loadouts;
using NexusMods.Abstractions.Settings;

namespace NexusMods.Games.Obsidian;

public static class Services
{
public static IServiceCollection AddObsidianGames(this IServiceCollection services)
{
services.AddGame<FalloutNewVegas.FalloutNewVegas>()
.AddSingleton<ITool, RunGameTool<FalloutNewVegas.FalloutNewVegas>>()
.AddSettings<FalloutNewVegas.FalloutNewVegasSettings>();
return services;
}
}
1 change: 1 addition & 0 deletions src/NexusMods.App/NexusMods.App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<ProjectReference Include="..\Games\NexusMods.Games.FOMOD\NexusMods.Games.FOMOD.csproj" />
<ProjectReference Include="..\Games\NexusMods.Games.Generic\NexusMods.Games.Generic.csproj" />
<ProjectReference Include="..\Games\NexusMods.Games.Larian\NexusMods.Games.Larian.csproj" />
<ProjectReference Include="..\Games\NexusMods.Games.Obsidian\NexusMods.Games.Obsidian.csproj" />
<ProjectReference Include="..\Games\NexusMods.Games.TestHarness\NexusMods.Games.TestHarness.csproj" />
<ProjectReference Include="..\Networking\NexusMods.Networking.Downloaders\NexusMods.Networking.Downloaders.csproj" />
<ProjectReference Include="..\Networking\NexusMods.Networking.HttpDownloader\NexusMods.Networking.HttpDownloader.csproj" />
Expand Down
7 changes: 4 additions & 3 deletions src/NexusMods.App/Services.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,23 +114,24 @@ public static IServiceCollection AddApp(this IServiceCollection services,
.AddDefaultRenderers()
.AddSettingsManager()
.AddSettings<LoggingSettings>();

if (!startupMode.IsAvaloniaDesigner)
services.AddSingleProcess(Mode.Client);
}

return services;
}

private static IServiceCollection AddSupportedGames(this IServiceCollection services, ExperimentalSettings? experimentalSettings)
{
if (experimentalSettings is { EnableAllGames: true })
{
}

Games.RedEngine.Services.AddRedEngineGames(services);
Games.StardewValley.Services.AddStardewValley(services);
Games.Larian.BaldursGate3.Services.AddBaldursGate3(services);
Games.Obsidian.Services.AddObsidianGames(services);
return services;
}
}
Loading