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

Index files from Steam #2385

Merged
merged 23 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
47595e6
Basic structure of the projects, can login via a test, and stay logge…
halgari Dec 11, 2024
669d794
Implemented the rest of the Steam flow for getting manifests. Introdu…
halgari Dec 11, 2024
f52f6a9
Boom, can stream files off of Steam
halgari Dec 11, 2024
c6d7703
Can download files, serialize manifests, all that good stuff
halgari Dec 12, 2024
49b3d9c
Fix the chunked stream so that it can use arbitrarily sized chunks
halgari Dec 12, 2024
c90d121
Delete the old Store project
halgari Dec 12, 2024
eb32afd
Disable the test that has to be run with human interaction
halgari Dec 12, 2024
ed8f39f
Few more comments and cleanup
halgari Dec 12, 2024
d64c215
Update Abstractions/NexusMods.Abstractions.Steam/DTOs/ProductInfo.cs
halgari Dec 16, 2024
f734e43
Update src/Abstractions/NexusMods.Abstractions.Hashes/Crc32.cs
halgari Dec 16, 2024
b58d349
Update src/Networking/NexusMods.Networking.Steam/NexusMods.Networking…
halgari Dec 16, 2024
05e5f85
Update src/Networking/NexusMods.Networking.Steam/Session.cs
halgari Dec 16, 2024
0c20eb3
Update Abstractions/NexusMods.Abstractions.Steam/DTOs/Manifest.cs
halgari Dec 16, 2024
fd26dc2
Update src/Networking/NexusMods.Networking.Steam/ProductInfoParser.cs
halgari Dec 16, 2024
869a959
Update src/Abstractions/NexusMods.Abstractions.Hashes/MultiHasher.cs
halgari Dec 16, 2024
752486b
Update src/Networking/NexusMods.Networking.Steam/Session.cs
halgari Dec 16, 2024
6815ce4
`sizeof(ulong)` vs `size(long)`
halgari Dec 16, 2024
f6e59ce
Update doc strings to better explain the value sizes
halgari Dec 16, 2024
782c74f
Update src/Networking/NexusMods.Networking.Steam/ManifestParser.cs
halgari Dec 17, 2024
a5dd382
Update src/Abstractions/NexusMods.Abstractions.Hashes/HashJsonConvert…
halgari Dec 17, 2024
6cca217
Fix the .Steam project
halgari Dec 17, 2024
71c769a
Merge branch 'steam-login' of https://github.com/Nexus-Mods/NexusMods…
halgari Dec 17, 2024
f2b3e6b
fix compile errors introduced during code review
halgari Dec 17, 2024
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
24 changes: 24 additions & 0 deletions Abstractions/NexusMods.Abstractions.Steam/DTOs/Depot.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using NexusMods.Abstractions.Steam.Values;

namespace NexusMods.Abstractions.Steam.DTOs;

/// <summary>
/// Information about a depot on Steam.
/// </summary>
public class Depot
{
/// <summary>
/// The OSes that the depot is available on.
/// </summary>
public required string[] OsList { get; init; }
Comment on lines +10 to +13
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have the OS list, we could also get the languages of a depot.


/// <summary>
/// The id of the depot.
/// </summary>
public required DepotId DepotId { get; init; }

/// <summary>
/// The manifests associated with the depot, with a key for each available branch
/// </summary>
public required Dictionary<string, ManifestInfo> Manifests { get; init; }
}
93 changes: 93 additions & 0 deletions Abstractions/NexusMods.Abstractions.Steam/DTOs/Manifest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using NexusMods.Abstractions.Hashes;
using NexusMods.Abstractions.Steam.Values;
using NexusMods.Paths;

namespace NexusMods.Abstractions.Steam.DTOs;

