using System; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Hosting; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SteamAuth; using SteamKit2; using SteamKit2.Authentication; var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapPost("/login", async (HttpContext http) => { var req = await GetRequestBodyAsync(http); if (req == null) { http.Response.StatusCode = 400; await http.Response.WriteAsync("invalid request"); return; } if (!File.Exists(req.MaFilePath)) { http.Response.StatusCode = 400; await http.Response.WriteAsync("maFile not found"); return; } SteamGuardAccount? account; try { var contents = await File.ReadAllTextAsync(req.MaFilePath); account = JsonConvert.DeserializeObject(contents); } catch (Exception ex) { http.Response.StatusCode = 500; await http.Response.WriteAsync("failed to read maFile: " + ex.Message); return; } try { // Start SteamClient and authenticate using the maFile-provided SteamGuardAccount SteamClient steamClient = CreateSteamClient(req.ProxyUrl, app.Logger, "login"); steamClient.Connect(); var timeoutMs = 15000; var waited = 0; while (!steamClient.IsConnected && waited < timeoutMs) { await Task.Delay(200); waited += 200; } if (!steamClient.IsConnected) { http.Response.StatusCode = 500; await http.Response.WriteAsync("failed to connect to steam network"); return; } CredentialsAuthSession authSession = await steamClient.Authentication.BeginAuthSessionViaCredentialsAsync(new AuthSessionDetails { Username = req.Username, Password = req.Password, IsPersistentSession = false, // PlatformType and ClientOSType are optional; omit to avoid enum symbol differences across SteamKit2 versions. Authenticator = new FileAuthenticator(account), }); var pollResponse = await authSession.PollingWaitForResultAsync(); var sessionData = new SessionData() { SteamID = authSession.SteamID.ConvertToUInt64(), AccessToken = pollResponse.AccessToken, RefreshToken = pollResponse.RefreshToken, }; // Serialize cookies var uri = new Uri("https://steamcommunity.com"); var cookies = sessionData.GetCookies().GetCookies(uri).Cast().Select(c => new { name = c.Name, value = c.Value, domain = c.Domain, path = c.Path, expires = c.Expires == DateTime.MinValue ? null : (DateTime?)c.Expires }).ToArray(); var resp = new { session = new { steamId = sessionData.SteamID, accessToken = sessionData.AccessToken, refreshToken = sessionData.RefreshToken, sessionId = sessionData.SessionID }, cookies = cookies }; http.Response.ContentType = "application/json"; await http.Response.WriteAsync(JsonConvert.SerializeObject(resp, Formatting.Indented)); return; } catch (Exception ex) { http.Response.StatusCode = 500; await http.Response.WriteAsync("login failed: " + ex.Message); return; } }); app.MapPost("/wallet-balance", async (HttpContext http) => { var req = await GetWalletBalanceRequestBodyAsync(http); if (req == null || string.IsNullOrWhiteSpace(req.SteamLoginSecure)) { http.Response.StatusCode = 400; await http.Response.WriteAsync("invalid request"); return; } try { var handler = CreateHttpClientHandler(req.ProxyUrl, app.Logger, "wallet-balance"); using var client = new HttpClient(handler, disposeHandler: true) { Timeout = TimeSpan.FromSeconds(20) }; var request = new HttpRequestMessage(HttpMethod.Get, "https://store.steampowered.com/api/getfundwalletinfo/?cc=us&l=english"); request.Headers.Add("Cookie", $"steamLoginSecure={req.SteamLoginSecure}"); var response = await client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { http.Response.StatusCode = 500; await http.Response.WriteAsync($"wallet request failed: {(int)response.StatusCode}"); return; } var json = JsonConvert.DeserializeObject(body); if (json == null || json["success"]?.Value() != 1 || json["user_wallet"] is not JObject wallet) { http.Response.StatusCode = 500; await http.Response.WriteAsync("wallet response invalid"); return; } var resp = new { amount = wallet["amount"]?.Value() ?? "0", currency = wallet["currency"]?.Value() ?? "USD" }; http.Response.ContentType = "application/json"; await http.Response.WriteAsync(JsonConvert.SerializeObject(resp)); } catch (Exception ex) { http.Response.StatusCode = 500; await http.Response.WriteAsync("wallet query failed: " + ex.Message); } }); app.Run(); async Task GetRequestBodyAsync(HttpContext http) { try { using var sr = new StreamReader(http.Request.Body); var body = await sr.ReadToEndAsync(); if (string.IsNullOrWhiteSpace(body)) return null; return JsonConvert.DeserializeObject(body); } catch { return null; } } async Task GetWalletBalanceRequestBodyAsync(HttpContext http) { try { using var sr = new StreamReader(http.Request.Body); var body = await sr.ReadToEndAsync(); if (string.IsNullOrWhiteSpace(body)) return null; return JsonConvert.DeserializeObject(body); } catch { return null; } } SteamClient CreateSteamClient(string? requestProxyUrl, ILogger logger, string endpointName) { var (proxyUrl, source) = ResolveProxyUrl(requestProxyUrl); logger.LogInformation( "[{Endpoint}] Proxy resolved. source={Source}, value={Proxy}", endpointName, source, MaskProxyUrlForLog(proxyUrl) ); if (string.IsNullOrWhiteSpace(proxyUrl)) { logger.LogInformation("[{Endpoint}] Proxy disabled for this request.", endpointName); return new SteamClient(); } if (!Uri.TryCreate(proxyUrl, UriKind.Absolute, out var proxyUri)) { logger.LogWarning( "[{Endpoint}] Proxy url invalid. raw={RawProxy}", endpointName, MaskProxyUrlForLog(proxyUrl) ); throw new ArgumentException("invalid proxy url, expected format like http://127.0.0.1:7890"); } var config = SteamConfiguration.Create(builder => { // Force WebSocket so proxy can be applied through HttpClient. builder.WithProtocolTypes(ProtocolTypes.WebSocket); builder.WithHttpClientFactory(() => { var handler = new HttpClientHandler { Proxy = new WebProxy(proxyUri), UseProxy = true }; return new HttpClient(handler, disposeHandler: true); }); }); return new SteamClient(config); } HttpClientHandler CreateHttpClientHandler(string? requestProxyUrl, ILogger logger, string endpointName) { var (proxyUrl, source) = ResolveProxyUrl(requestProxyUrl); logger.LogInformation( "[{Endpoint}] Proxy resolved. source={Source}, value={Proxy}", endpointName, source, MaskProxyUrlForLog(proxyUrl) ); var handler = new HttpClientHandler(); if (string.IsNullOrWhiteSpace(proxyUrl)) { logger.LogInformation("[{Endpoint}] Proxy disabled for this request.", endpointName); return handler; } if (!Uri.TryCreate(proxyUrl, UriKind.Absolute, out var proxyUri)) { logger.LogWarning( "[{Endpoint}] Proxy url invalid. raw={RawProxy}", endpointName, MaskProxyUrlForLog(proxyUrl) ); throw new ArgumentException("invalid proxy url, expected format like http://127.0.0.1:7890"); } handler.Proxy = new WebProxy(proxyUri); handler.UseProxy = true; return handler; } (string? ProxyUrl, string Source) ResolveProxyUrl(string? requestProxyUrl) { if (!string.IsNullOrWhiteSpace(requestProxyUrl)) { return (requestProxyUrl, "request.ProxyUrl"); } var envProxy = Environment.GetEnvironmentVariable("STEAM_PROXY_URL"); if (!string.IsNullOrWhiteSpace(envProxy)) { return (envProxy, "env.STEAM_PROXY_URL"); } return (null, "none"); } string MaskProxyUrlForLog(string? proxyUrl) { if (string.IsNullOrWhiteSpace(proxyUrl)) { return ""; } if (!Uri.TryCreate(proxyUrl, UriKind.Absolute, out var uri)) { var trimmed = proxyUrl.Trim(); if (trimmed.Length > 64) { trimmed = trimmed.Substring(0, 64) + "..."; } return $""; } var hostPort = uri.IsDefaultPort ? uri.Host : $"{uri.Host}:{uri.Port}"; return $"{uri.Scheme}://{hostPort}"; } record LoginRequest(string Username, string Password, string MaFilePath, string? ProxyUrl = null); record WalletBalanceRequest(string SteamLoginSecure, string? ProxyUrl = null); internal class FileAuthenticator : IAuthenticator { private readonly SteamGuardAccount? account; private int deviceCodesGenerated = 0; public FileAuthenticator(SteamGuardAccount? account) { this.account = account; } public Task AcceptDeviceConfirmationAsync() { return Task.FromResult(false); } public async Task GetDeviceCodeAsync(bool previousCodeWasIncorrect) { if (previousCodeWasIncorrect) { if (deviceCodesGenerated > 2) throw new Exception("failed to generate device code from maFile"); await Task.Delay(30000); } if (account == null) return null; deviceCodesGenerated++; var code = await account.GenerateSteamGuardCodeAsync(); return code ?? ""; } public Task GetEmailCodeAsync(string email, bool previousCodeWasIncorrect) { // This implementation does not support interactive email codes. return Task.FromResult(string.Empty); } }