Skip to content

Commit

Permalink
Merge pull request #2120 from Nexus-Mods/collection-install-updates
Browse files Browse the repository at this point in the history
Binary patching and FOMOD Presets
  • Loading branch information
Sewer56 authored Oct 8, 2024
2 parents 59eea7d + f5604e9 commit 13f7267
Show file tree
Hide file tree
Showing 27 changed files with 635 additions and 118 deletions.
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<PackageVersion Include="Avalonia.Labs.Panels" Version="11.1.0" />
<PackageVersion Include="Avalonia.Skia" Version="11.1.0" />
<PackageVersion Include="AvaloniaEdit.TextMate" Version="11.1.0" />
<PackageVersion Include="BsDiff" Version="1.1.0" />
<PackageVersion Include="LinqGen" Version="0.3.1" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="8.9.1" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public Task<Stream> GetFileStream(Hash hash, CancellationToken token = default)
return null!;
}

public Task<byte[]> Load(Hash hash, CancellationToken token = default)
{
return Task.FromResult(Array.Empty<byte>());
}

public HashSet<ulong> GetFileHashes()
{
return [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using NexusMods.Games.FOMOD;

namespace NexusMods.Abstractions.Collections.Json;

Expand All @@ -7,4 +8,7 @@ public class Choices
[JsonPropertyName("type")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public required ChoicesType Type { get; init; }

[JsonPropertyName("options")]
public FomodOption[] Options { get; init; } = [];
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text.Json.Serialization;
using NexusMods.Abstractions.Games.DTO;
using NexusMods.Paths;
using NexusMods.Abstractions.NexusWebApi.Types.V2;

namespace NexusMods.Abstractions.Collections.Json;
Expand Down Expand Up @@ -32,4 +33,12 @@ public class Mod

[JsonPropertyName("choices")]
public Choices? Choices { get; init; }

/// <summary>
/// Patches for files found in the mod, the string is a path to the file inside the mod's downloaded archive
/// and the PatchHash is the CRC32 hash of the file before it's patched. The files patched in this way may
/// be installed later via MD5 hash. If the file appears in the Hashes array.
/// </summary>
[JsonPropertyName("patches")]
public Dictionary<string, PatchHash> Patches { get; init; } = new();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
using TransparentValueObjects;

namespace NexusMods.Abstractions.Collections.Json;

[JsonConverter(typeof(PatchHashJsonConverter))]
[ValueObject<uint>]
public readonly partial struct PatchHash
{

}

public class PatchHashJsonConverter : JsonConverter<PatchHash>
{
public override PatchHash Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return PatchHash.From(uint.Parse(reader.GetString()!, NumberStyles.HexNumber));
}

public override void Write(Utf8JsonWriter writer, PatchHash value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.Value.ToString("X"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
<Import Project="$([MSBuild]::GetPathOfFileAbove('NuGet.Build.props', '$(MSBuildThisFileDirectory)../'))" />

<ItemGroup>
<PackageReference Include="BsDiff" />
<PackageReference Include="NexusMods.MnemonicDB.SourceGenerator" PrivateAssets="all" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<PackageReference Include="TransparentValueObjects" PrivateAssets="all" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Games\NexusMods.Games.FOMOD\NexusMods.Games.FOMOD.csproj" />
<ProjectReference Include="..\NexusMods.Abstractions.Loadouts\NexusMods.Abstractions.Loadouts.csproj" />
<ProjectReference Include="..\NexusMods.Abstractions.NexusModsLibrary\NexusMods.Abstractions.NexusModsLibrary.csproj" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using NexusMods.Hashing.xxHash64;
using NexusMods.Paths;

namespace NexusMods.Abstractions.Collections.Types;

/// <summary>
/// Metadata about a mapping from a MD5 hash to a xxHash64 hash and the size of the file.
/// </summary>
public struct HashMapping
{
/// <summary>
/// The xxHash64 hash of the file.
/// </summary>
public required Hash Hash { get; init; }

/// <summary>
/// The size of the file.
/// </summary>
public required Size Size { get; init; }
}
8 changes: 7 additions & 1 deletion src/Abstractions/NexusMods.Abstractions.IO/IFileStore.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using NexusMods.Hashing.xxHash64;
using System.Buffers;
using NexusMods.Hashing.xxHash64;
using NexusMods.Paths;

namespace NexusMods.Abstractions.IO;
Expand Down Expand Up @@ -75,6 +76,11 @@ public interface IFileStore
/// <returns></returns>
Task<Stream> GetFileStream(Hash hash, CancellationToken token = default);

/// <summary>
/// Load the given file into memory,
/// </summary>
Task<byte[]> Load(Hash hash, CancellationToken token = default);

/// <summary>
/// Retrieves hashes of all files associated with this FileStore.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@
<ItemGroup>
<PackageReference Include="NexusMods.MnemonicDB.Abstractions" />
</ItemGroup>

<ItemGroup>
<PackageVersion Update="NexusMods.MnemonicDB.Abstractions" Version="0.9.86" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public sealed class InstallerDelegates : ICoreDelegates
public IPluginDelegates plugin { get; }

public IUIDelegates ui => UiDelegates;
public readonly UiDelegates UiDelegates;
public UiDelegates UiDelegates;

public InstallerDelegates(
ILoggerFactory loggerFactory,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Diagnostics;
using NexusMods.Abstractions.Activities;
using NexusMods.Abstractions.GuidedInstallers;

namespace NexusMods.Games.FOMOD.CoreDelegates;

/// <summary>
/// A IGuidedInstaller implementation that uses a preset list of steps to make the same choices
/// a user previously made for specific steps.
/// </summary>
public class PresetGuidedInstaller : IGuidedInstaller
{
private readonly FomodOption[] _steps;
private int _currentStep = 0;

public PresetGuidedInstaller(FomodOption[] steps)
{
_steps = steps;
}

public void Dispose()
{
}

public void SetupInstaller(string name)
{
}

public void CleanupInstaller()
{
}

public Task<UserChoice> RequestUserChoice(GuidedInstallationStep installationStep, Percent progress, CancellationToken cancellationToken)
{
var step = _steps[_currentStep];

// This looks gross, but it's fairly simple we map through the two trees matching by name, and it's cleaner than 4 nested loops.
var choices =
from srcGroup in step.groups
from installGroup in installationStep.Groups
where installGroup.Name == srcGroup.name
from srcChoice in srcGroup.choices
from installChoice in installGroup.Options
where installChoice.Name == srcChoice.name
select new SelectedOption(installGroup.Id, installChoice.Id);

_currentStep++;
return Task.FromResult(new UserChoice(new UserChoice.GoToNextStep(choices.ToArray())));
}
}
12 changes: 10 additions & 2 deletions src/Games/NexusMods.Games.FOMOD/FomodAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,16 @@ public async Task DumpToFileSystemAsync(AbsolutePath fomodFolder)
async Task DumpItem(string relativePath, byte[] data)
{
var finalPath = fomodFolder.Combine(relativePath);
fs.CreateDirectory(finalPath.Parent);
await fs.WriteAllBytesAsync(finalPath, data);
try
{
fs.CreateDirectory(finalPath.Parent);
await fs.WriteAllBytesAsync(finalPath, data);
}
catch (IOException)
{
// ignored, this is a pathological case where path is broken
}

}

// Dump Xml
Expand Down
30 changes: 30 additions & 0 deletions src/Games/NexusMods.Games.FOMOD/FomodOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Text.Json.Serialization;

namespace NexusMods.Games.FOMOD;

public class FomodOption
{
[JsonPropertyName("name")]
public string name { get; init; } = string.Empty;

[JsonPropertyName("groups")]
public FomodGroup[] groups { get; init; } = [];
}

public class FomodGroup
{
[JsonPropertyName("name")]
public string name { get; init; } = string.Empty;

[JsonPropertyName("choices")]
public FomodChoice[] choices { get; init; } = [];
}

public class FomodChoice
{
[JsonPropertyName("name")]
public string name { get; init; } = string.Empty;

[JsonPropertyName("idx")]
public int idx { get; init; }
}
19 changes: 19 additions & 0 deletions src/Games/NexusMods.Games.FOMOD/FomodXmlInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@ public override async ValueTask<InstallerResult> ExecuteAsync(
ITransaction transaction,
Loadout.ReadOnly loadout,
CancellationToken cancellationToken)
{
return await ExecuteAsync(libraryArchive, loadoutGroup, transaction, loadout, null, cancellationToken);
}

public async ValueTask<InstallerResult> ExecuteAsync(
LibraryArchive.ReadOnly libraryArchive,
LoadoutItemGroup.New loadoutGroup,
ITransaction transaction,
Loadout.ReadOnly loadout,
FomodOption[]? options,
CancellationToken cancellationToken)
{
// the component dealing with FOMODs is built to support all kinds of mods, including those without a script.
// for those cases, stop patterns can be way more complex to deduce the intended installation structure. In our case, where
Expand All @@ -83,6 +94,14 @@ public override async ValueTask<InstallerResult> ExecuteAsync(
var installerDelegates = _delegates as InstallerDelegates;
if (installerDelegates is not null)
{
if (options is not null)
{
// NOTE(halgari) The support for passing in presets to the installer is utterly broken. So we're going to
// something different: we will create a new guided installer that will simply emit the user choices based on the
// provided options.
var installer = new PresetGuidedInstaller(options);
installerDelegates.UiDelegates = new UiDelegates(ServiceProvider.GetRequiredService<ILogger<UiDelegates>>(), installer);
}
installerDelegates.UiDelegates.CurrentArchiveFiles = tree;
}

Expand Down
4 changes: 3 additions & 1 deletion src/Games/NexusMods.Games.FOMOD/NexusMods.Games.FOMOD.csproj
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<!-- NuGet Package Shared Details -->
<Import Project="$([MSBuild]::GetPathOfFileAbove('NuGet.Build.props', '$(MSBuildThisFileDirectory)../'))" />

<ItemGroup>
<ProjectReference Include="..\..\Abstractions\NexusMods.Abstractions.GuidedInstallers\NexusMods.Abstractions.GuidedInstallers.csproj" />
<ProjectReference Include="..\..\Abstractions\NexusMods.Abstractions.Library.Installers\NexusMods.Abstractions.Library.Installers.csproj" />
Expand Down
2 changes: 1 addition & 1 deletion src/Games/NexusMods.Games.FOMOD/Services.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static class Services
public static IServiceCollection AddFomod(this IServiceCollection services)
{
services.AddAttributeCollection(typeof(EmptyDirectory));
services.AddSingleton<ICoreDelegates, InstallerDelegates>();
services.AddTransient<ICoreDelegates, InstallerDelegates>();
return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

query CollectionsForGame($gameDomain: String!, $offset: Int!, $count: Int!)
{
collections(viewAdultContent: true, filter: {gameDomain: {value: $gameDomain, op: EQUALS}}, offset: $offset, count: $count)
{
totalCount
nodesCount
nodes {
slug
name
latestPublishedRevision {
id
revisionNumber
}
}
}
}
Loading

0 comments on commit 13f7267

Please sign in to comment.