/// <summary>
/// A full, detailed manifest for a specific manifest id
/// </summary>
public class Manifest
{
/// <summary>
/// The gid of the manifest
/// </summary>
public required ManifestId ManifestId { get; init; }

/// <summary>
/// The files in the manifest
/// </summary>
public required FileData[] Files { get; init; }

/// <summary>
/// The depot id of the manifest
/// </summary>
public required DepotId DepotId { get; init; }

/// <summary>
/// The time the manifest was created
/// </summary>
public required DateTimeOffset CreationTime { get; init; }

/// <summary>
/// The size of all files in the manifest, compressed
/// </summary>
public required Size TotalCompressedSize { get; init; }

/// <summary>
/// The size of all files in the manifest, uncompressed
/// </summary>
public required Size TotalUncompressedSize { get; init; }

public class FileData
{
/// <summary>
/// The name of the file
/// </summary>
public RelativePath Path { get; init; }

/// <summary>
/// The size of the file, compressed
/// </summary>
public Size Size { get; init; }
Comment on lines +49 to +52
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other types have both compressed and decompressed sizes, for consistency this should be named CompressedSize and we should add DecompressedSize if we have it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm matching what SteamKit provides here, I don't mind changing it, but then we'd be out-of-sync with the "official" source

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be happy if we have something that is consistent internally.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the comment on the other section


/// <summary>
/// The Sha1 hash of the file
/// </summary>
public required Sha1 Hash { get; init; }

/// <summary>
/// The chunks of the file
/// </summary>
public required Chunk[] Chunks { get; init; }
}

public class Chunk
{
/// <summary>
/// The id of the chunk
/// </summary>
public required Sha1 ChunkId { get; init; }

/// <summary>
/// The crc32 checksum of the chunk
/// </summary>
public required Crc32 Checksum { get; init; }

/// <summary>
/// The offset of the chunk in the resulting file
/// </summary>
public required ulong Offset { get; init; }

/// <summary>
/// The size of the chunk, compressed
/// </summary>
public required Size CompressedSize { get; init; }

/// <summary>
/// The size of the chunk, uncompressed
/// </summary>
public required Size UncompressedSize { get; init; }

}
}
26 changes: 26 additions & 0 deletions Abstractions/NexusMods.Abstractions.Steam/DTOs/ManifestInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using NexusMods.Abstractions.Steam.Values;
using NexusMods.Paths;

namespace NexusMods.Abstractions.Steam.DTOs;

/// <summary>
/// Meta information about a manifest, not the actual contents, just the id
/// and the size of the files in aggregate.
/// </summary>
public class ManifestInfo
{
/// <summary>
/// The globally unique identifier of the manifest.
/// </summary>
public required ManifestId ManifestId { get; init; }

/// <summary>
/// The size of the downloaded files, decompressed
/// </summary>
public required Size Size { get; init; }

/// <summary>
/// The size of the files, compressed
/// </summary>
public required Size DownloadSize { get; init; }
}
24 changes: 24 additions & 0 deletions Abstractions/NexusMods.Abstractions.Steam/DTOs/ProductInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using NexusMods.Abstractions.Steam.Values;

namespace NexusMods.Abstractions.Steam.DTOs;

