-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit fce1386
Showing
10 changed files
with
307 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* text=auto |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.vs/ | ||
bin/ | ||
obj/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright © 2024 Nuclearist | ||
|
||
All rights reserved. | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<OutputType>Exe</OutputType> | ||
<Version>1.0.0</Version> | ||
<ApplicationManifest>res/MRCP.manifest</ApplicationManifest> | ||
<Authors>Nuclearist</Authors> | ||
<Product>Steam Manifest Request Code Provider</Product> | ||
<Copyright>Copyright © 2024 Nuclearist</Copyright> | ||
<AppendTargetFrameworkToOutputPath>False</AppendTargetFrameworkToOutputPath> | ||
<ImplicitUsings>Enable</ImplicitUsings> | ||
<Nullable>Enable</Nullable> | ||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> | ||
<InvariantGlobalization>True</InvariantGlobalization> | ||
<JsonSerializerIsReflectionEnabledByDefault>False</JsonSerializerIsReflectionEnabledByDefault> | ||
<WindowsKitRoot Condition="$([MSBuild]::IsOSPlatform('Windows'))">$([MSBuild]::GetRegistryValue('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed Roots', 'KitsRoot10'))</WindowsKitRoot> | ||
<SignToolPath Condition="'$(WindowsKitRoot)' != ''">$(WindowsKitRoot)App Certification Kit\signtool.exe</SignToolPath> | ||
</PropertyGroup> | ||
<PropertyGroup Condition="'$(Configuration)' == 'Release'"> | ||
<Optimize>True</Optimize> | ||
<SignAssembly>True</SignAssembly> | ||
<AssemblyOriginatorKeyFile>MRCP.snk</AssemblyOriginatorKeyFile> | ||
<DelaySign>False</DelaySign> | ||
<PublishAot>True</PublishAot> | ||
<OptimizationPreference>Speed</OptimizationPreference> | ||
<EnableUnsafeBinaryFormatterSerialization>False</EnableUnsafeBinaryFormatterSerialization> | ||
<EnableUnsafeUTF7Encoding>False</EnableUnsafeUTF7Encoding> | ||
<MetadataUpdaterSupport>False</MetadataUpdaterSupport> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<PackageReference Include="TEKSteamClient" Version="1.4.1" /> | ||
</ItemGroup> | ||
<Target Name="SignFile" AfterTargets="ComputeAndCopyFilesToPublishDirectory" Condition="$([MSBuild]::IsOSPlatform('Windows')) And exists('$(SignToolPath)')"> | ||
<Exec Command="call "$(SignToolPath)" sign /tr http://timestamp.digicert.com /td sha256 /fd sha256 /a bin\Release\win-x64\publish\MRCP.exe" /> | ||
</Target> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MRCP", "MRCP.csproj", "{50DF6FC1-E9E9-4DB8-9A2E-31D8B3500DD3}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{50DF6FC1-E9E9-4DB8-9A2E-31D8B3500DD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{50DF6FC1-E9E9-4DB8-9A2E-31D8B3500DD3}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{50DF6FC1-E9E9-4DB8-9A2E-31D8B3500DD3}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{50DF6FC1-E9E9-4DB8-9A2E-31D8B3500DD3}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(ExtensibilityGlobals) = postSolution | ||
SolutionGuid = {65836321-A81B-4F9B-A6AC-81DB477FDBE5} | ||
EndGlobalSection | ||
EndGlobal |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# MRCP | ||
[![Discord](https://img.shields.io/discord/937821572285206659?style=flat-square&label=Discord&logo=discord&logoColor=white&color=7289DA)](https://discord.com/servers/teknology-hub-937821572285206659) | ||
|
||
## Overview | ||
|
||
Steam Manifest Request Code Provider (MRCP) is a lightweight bot that supplies Steam manifest request codes over a UDP socket using your Steam account | ||
|
||
## How to use | ||
|
||
Download the binary for your OS in [releases](https://github.com/Nuclearistt/MRCP/releases) or build it manually (relevant for Linux since Native AOT adds dynamic dependencies on specific library versions), run it once with `--setup` argument to interactively input credentials, then you may run it without any arguments (e.g as as service), it'll listen on specified port for UDP requests | ||
|
||
## Request and response data formats | ||
|
||
Request (16 bytes): | ||
+ uint AppId | ||
+ uint DepotId | ||
+ ulong ManifestId | ||
|
||
Response (8 bytes): ulong ManifestRequestCode | ||
|
||
## Why do you need it | ||
|
||
While [TEK Steam Client](https://github.com/Nuclearistt/TEKSteamClient) can install arbitrary Steam apps, it is unable to get manifest request codes for apps not owned on the account it's logged on, and hence to download those apps. MCRP allows to proxy MCR requests to an account that owns the apps in question without leaking account credentials because it's running on a remote server | ||
|
||
## Client side code example | ||
|
||
```cs | ||
using System.Collections.Frozen; | ||
using System.Net.Sockets; | ||
using TEKSteamClient; | ||
|
||
static readonly IPEndPoint ServerEndpoint = IPEndPoint.Parse("*Your server IP*:*MCPR Port*"); | ||
static ulong GetManifestRequestCode(uint appId, uint depotId, ulong manifestId) | ||
{ | ||
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); //May make it a static singleton | ||
Span<byte> buffer = stackalloc byte[16]; | ||
BitConverter.TryWriteBytes(buffer[..4], appId); | ||
BitConverter.TryWriteBytes(buffer[4..8], depotId); | ||
BitConverter.TryWriteBytes(buffer[8..], manifestId); | ||
socket.SendTo(buffer, ServerEndpoint); | ||
socket.Receive(buffer); | ||
return BitConverter.ToUInt64(buffer); | ||
} | ||
|
||
//The following code needs to be executed only once per app lifetime, you may put it in the beginning of Main method | ||
CMClient.ManifestRequestCodeSourceOverrides = System.Collections.Frozen.FrozenDictionary.ToFrozenDictionary((IEnumerable<KeyValuePair<uint, Func<uint, uint, ulong, ulong>>>) | ||
[ | ||
new(/*A depot ID*/, GetManifestRequestCode), | ||
new(/*Another depot ID*/, GetManifestRequestCode) | ||
]); | ||
|
||
//Your code using TEK Steam Client goes here, it'll automatically send requests to MCRP when needed | ||
``` | ||
|
||
## License | ||
|
||
MRCP is licensed under the [MIT](https://github.com/Nuclearistt/MRCP/blob/main/LICENSE) license. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> | ||
<assemblyIdentity type="win32" version="1.0.0.0" name="MRCP" processorArchitecture="amd64"/> | ||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> | ||
<application> | ||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> | ||
</application> | ||
</compatibility> | ||
<application xmlns="urn:schemas-microsoft-com:asm.v3"> | ||
<windowsSettings xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings"> | ||
<longPathAware>True</longPathAware> | ||
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage> | ||
<heapType xmlns="http://schemas.microsoft.com/SMI/2020/WindowsSettings">SegmentHeap</heapType> | ||
</windowsSettings> | ||
</application> | ||
</assembly> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
|
||
namespace MRCP; | ||
|
||
/// <summary>App config object.</summary> | ||
internal class Config | ||
{ | ||
/// <summary>Singleton path to the config file.</summary> | ||
private static readonly string s_filePath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MRCP", "Settings.json"); | ||
/// <summary>Port to listen on for requests.</summary> | ||
public required int Port { get; init; } | ||
/// <summary>Steam account name to use.</summary> | ||
public required string AccountName { get; init; } | ||
/// <summary>Access/refresh token for Steam account.</summary> | ||
public required string Token { get; init; } | ||
|
||
/// <summary>Writes current object state to the file.</summary> | ||
public void SaveToFile() | ||
{ | ||
Directory.CreateDirectory(Path.GetDirectoryName(s_filePath)!); | ||
byte[] data = JsonSerializer.SerializeToUtf8Bytes(this, ConfigJsonContext.Default.Config); | ||
using var fileHandle = File.OpenHandle(s_filePath, FileMode.Create, FileAccess.Write, preallocationSize: data.Length); | ||
RandomAccess.Write(fileHandle, data, 0); | ||
} | ||
/// <summary>Loads config from the file.</summary> | ||
/// <returns>Config object loaded from the file.</returns> | ||
public static Config Load() | ||
{ | ||
if (!File.Exists(s_filePath)) | ||
throw new FileNotFoundException("Config file not found. Run the app with --setup flag."); | ||
byte[] buffer; | ||
using (var fileHandle = File.OpenHandle(s_filePath)) | ||
{ | ||
buffer = new byte[(int)RandomAccess.GetLength(fileHandle)]; | ||
RandomAccess.Read(fileHandle, buffer, 0); | ||
} | ||
return JsonSerializer.Deserialize(buffer, ConfigJsonContext.Default.Config)!; | ||
} | ||
} | ||
[JsonSourceGenerationOptions(PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate)] | ||
[JsonSerializable(typeof(Config))] | ||
partial class ConfigJsonContext : JsonSerializerContext { } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
using System.Runtime.InteropServices; | ||
using System.Net.Sockets; | ||
using System.Net; | ||
using System.Runtime.CompilerServices; | ||
using System.Text; | ||
using TEKSteamClient; | ||
using TEKSteamClient.CM; | ||
using MRCP; | ||
|
||
AppDomain.CurrentDomain.UnhandledException += (sender, e) => Console.WriteLine($"Unhandled exception caught: {e.ExceptionObject}"); | ||
Environment.ExitCode = -1; | ||
if (args.Length > 0 && args[0] is "--setup") | ||
{ | ||
Console.Write("Enter the port to listen on: "); | ||
if (!ushort.TryParse(Console.ReadLine(), out ushort port)) | ||
{ | ||
Console.WriteLine("Error: Invalid port value"); | ||
return; | ||
} | ||
Console.Write("Enter Steam account login: "); | ||
string accountName = Console.ReadLine()!; | ||
Console.Write("Enter Steam account password: "); | ||
var builder = new StringBuilder(); | ||
ConsoleKey key; | ||
do | ||
{ | ||
var keyInfo = Console.ReadKey(true); | ||
key = keyInfo.Key; | ||
if (key is ConsoleKey.Backspace && builder.Length > 0) | ||
builder.Remove(builder.Length - 1, 1); | ||
else if (!char.IsControl(keyInfo.KeyChar)) | ||
builder.Append(keyInfo.KeyChar); | ||
} while (key != ConsoleKey.Enter); | ||
Console.WriteLine(); | ||
var cmClient = new CMClient(); | ||
string? token = null; | ||
try { cmClient.LogOn(accountName, ref token, builder.ToString()); } | ||
catch (SteamException se) | ||
{ | ||
Console.WriteLine($"Log on failed: {se.Message}"); | ||
return; | ||
} | ||
cmClient.Disconnect(); | ||
new Config | ||
{ | ||
Port = port, | ||
AccountName = accountName, | ||
Token = token! | ||
}.SaveToFile(); | ||
Environment.ExitCode = 0; | ||
} | ||
else | ||
{ | ||
var config = Config.Load(); | ||
var cmClient = new CMClient(); | ||
string? token = config.Token; | ||
cmClient.LogOn(config.AccountName, ref token); | ||
using var cts = new CancellationTokenSource(); | ||
using var signalRegistration = PosixSignalRegistration.Create(PosixSignal.SIGTERM, delegate | ||
{ | ||
Environment.ExitCode = 0; | ||
cts.Cancel(); | ||
}); | ||
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp) { SendTimeout = 2000 }; | ||
socket.Bind(new IPEndPoint(IPAddress.Any, config.Port)); | ||
byte[] buffer = new byte[16]; | ||
ref byte bufferRef = ref MemoryMarshal.GetArrayDataReference(buffer); | ||
var sendSpan = new ReadOnlySpan<byte>(buffer, 0, 8); | ||
var remoteAddress = new SocketAddress(AddressFamily.InterNetwork); | ||
try | ||
{ | ||
while (!cts.IsCancellationRequested) | ||
{ | ||
var task = socket.ReceiveFromAsync(buffer, SocketFlags.None, remoteAddress, cts.Token); | ||
if ((task.IsCompletedSuccessfully ? task.GetAwaiter().GetResult() : task.AsTask().GetAwaiter().GetResult()) < 16) | ||
continue; | ||
ulong requestCode; | ||
tryAgain: | ||
try | ||
{ | ||
requestCode = cmClient.GetManifestRequestCode(Unsafe.As<byte, uint>(ref bufferRef), Unsafe.As<byte, uint>(ref Unsafe.AddByteOffset(ref bufferRef, 4)), Unsafe.As<byte, ulong>(ref Unsafe.AddByteOffset(ref bufferRef, 8))); | ||
} | ||
catch (SteamException se) when (se.Type is SteamException.ErrorType.CMNotLoggedOn) | ||
{ | ||
try | ||
{ | ||
cmClient.LogOn(config.AccountName, ref token); | ||
goto tryAgain; | ||
} | ||
catch (Exception e) | ||
{ | ||
Console.WriteLine(e); | ||
continue; | ||
} | ||
} | ||
catch (Exception e) | ||
{ | ||
Console.WriteLine(e); | ||
continue; | ||
} | ||
Unsafe.As<byte, ulong>(ref bufferRef) = requestCode; | ||
socket.SendTo(sendSpan, SocketFlags.None, remoteAddress); | ||
} | ||
} | ||
catch (OperationCanceledException) { } | ||
} |