Skip to content

Commit f79dd35

Browse files
halgarierri120
andauthored
Index files from Steam (#2385)
* Basic structure of the projects, can login via a test, and stay logged in * Implemented the rest of the Steam flow for getting manifests. Introduced a .Hashes abstraction library for hash value types. Moved Chunked streams into the IO abstractions library * Boom, can stream files off of Steam * Can download files, serialize manifests, all that good stuff * Fix the chunked stream so that it can use arbitrarily sized chunks * Delete the old Store project * Disable the test that has to be run with human interaction * Few more comments and cleanup * Update Abstractions/NexusMods.Abstractions.Steam/DTOs/ProductInfo.cs Co-authored-by: erri120 <[email protected]> * Update src/Abstractions/NexusMods.Abstractions.Hashes/Crc32.cs Co-authored-by: erri120 <[email protected]> * Update src/Networking/NexusMods.Networking.Steam/NexusMods.Networking.Steam.csproj Co-authored-by: erri120 <[email protected]> * Update src/Networking/NexusMods.Networking.Steam/Session.cs Co-authored-by: erri120 <[email protected]> * Update Abstractions/NexusMods.Abstractions.Steam/DTOs/Manifest.cs Co-authored-by: erri120 <[email protected]> * Update src/Networking/NexusMods.Networking.Steam/ProductInfoParser.cs Co-authored-by: erri120 <[email protected]> * Update src/Abstractions/NexusMods.Abstractions.Hashes/MultiHasher.cs Co-authored-by: erri120 <[email protected]> * Update src/Networking/NexusMods.Networking.Steam/Session.cs Co-authored-by: erri120 <[email protected]> * `sizeof(ulong)` vs `size(long)` * Update doc strings to better explain the value sizes * Update src/Networking/NexusMods.Networking.Steam/ManifestParser.cs Co-authored-by: erri120 <[email protected]> * Update src/Abstractions/NexusMods.Abstractions.Hashes/HashJsonConverter.cs Co-authored-by: erri120 <[email protected]> * Fix the .Steam project * fix compile errors introduced during code review --------- Co-authored-by: erri120 <[email protected]>
1 parent a62eeb1 commit f79dd35

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1809
-72
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using NexusMods.Abstractions.Steam.Values;
2+
3+
namespace NexusMods.Abstractions.Steam.DTOs;
4+
5+
/// <summary>
6+
/// Information about a depot on Steam.
7+
/// </summary>
8+
public class Depot
9+
{
10+
/// <summary>
11+
/// The OSes that the depot is available on.
12+
/// </summary>
13+
public required string[] OsList { get; init; }
14+
15+
/// <summary>
16+
/// The id of the depot.
17+
/// </summary>
18+
public required DepotId DepotId { get; init; }
19+
20+
/// <summary>
21+
/// The manifests associated with the depot, with a key for each available branch
22+
/// </summary>
23+
public required Dictionary<string, ManifestInfo> Manifests { get; init; }
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
using NexusMods.Abstractions.Hashes;
2+
using NexusMods.Abstractions.Steam.Values;
3+
using NexusMods.Paths;
4+
5+
namespace NexusMods.Abstractions.Steam.DTOs;
6+
7+
/// <summary>
8+
/// A full, detailed manifest for a specific manifest id
9+
/// </summary>
10+
public class Manifest
11+
{
12+
/// <summary>
13+
/// The gid of the manifest
14+
/// </summary>
15+
public required ManifestId ManifestId { get; init; }
16+
17+
/// <summary>
18+
/// The files in the manifest
19+
/// </summary>
20+
public required FileData[] Files { get; init; }
21+
22+
/// <summary>
23+
/// The depot id of the manifest
24+
/// </summary>
25+
public required DepotId DepotId { get; init; }
26+
27+
/// <summary>
28+
/// The time the manifest was created
29+
/// </summary>
30+
public required DateTimeOffset CreationTime { get; init; }
31+
32+
/// <summary>
33+
/// The size of all files in the manifest, compressed
34+
/// </summary>
35+
public required Size TotalCompressedSize { get; init; }
36+
37+
/// <summary>
38+
/// The size of all files in the manifest, uncompressed
39+
/// </summary>
40+
public required Size TotalUncompressedSize { get; init; }
41+
42+
public class FileData
43+
{
44+
/// <summary>
45+
/// The name of the file
46+
/// </summary>
47+
public RelativePath Path { get; init; }
48+
49+
/// <summary>
50+
/// The size of the file, compressed
51+
/// </summary>
52+
public Size Size { get; init; }
53+
54+
/// <summary>
55+
/// The Sha1 hash of the file
56+
/// </summary>
57+
public required Sha1 Hash { get; init; }
58+
59+
/// <summary>
60+
/// The chunks of the file
61+
/// </summary>
62+
public required Chunk[] Chunks { get; init; }
63+
}
64+
65+
public class Chunk
66+
{
67+
/// <summary>
68+
/// The id of the chunk
69+
/// </summary>
70+
public required Sha1 ChunkId { get; init; }
71+
72+
/// <summary>
73+
/// The crc32 checksum of the chunk
74+
/// </summary>
75+
public required Crc32 Checksum { get; init; }
76+
77+
/// <summary>
78+
/// The offset of the chunk in the resulting file
79+
/// </summary>
80+
public required ulong Offset { get; init; }
81+
82+
/// <summary>
83+
/// The size of the chunk, compressed
84+
/// </summary>
85+
public required Size CompressedSize { get; init; }
86+
87+
/// <summary>
88+
/// The size of the chunk, uncompressed
89+
/// </summary>
90+
public required Size UncompressedSize { get; init; }
91+
92+
}
93+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using NexusMods.Abstractions.Steam.Values;
2+
using NexusMods.Paths;
3+
4+
namespace NexusMods.Abstractions.Steam.DTOs;
5+
6+
/// <summary>
7+
/// Meta information about a manifest, not the actual contents, just the id
8+
/// and the size of the files in aggregate.
9+
/// </summary>
10+
public class ManifestInfo
11+
{
12+
/// <summary>
13+
/// The globally unique identifier of the manifest.
14+
/// </summary>
15+
public required ManifestId ManifestId { get; init; }
16+
17+
/// <summary>
18+
/// The size of the downloaded files, decompressed
19+
/// </summary>
20+
public required Size Size { get; init; }
21+
22+
/// <summary>
23+
/// The size of the files, compressed
24+
/// </summary>
25+
public required Size DownloadSize { get; init; }
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using NexusMods.Abstractions.Steam.Values;
2+
3+
namespace NexusMods.Abstractions.Steam.DTOs;
4+
5+
/// <summary>
6+
/// Information about a product (a game) on Steam.
7+
/// </summary>
8+
public class ProductInfo
9+
{
10+
/// <summary>
11+
/// The revision number of this product info.
12+
/// </summary>
13+
public required uint ChangeNumber { get; init; }
14+
15+
/// <summary>
16+
/// The app id of the product.
17+
/// </summary>
18+
public required AppId AppId { get; init; }
19+
20+
/// <summary>
21+
/// The depots of the product.
22+
/// </summary>
23+
public required Depot[] Depots { get; init; }
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace NexusMods.Abstractions.Steam;
2+
3+
/// <summary>
4+
/// A user intervention handler that can be used to request authorization information from the user.
5+
/// </summary>
6+
public interface IAuthInterventionHandler
7+
{
8+
/// <summary>
9+
/// Display a QR code to the user for the given uri. When the token is cancelled, the QR code should be hidden.
10+
/// </summary>
11+
public void ShowQRCode(Uri uri, CancellationToken token);
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace NexusMods.Abstractions.Steam;
2+
3+
/// <summary>
4+
/// Interface for abstracting away the storage of Steam authentication data.
5+
/// </summary>
6+
public interface IAuthStorage
7+
{
8+
/// <summary>
9+
/// Tries to load the authentication data, if it does not exist or fails to load, returns false.
10+
/// </summary>
11+
public Task<(bool Success, byte[] Data)> TryLoad();
12+
13+
/// <summary>
14+
/// Saves the authentication data.
15+
/// </summary>
16+
public Task SaveAsync(byte[] data);
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using NexusMods.Abstractions.Steam.DTOs;
2+
using NexusMods.Abstractions.Steam.Values;
3+
using NexusMods.Paths;
4+
5+
namespace NexusMods.Abstractions.Steam;
6+
7+
/// <summary>
8+
/// An abstraction for a Steam session.
9+
/// </summary>
10+
public interface ISteamSession
11+
{
12+
/// <summary>
13+
/// Get the product info for the specified app ID
14+
/// </summary>
15+
public Task<ProductInfo> GetProductInfoAsync(AppId appId, CancellationToken cancellationToken = default);
16+
17+
/// <summary>
18+
/// Get the manifest data for a specific manifest
19+
/// </summary>
20+
public Task<Manifest> GetManifestContents(AppId appId, DepotId depotId, ManifestId manifestId, string branch, CancellationToken token = default);
21+
22+
/// <summary>
23+
/// Get a readable, seekable, stream for the specified file in the specified manifest
24+
/// </summary>
25+
public Stream GetFileStream(AppId appId, Manifest manifest, RelativePath file);
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<Import Project="$([MSBuild]::GetPathOfFileAbove('NuGet.Build.props', '$(MSBuildThisFileDirectory)../'))" />
3+
4+
<ItemGroup>
5+
<PackageReference Include="NexusMods.Paths" />
6+
<PackageReference Include="TransparentValueObjects" PrivateAssets="all" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
7+
</ItemGroup>
8+
9+
<ItemGroup>
10+
<ProjectReference Include="..\..\src\Abstractions\NexusMods.Abstractions.Hashes\NexusMods.Abstractions.Hashes.csproj" />
11+
</ItemGroup>
12+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using TransparentValueObjects;
2+
3+
namespace NexusMods.Abstractions.Steam.Values;
4+
5+
/// <summary>
6+
/// A globally unique identifier for an application on Steam.
7+
/// </summary>
8+
[ValueObject<uint>]
9+
public readonly partial struct AppId : IAugmentWith<JsonAugment>
10+
{
11+
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using TransparentValueObjects;
2+
3+
namespace NexusMods.Abstractions.Steam.Values;
4+
5+
/// <summary>
6+
/// A globally unique identifier for a depot, a reference to a collection of files on the Steam CDN.
7+
/// </summary>
8+
[ValueObject<uint>]
9+
public readonly partial struct DepotId : IAugmentWith<JsonAugment>
10+
{
11+
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using TransparentValueObjects;
2+
3+
namespace NexusMods.Abstractions.Steam.Values;
4+
5+
/// <summary>
6+
/// A global unique identifier for a manifest, a specific collection of files that can be downloaded
7+
/// </summary>
8+
[ValueObject<ulong>]
9+
public readonly partial struct ManifestId : IAugmentWith<JsonAugment>
10+
{
11+
12+
}

Directory.Packages.props

+2
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@
3131
<PackageVersion Include="ObservableCollections" Version="3.3.2" />
3232
<PackageVersion Include="ObservableCollections.R3" Version="3.3.2" />
3333
<PackageVersion Include="QoiSharp" Version="1.0.0" />
34+
<PackageVersion Include="QRCoder" Version="1.6.0" />
3435
<PackageVersion Include="R3" Version="1.2.9" />
3536
<PackageVersion Include="R3Extensions.Avalonia" Version="1.2.9" />
3637
<PackageVersion Include="ReactiveUI" Version="20.1.63" />
3738
<PackageVersion Include="Spectre.Console.Testing" Version="0.49.1" />
39+
<PackageVersion Include="SteamKit2" Version="3.0.0" />
3840
<PackageVersion Include="StrawberryShake.Server" Version="14.1.0" />
3941
<PackageVersion Include="System.Linq" Version="4.3.0" />
4042
<PackageVersion Include="System.Text.Json" Version="9.0.0" VersionOverride="9.0.0" />

NexusMods.App.sln

+28
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Games.MountAndBla
278278
EndProject
279279
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Telemetry.Tests", "tests\NexusMods.Telemetry.Tests\NexusMods.Telemetry.Tests.csproj", "{336387F7-3635-43FE-9C23-CBC0CE534989}"
280280
EndProject
281+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Networking.Steam", "src\Networking\NexusMods.Networking.Steam\NexusMods.Networking.Steam.csproj", "{4A501BBB-389C-460C-B0C3-6F2F968773B1}"
282+
EndProject
283+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Abstractions.Steam", "Abstractions\NexusMods.Abstractions.Steam\NexusMods.Abstractions.Steam.csproj", "{24457AAA-8954-4BD6-8EB5-168EAC6EFB1B}"
284+
EndProject
285+
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}"
286+
EndProject
287+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Abstractions.Hashes", "src\Abstractions\NexusMods.Abstractions.Hashes\NexusMods.Abstractions.Hashes.csproj", "{AF703852-D7B0-4BAD-8C75-B6046C6F0490}"
288+
EndProject
281289
Global
282290
GlobalSection(SolutionConfigurationPlatforms) = preSolution
283291
Debug|Any CPU = Debug|Any CPU
@@ -728,6 +736,22 @@ Global
728736
{336387F7-3635-43FE-9C23-CBC0CE534989}.Debug|Any CPU.Build.0 = Debug|Any CPU
729737
{336387F7-3635-43FE-9C23-CBC0CE534989}.Release|Any CPU.ActiveCfg = Release|Any CPU
730738
{336387F7-3635-43FE-9C23-CBC0CE534989}.Release|Any CPU.Build.0 = Release|Any CPU
739+
{4A501BBB-389C-460C-B0C3-6F2F968773B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
740+
{4A501BBB-389C-460C-B0C3-6F2F968773B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
741+
{4A501BBB-389C-460C-B0C3-6F2F968773B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
742+
{4A501BBB-389C-460C-B0C3-6F2F968773B1}.Release|Any CPU.Build.0 = Release|Any CPU
743+
{24457AAA-8954-4BD6-8EB5-168EAC6EFB1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
744+
{24457AAA-8954-4BD6-8EB5-168EAC6EFB1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
745+
{24457AAA-8954-4BD6-8EB5-168EAC6EFB1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
746+
{24457AAA-8954-4BD6-8EB5-168EAC6EFB1B}.Release|Any CPU.Build.0 = Release|Any CPU
747+
{17023DB9-8E31-4397-B3E1-141149987865}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
748+
{17023DB9-8E31-4397-B3E1-141149987865}.Debug|Any CPU.Build.0 = Debug|Any CPU
749+
{17023DB9-8E31-4397-B3E1-141149987865}.Release|Any CPU.ActiveCfg = Release|Any CPU
750+
{17023DB9-8E31-4397-B3E1-141149987865}.Release|Any CPU.Build.0 = Release|Any CPU
751+
{AF703852-D7B0-4BAD-8C75-B6046C6F0490}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
752+
{AF703852-D7B0-4BAD-8C75-B6046C6F0490}.Debug|Any CPU.Build.0 = Debug|Any CPU
753+
{AF703852-D7B0-4BAD-8C75-B6046C6F0490}.Release|Any CPU.ActiveCfg = Release|Any CPU
754+
{AF703852-D7B0-4BAD-8C75-B6046C6F0490}.Release|Any CPU.Build.0 = Release|Any CPU
731755
EndGlobalSection
732756
GlobalSection(SolutionProperties) = preSolution
733757
HideSolutionNode = FALSE
@@ -857,6 +881,10 @@ Global
857881
{A5A2932D-B3EF-480B-BEBC-793F6FC90EDE} = {52AF9D62-7D5B-4AD0-BA12-86F2AA67428B}
858882
{8D7E82BB-2F8D-455A-AF12-C486D9EC3B77} = {70D38D24-79AE-4600-8E83-17F3C11BA81F}
859883
{336387F7-3635-43FE-9C23-CBC0CE534989} = {52AF9D62-7D5B-4AD0-BA12-86F2AA67428B}
884+
{4A501BBB-389C-460C-B0C3-6F2F968773B1} = {D7E9D8F5-8AC8-4ADA-B219-C549084AD84C}
885+
{24457AAA-8954-4BD6-8EB5-168EAC6EFB1B} = {0CB73565-1207-4A56-A79F-6A8E9BBD795C}
886+
{17023DB9-8E31-4397-B3E1-141149987865} = {897C4198-884F-448A-B0B0-C2A6D971EAE0}
887+
{AF703852-D7B0-4BAD-8C75-B6046C6F0490} = {0CB73565-1207-4A56-A79F-6A8E9BBD795C}
860888
EndGlobalSection
861889
GlobalSection(ExtensibilityGlobals) = postSolution
862890
SolutionGuid = {9F9F8352-34DD-42C0-8564-EE9AF34A3501}

NexusMods.App.sln.DotSettings

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MIME/@EntryIndexedValue">MIME</s:String>
4545
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NXM/@EntryIndexedValue">NXM</s:String>
4646
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OSX/@EntryIndexedValue">OSX</s:String>
47+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=QR/@EntryIndexedValue">QR</s:String>
4748
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SMAPI/@EntryIndexedValue">SMAPI</s:String>
4849
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=VM/@EntryIndexedValue">VM</s:String>
4950
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/Abbreviations/=LG/@EntryIndexedValue">LG</s:String>

src/Abstractions/NexusMods.Abstractions.Cli/Renderable.cs

+5
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,10 @@ public static class Renderable
2020
/// <param name="template"></param>
2121
/// <returns></returns>
2222
public static Text Text(string template, string[] args) => new Text { Template = template, Arguments = args};
23+
24+
/// <summary>
25+
/// Creates a new <see cref="Text"/> renderable with a new line at the end.
26+
/// </summary>
27+
public static Text TextLine(string template) => new Text { Template = template + "\n" };
2328

2429
}

0 commit comments

Comments
 (0)