/// <summary>
/// Information about a product (a game) on Steam.
/// </summary>
public class ProductInfo
{
/// <summary>
/// The revision number of this product info.
/// </summary>
public required uint ChangeNumber { get; init; }

/// <summary>
/// The app id of the product.
/// </summary>
public required AppId AppId { get; init; }

/// <summary>
/// The depots of the product.
/// </summary>
public required Depot[] Depots { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace NexusMods.Abstractions.Steam;

/// <summary>
/// A user intervention handler that can be used to request authorization information from the user.
/// </summary>
public interface IAuthInterventionHandler
{
/// <summary>
/// Display a QR code to the user for the given uri. When the token is cancelled, the QR code should be hidden.
/// </summary>
public void ShowQRCode(Uri uri, CancellationToken token);
}
17 changes: 17 additions & 0 deletions Abstractions/NexusMods.Abstractions.Steam/IAuthStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace NexusMods.Abstractions.Steam;

/// <summary>
/// Interface for abstracting away the storage of Steam authentication data.
/// </summary>
public interface IAuthStorage
{
/// <summary>
/// Tries to load the authentication data, if it does not exist or fails to load, returns false.
/// </summary>
public Task<(bool Success, byte[] Data)> TryLoad();

/// <summary>
/// Saves the authentication data.
/// </summary>
public Task SaveAsync(byte[] data);
}
26 changes: 26 additions & 0 deletions Abstractions/NexusMods.Abstractions.Steam/ISteamSession.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using NexusMods.Abstractions.Steam.DTOs;
using NexusMods.Abstractions.Steam.Values;
using NexusMods.Paths;

namespace NexusMods.Abstractions.Steam;

/// <summary>
/// An abstraction for a Steam session.
/// </summary>
public interface ISteamSession
{
/// <summary>
/// Get the product info for the specified app ID
/// </summary>
public Task<ProductInfo> GetProductInfoAsync(AppId appId, CancellationToken cancellationToken = default);

/// <summary>
/// Get the manifest data for a specific manifest
/// </summary>
public Task<Manifest> GetManifestContents(AppId appId, DepotId depotId, ManifestId manifestId, string branch, CancellationToken token = default);

/// <summary>
/// Get a readable, seekable, stream for the specified file in the specified manifest
/// </summary>
public Stream GetFileStream(AppId appId, Manifest manifest, RelativePath file);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$([MSBuild]::GetPathOfFileAbove('NuGet.Build.props', '$(MSBuildThisFileDirectory)../'))" />

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

<ItemGroup>
<ProjectReference Include="..\..\src\Abstractions\NexusMods.Abstractions.Hashes\NexusMods.Abstractions.Hashes.csproj" />
</ItemGroup>
</Project>
12 changes: 12 additions & 0 deletions Abstractions/NexusMods.Abstractions.Steam/Values/AppId.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using TransparentValueObjects;

namespace NexusMods.Abstractions.Steam.Values;

/// <summary>
/// A globally unique identifier for an application on Steam.
/// </summary>
[ValueObject<uint>]
public readonly partial struct AppId : IAugmentWith<JsonAugment>
{

}
12 changes: 12 additions & 0 deletions Abstractions/NexusMods.Abstractions.Steam/Values/DepotId.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using TransparentValueObjects;

namespace NexusMods.Abstractions.Steam.Values;

/// <summary>
/// A globally unique identifier for a depot, a reference to a collection of files on the Steam CDN.
/// </summary>
[ValueObject<uint>]
public readonly partial struct DepotId : IAugmentWith<JsonAugment>
{

}
12 changes: 12 additions & 0 deletions Abstractions/NexusMods.Abstractions.Steam/Values/ManifestId.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using TransparentValueObjects;

namespace NexusMods.Abstractions.Steam.Values;

/// <summary>
/// A global unique identifier for a manifest, a specific collection of files that can be downloaded
/// </summary>
[ValueObject<ulong>]
public readonly partial struct ManifestId : IAugmentWith<JsonAugment>
{

}
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@
<PackageVersion Include="ObservableCollections" Version="3.3.2" />
<PackageVersion Include="ObservableCollections.R3" Version="3.3.2" />
<PackageVersion Include="QoiSharp" Version="1.0.0" />
<PackageVersion Include="QRCoder" Version="1.6.0" />
<PackageVersion Include="R3" Version="1.2.9" />
<PackageVersion Include="R3Extensions.Avalonia" Version="1.2.9" />
<PackageVersion Include="ReactiveUI" Version="20.1.63" />
<PackageVersion Include="Spectre.Console.Testing" Version="0.49.1" />
<PackageVersion Include="SteamKit2" Version="3.0.0" />
<PackageVersion Include="StrawberryShake.Server" Version="14.1.0" />
<PackageVersion Include="System.Linq" Version="4.3.0" />
<PackageVersion Include="System.Text.Json" Version="9.0.0" VersionOverride="9.0.0" />
Expand Down
28 changes: 28 additions & 0 deletions NexusMods.App.sln
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Games.MountAndBla
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Telemetry.Tests", "tests\NexusMods.Telemetry.Tests\NexusMods.Telemetry.Tests.csproj", "{336387F7-3635-43FE-9C23-CBC0CE534989}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Networking.Steam", "src\Networking\NexusMods.Networking.Steam\NexusMods.Networking.Steam.csproj", "{4A501BBB-389C-460C-B0C3-6F2F968773B1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Abstractions.Steam", "Abstractions\NexusMods.Abstractions.Steam\NexusMods.Abstractions.Steam.csproj", "{24457AAA-8954-4BD6-8EB5-168EAC6EFB1B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Networking.Steam.Tests", "tests\Networking\NexusMods.Networking.Steam.Tests\NexusMods.Networking.Steam.Tests.csproj", "{17023DB9-8E31-4397-B3E1-141149987865}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Abstractions.Hashes", "src\Abstractions\NexusMods.Abstractions.Hashes\NexusMods.Abstractions.Hashes.csproj", "{AF703852-D7B0-4BAD-8C75-B6046C6F0490}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -728,6 +736,22 @@ Global
{336387F7-3635-43FE-9C23-CBC0CE534989}.Debug|Any CPU.Build.0 = Debug|Any CPU
{336387F7-3635-43FE-9C23-CBC0CE534989}.Release|Any CPU.ActiveCfg = Release|Any CPU
{336387F7-3635-43FE-9C23-CBC0CE534989}.Release|Any CPU.Build.0 = Release|Any CPU
{4A501BBB-389C-460C-B0C3-6F2F968773B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A501BBB-389C-460C-B0C3-6F2F968773B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A501BBB-389C-460C-B0C3-6F2F968773B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4A501BBB-389C-460C-B0C3-6F2F968773B1}.Release|Any CPU.Build.0 = Release|Any CPU
{24457AAA-8954-4BD6-8EB5-168EAC6EFB1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{24457AAA-8954-4BD6-8EB5-168EAC6EFB1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{24457AAA-8954-4BD6-8EB5-168EAC6EFB1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{24457AAA-8954-4BD6-8EB5-168EAC6EFB1B}.Release|Any CPU.Build.0 = Release|Any CPU
{17023DB9-8E31-4397-B3E1-141149987865}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{17023DB9-8E31-4397-B3E1-141149987865}.Debug|Any CPU.Build.0 = Debug|Any CPU
{17023DB9-8E31-4397-B3E1-141149987865}.Release|Any CPU.ActiveCfg = Release|Any CPU
{17023DB9-8E31-4397-B3E1-141149987865}.Release|Any CPU.Build.0 = Release|Any CPU
{AF703852-D7B0-4BAD-8C75-B6046C6F0490}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF703852-D7B0-4BAD-8C75-B6046C6F0490}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF703852-D7B0-4BAD-8C75-B6046C6F0490}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF703852-D7B0-4BAD-8C75-B6046C6F0490}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -857,6 +881,10 @@ Global
{A5A2932D-B3EF-480B-BEBC-793F6FC90EDE} = {52AF9D62-7D5B-4AD0-BA12-86F2AA67428B}
{8D7E82BB-2F8D-455A-AF12-C486D9EC3B77} = {70D38D24-79AE-4600-8E83-17F3C11BA81F}
{336387F7-3635-43FE-9C23-CBC0CE534989} = {52AF9D62-7D5B-4AD0-BA12-86F2AA67428B}
{4A501BBB-389C-460C-B0C3-6F2F968773B1} = {D7E9D8F5-8AC8-4ADA-B219-C549084AD84C}
{24457AAA-8954-4BD6-8EB5-168EAC6EFB1B} = {0CB73565-1207-4A56-A79F-6A8E9BBD795C}
{17023DB9-8E31-4397-B3E1-141149987865} = {897C4198-884F-448A-B0B0-C2A6D971EAE0}
{AF703852-D7B0-4BAD-8C75-B6046C6F0490} = {0CB73565-1207-4A56-A79F-6A8E9BBD795C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9F9F8352-34DD-42C0-8564-EE9AF34A3501}
Expand Down
1 change: 1 addition & 0 deletions NexusMods.App.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MIME/@EntryIndexedValue">MIME</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NXM/@EntryIndexedValue">NXM</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OSX/@EntryIndexedValue">OSX</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=QR/@EntryIndexedValue">QR</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SMAPI/@EntryIndexedValue">SMAPI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=VM/@EntryIndexedValue">VM</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/Abbreviations/=LG/@EntryIndexedValue">LG</s:String>
Expand Down
5 changes: 5 additions & 0 deletions src/Abstractions/NexusMods.Abstractions.Cli/Renderable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,22 @@
public static class Renderable
{
/// <summary>
/// Creates a new <see cref="Text"/> renderable.

Check warning on line 11 in src/Abstractions/NexusMods.Abstractions.Cli/Renderable.cs

View workflow job for this annotation

GitHub Actions / build-and-test / Build and Test (ubuntu-latest)

Ambiguous reference in cref attribute: 'Text'. Assuming 'Renderable.Text(string)', but could have also matched other overloads including 'Renderable.Text(string, string[])'.
/// </summary>
/// <param name="template"></param>
/// <returns></returns>
public static Text Text(string template) => new Text { Template = template };

/// <summary>
/// Creates a new <see cref="Text"/> renderable out of the given arguments and template.

Check warning on line 18 in src/Abstractions/NexusMods.Abstractions.Cli/Renderable.cs

View workflow job for this annotation

GitHub Actions / build-and-test / Build and Test (ubuntu-latest)

Ambiguous reference in cref attribute: 'Text'. Assuming 'Renderable.Text(string)', but could have also matched other overloads including 'Renderable.Text(string, string[])'.
/// </summary>
/// <param name="template"></param>
/// <returns></returns>
public static Text Text(string template, string[] args) => new Text { Template = template, Arguments = args};

Check warning on line 22 in src/Abstractions/NexusMods.Abstractions.Cli/Renderable.cs

View workflow job for this annotation

GitHub Actions / build-and-test / Build and Test (ubuntu-latest)

Parameter 'args' has no matching param tag in the XML comment for 'Renderable.Text(string, string[])' (but other parameters do)

/// <summary>
/// Creates a new <see cref="Text"/> renderable with a new line at the end.

Check warning on line 25 in src/Abstractions/NexusMods.Abstractions.Cli/Renderable.cs

View workflow job for this annotation

GitHub Actions / build-and-test / Build and Test (ubuntu-latest)

Ambiguous reference in cref attribute: 'Text'. Assuming 'Renderable.Text(string)', but could have also matched other overloads including 'Renderable.Text(string, string[])'.
/// </summary>
public static Text TextLine(string template) => new Text { Template = template + "\n" };

}
Loading
Loading