Compare commits

..
4 Commits
Author SHA1 Message Date
rmbgame 5c86b53000 ready 1.0.0 2020-12-17 12:10:47 +08:00
rmbgame efb6adbd9b delete ref lib 2020-12-16 10:38:03 +08:00
rmbgame 209202b5cf sync develop 2020-12-16 10:36:52 +08:00
rmbgame c0a081e226 delete ref lib 2020-12-16 10:34:52 +08:00
957 changed files with 6615 additions and 51784 deletions
+13
View File
@@ -0,0 +1,13 @@
### 该问题是怎么引起的?
### 重现步骤
### 报错信息
+15
View File
@@ -0,0 +1,15 @@
### 相关的Issue
### 原因(目的、解决的问题等)
### 描述(做了什么,变更了什么)
### 测试用例(新增、改动、可能影响的功能)
-6
View File
@@ -1,6 +0,0 @@
[submodule "references/Titanium-Web-Proxy"]
path = references/Titanium-Web-Proxy
url = https://github.com/rmbadmin/Titanium-Web-Proxy.git
[submodule "references/MetroRadiance"]
path = references/MetroRadiance
url = https://github.com/rmbadmin/MetroRadiance.git
-12
View File
@@ -1,12 +0,0 @@
<Project>
<PropertyGroup>
<LangVersion>9.0</LangVersion>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition="Exists('..\Unicorn.pfx')">
<SignAssembly>true</SignAssembly>
<DelaySign>false</DelaySign>
<AssemblyOriginatorKeyFile>..\Unicorn.pfx</AssemblyOriginatorKeyFile>
</PropertyGroup>
</Project>
+63 -82
View File
@@ -1,80 +1,79 @@
<h1 align="center">Steam++ Tools 🧰</h1>
# Steam++工具箱 v1.0
<div align="center">
------
「Steam++」is a toolkit that contains a variety of Steam tools, most of which require you to download and install Steam in order to use them.
「Steam++」是一个包含多种Steam工具功能的工具箱。
![Release Download](https://img.shields.io/github/downloads/rmbadmin/SteamTools/latest/total?style=flat-square)
[![Release Version](https://img.shields.io/github/v/release/rmbadmin/SteamTools?style=flat-square)](https://github.com/rmbadmin/SteamTools/releases/latest)
[![GitHub license](https://img.shields.io/github/license/rmbadmin/SteamTools?style=flat-square)](LICENSE)
[![GitHub Star](https://img.shields.io/github/stars/rmbadmin/SteamTools?style=flat-square)](https://github.com/rmbadmin/SteamTools/stargazers)
[![GitHub Fork](https://img.shields.io/github/forks/rmbadmin/SteamTools?style=flat-square)](https://github.com/rmbadmin/SteamTools/network/members)
![GitHub repo size](https://img.shields.io/github/repo-size/rmbadmin/SteamTools?style=flat-square&color=3cb371)
此工具的大部分功能都是需要您下载安装Steam才能使用。
</div>
------
English | [简体中文](./README.md)
## 核心功能
## ✨ Functions
1. Local reverse proxy for Steam's community webpage to enable normal access in **the Chinese Mainland, Mainland of China**
2. Quickly switch the Steam account that the current PC has remembered to log into
- This feature is to read the local user login records stored under the Steam path to directly display the operation, you can switch between multiple accounts without re-entering passwords and tokens.
3. Achievement stat modifications for Steam games
- Function reference SteamAchievementManager for secondary development, modified the game list loading and operation ease of use.
4. Steam Local Two-Step Authenticator
- The function reference [WinAuth](https://github.com/winauth/winauth) is developed to enable you to view your token without launching the mobile version of Steam App, similar function software are [WinAuth](https://github.com/winauth/winauth), [SteamDesktopAuthenticator](https://github.com/Jessecar96/SteamDesktopAuthenticator).
5. Some game tools
- Currently there is a forced game borderless windowing, CSGO fix VAC shield.
### 1. 反代Steam的社区网页使其能正常访问
## 🖥 Operating Environment
- Windows 10 1607+
- Windows 8.1 / Windows Server 2012 R2
- Prerequisites
- Microsoft Visual C++ 2015-2019 Redistributable [64 bit](https://aka.ms/vs/16/release/vc_redist.x64.exe) / [32 bit](https://aka.ms/vs/16/release/vc_redist.x86.exe)
- Windows 7 SP1 [ESU](https://docs.microsoft.com/troubleshoot/windows-client/windows-7-eos-faq/windows-7-extended-security-updates-faq)
- Prerequisites
- Microsoft Visual C++ 2015-2019 Redistributable [64 bit](https://aka.ms/vs/16/release/vc_redist.x64.exe) / [32 bit](https://aka.ms/vs/16/release/vc_redist.x86.exe)
- KB3063858 [64 bit](https://www.microsoft.com/download/details.aspx?id=47442) / [32 bit](https://www.microsoft.com/download/details.aspx?id=47409)
- ~~macOS 10.13 High Sierra Or Higher~~
<!--
- ~~Alpine Linux 3.11+~~
- ~~CentOS 7+~~
- ~~Debian 9+~~
- ~~Fedora 32+~~
- ~~Linux Mint 18+~~
- ~~openSUSE 15+~~
- ~~Red Hat Enterprise Linux 7+~~
- ~~SUSE Enterprise Linux (SLES) 12 SP2+~~
- ~~Ubuntu 20.10, 20.04, 18.04, 16.04~~
- ~~Android 5.0+~~
- ~~iOS 8.0+~~
-->
功能类似羽翼城大佬的Steam302,使用[Titanium-Web-Proxy](https://github.com/justcoding121/Titanium-Web-Proxy)开源项目进行本地反代,相比302工具具有更快的启动速度,以及支持简单的脚本注入。
## 🌎 Roadmap
Read what we [milestones](https://github.com/SteamTools-Team/SteamTools/milestones), and feel free to ask questions.
---
### 2. 快速切换当前PC已经记住登陆的Steam账号
## ⌨️ Development Environment
[Visual Studio 2019 Version 16.8 Or Higher](https://visualstudio.microsoft.com/vs/) Or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/) Or [JetBrains Rider](https://www.jetbrains.com/rider/) Or [Visual Studio Code](https://code.visualstudio.com/)
- Workload
- Web and Cloud
- ASP.NET and Web Development
- Desktop and Mobile Applications
- .NET Desktop Development
- Mobile Development using .Net
- Other Toolsets
- .NET Core Cross Platform Development
- Single Component
- GitHub Extension for Visual Studio
- [Visual Studio Marketplace](https://marketplace.visualstudio.com/)
- [Avalonia for Visual Studio](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio)
- [NUnit VS Templates](https://marketplace.visualstudio.com/items?itemName=NUnitDevelopers.NUnitTemplatesforVisualStudio)
该功能是读取Steam路径下存储的本地用户登录记录直接展示操作,可以多账号切换无需重新输入密码和令牌。
## 📄 Thanks to the following Open Source Projects
---
### 3. Steam游戏的成就统计管理功能
功能参考[SteamAchievementManager](https://github.com/gibbed/SteamAchievementManager)进行二次开发,修改了游戏列表的加载和操作易用性。
---
### 4. Steam本地两步身份验证器
功能参考[WinAuth](https://github.com/winauth/winauth)开发,可以使您不用启动移动版Steam App也能查看您的令牌,功能类似的软件有[WinAuth](https://github.com/winauth/winauth)、[SteamDesktopAuthenticator](https://github.com/Jessecar96/SteamDesktopAuthenticator)。
---
### 5. 一些游戏工具
目前已有强制游戏无边框窗口化,CSGO修复VAC屏蔽。
这一块是随缘做一些我经常用或者闲着没事捣鼓的功能。
---
## 预计后续添加的功能
### Steam自动挂卡
尝试用社区反代功能结合成就解锁功能来重新实现,目的是实现在软件内登录Steam即可直接获取徽章卡片信息并开始挂卡。
### Steam皮肤设计器
挖坑画大饼,可视化编辑Steam皮肤,而且如果软件能上架Steam的话打算支持创意工坊分享设计的Steam皮肤,短期内肯定做不完。
### 插件形式加载运行ASF
以插件形式支持ASF在工具内运行并增强ASF在Windows Desktop环境下的使用。
### Steam自定义封面管理
增强Steam自定义封面的管理以及从[SteamGridDB](https://www.steamgriddb.com/)快速匹配下载应用封面。
### 更多其它游戏平台功能
### 更多非作弊游戏功能
---
## 运行环境
> 程序使用C# WPF在 .NET Framework4.7.2环境下开发,如果无法运行请下载安装[.NET Framework 4.7.2](https://dotnet.microsoft.com/download/dotnet-framework/net472)。
---
## License
> [GPL-3.0 License](LICENSE.txt)
> 根据GPLv3许可发布的开源/免费软件。
---
## 感谢以下开源项目
* [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json)
* [MetroRadiance](https://github.com/Grabacr07/MetroRadiance)
* [MetroTrilithon](https://github.com/Grabacr07/MetroTrilithon)
* [Livet](https://github.com/runceel/Livet)
* [StatefulModel](https://github.com/ugaya40/StatefulModel)
* [StatefulModel](https://github.com/Grabacr07/MetroTrilithonhttps://github.com/ugaya40/StatefulModel)
* [Hardcodet.NotifyIcon](https://github.com/HavenDV/Hardcodet.NotifyIcon.Wpf.NetCore)
* [System.Reactive](https://github.com/dotnet/reactive)
* [Titanium-Web-Proxy](https://github.com/justcoding121/Titanium-Web-Proxy)
@@ -84,22 +83,4 @@ Read what we [milestones](https://github.com/SteamTools-Team/SteamTools/mileston
* [SteamAchievementManager](https://github.com/gibbed/SteamAchievementManager)
* [ArchiSteamFarm](https://github.com/JustArchiNET/ArchiSteamFarm)
* [WinAuth](https://github.com/winauth/winauth)
* [SteamDesktopAuthenticator](https://github.com/Jessecar96/SteamDesktopAuthenticator)
* [Gameloop.Vdf](https://github.com/shravan2x/Gameloop.Vdf)
* [Costura.Fody](https://github.com/Fody/Costura)
* [MessagePack-CSharp](https://github.com/neuecc/MessagePack-CSharp)
* [Nito.Comparers](https://github.com/StephenCleary/Comparers)
* [Crc32.NET](https://github.com/force-net/Crc32.NET)
* [gfoidl.Base64](https://github.com/gfoidl/Base64)
* [sqlite-net-pcl](https://github.com/praeclarum/sqlite-net)
* [Polly](https://github.com/App-vNext/Polly)
* [NLog](https://github.com/nlog/NLog)
* [NUnit](https://github.com/nunit/nunit)
* [ReactiveUI](https://github.com/reactiveui/reactiveui)
* [MessageBox.Avalonia](https://github.com/AvaloniaUtils/MessageBox.Avalonia)
* [AvaloniaUI](https://github.com/AvaloniaUI/Avalonia)
* [EntityFrameworkCore](https://github.com/dotnet/efcore)
* [ASP.NET Core](https://github.com/dotnet/aspnetcore)
* [Xamarin.Essentials](https://github.com/xamarin/essentials)
* [.NET Extensions](https://github.com/dotnet/extensions)
* [.NET Runtime](https://github.com/dotnet/runtime)
* [SteamDesktopAuthenticator](https://github.com/Jessecar96/SteamDesktopAuthenticator)
+63 -89
View File
@@ -1,87 +1,79 @@
<h1 align="center">Steam++ 工具箱 🧰</h1>
# Steam++工具箱 v1.0
<div align="center">
------
「Steam++」是一个包含多种Steam工具功能的工具箱,此工具的大部分功能都是需要您下载安装Steam才能使用。
「Steam++」是一个包含多种Steam工具功能的工具箱。
![Release Download](https://img.shields.io/github/downloads/rmbadmin/SteamTools/latest/total?style=flat-square)
[![Release Version](https://img.shields.io/github/v/release/rmbadmin/SteamTools?style=flat-square)](https://github.com/rmbadmin/SteamTools/releases/latest)
[![GitHub license](https://img.shields.io/github/license/rmbadmin/SteamTools?style=flat-square)](LICENSE)
[![GitHub Star](https://img.shields.io/github/stars/rmbadmin/SteamTools?style=flat-square)](https://github.com/rmbadmin/SteamTools/stargazers)
[![GitHub Fork](https://img.shields.io/github/forks/rmbadmin/SteamTools?style=flat-square)](https://github.com/rmbadmin/SteamTools/network/members)
![GitHub repo size](https://img.shields.io/github/repo-size/rmbadmin/SteamTools?style=flat-square&color=3cb371)
此工具的大部分功能都是需要您下载安装Steam才能使用。
</div>
------
[English](./README.en.md) | 简体中文
## 核心功能
## ✨ 功能
1. 反代 Steam 的社区网页使其能正常访问
- 功能类似羽翼城大佬的 [steamcommunity_302](https://www.dogfight360.com/blog/686/)
- 使用 [Titanium-Web-Proxy](https://github.com/justcoding121/Titanium-Web-Proxy) 开源项目进行本地反代,使国内用户可以正常访问steam社区页
- 相比 **302工具** 具有更快的启动速度,以及支持简单的脚本注入(还顺便支持了Pixiv、Discord、Twitch等网站的反代支持)
2. 快速切换当前设备已记住登陆的 Steam 账号
- 该功能是读取 Steam 路径下存储的本地用户登录记录直接展示操作,可以多账号切换无需重新输入密码和令牌
3. Steam游戏的成就统计管理功能
- 功能参考 [SteamAchievementManager](https://github.com/gibbed/SteamAchievementManager) 进行二次开发,修改了游戏列表的加载和操作易用性
4. Steam本地两步身份验证器
- 功能参考 [WinAuth](https://github.com/winauth/winauth) 开发,可以使您不用启动移动版 Steam App 也能查看您的令牌
- 本地令牌交易市场报价确认
- 功能类似的软件例如:
- [WinAuth](https://github.com/winauth/winauth)
- [SteamDesktopAuthenticator](https://github.com/Jessecar96/SteamDesktopAuthenticator)
5. 其他游戏工具
- 目前已有强制游戏无边框窗口化,CSGO 修复 VAC 屏蔽
### 1. 反代Steam的社区网页使其能正常访问
## 🖥 系统要求
- Windows 10 1607+
- Windows 8.1 / Windows Server 2012 R2
- 先决条件
- Microsoft Visual C++ 2015-2019 Redistributable [64 位](https://aka.ms/vs/16/release/vc_redist.x64.exe) / [32 位](https://aka.ms/vs/16/release/vc_redist.x86.exe)
- Windows 7 SP1 [ESU](https://docs.microsoft.com/zh-cn/troubleshoot/windows-client/windows-7-eos-faq/windows-7-extended-security-updates-faq)
- 先决条件
- Microsoft Visual C++ 2015-2019 Redistributable [64 位](https://aka.ms/vs/16/release/vc_redist.x64.exe) / [32 位](https://aka.ms/vs/16/release/vc_redist.x86.exe)
- KB3063858 [64 位](https://www.microsoft.com/zh-cn/download/details.aspx?id=47442) / [32 位](https://www.microsoft.com/zh-cn/download/details.aspx?id=47409)
- ~~macOS 10.13 High Sierra 或更高版本~~
<!--
- ~~Alpine Linux 3.11+~~
- ~~CentOS 7+~~
- ~~Debian 9+~~
- ~~Fedora 32+~~
- ~~Linux Mint 18+~~
- ~~openSUSE 15+~~
- ~~Red Hat Enterprise Linux 7+~~
- ~~SUSE Enterprise Linux (SLES) 12 SP2+~~
- ~~Ubuntu 20.10, 20.04, 18.04, 16.04~~
- ~~Android 5.0+~~
- ~~iOS 8.0+~~
-->
功能类似羽翼城大佬的Steam302,使用[Titanium-Web-Proxy](https://github.com/justcoding121/Titanium-Web-Proxy)开源项目进行本地反代,相比302工具具有更快的启动速度,以及支持简单的脚本注入。
## 🌎 路线图
查看这个 [milestones](https://github.com/SteamTools-Team/SteamTools/milestones) 来了解我们下一步的开发计划,并随时提出问题。
---
### 2. 快速切换当前PC已经记住登陆的Steam账号
## ⌨️ 开发环境
[Visual Studio 2019 版本 16.8 或更高版本](https://visualstudio.microsoft.com/zh-hans/vs/) 或 [Visual Studio for Mac](https://visualstudio.microsoft.com/zh-hans/vs/mac/) 或 [JetBrains Rider](https://www.jetbrains.com/rider/) 或 [Visual Studio Code](https://code.visualstudio.com/)
- 工作负载
- Web 和云
- ASP.NET 和 Web 开发
- 桌面应用和移动应用
- .NET 桌面开发
- 使用 .NET 的移动开发
- 其他工具集
- .NET Core 跨平台开发
- 单个组件
- GitHub Extension for Visual Studio
- [Visual Studio Marketplace](https://marketplace.visualstudio.com/)
- [Avalonia for Visual Studio](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio)
- [NUnit VS Templates](https://marketplace.visualstudio.com/items?itemName=NUnitDevelopers.NUnitTemplatesforVisualStudio)
该功能是读取Steam路径下存储的本地用户登录记录直接展示操作,可以多账号切换无需重新输入密码和令牌。
## 📄 感谢以下开源项目
---
### 3. Steam游戏的成就统计管理功能
功能参考[SteamAchievementManager](https://github.com/gibbed/SteamAchievementManager)进行二次开发,修改了游戏列表的加载和操作易用性。
---
### 4. Steam本地两步身份验证器
功能参考[WinAuth](https://github.com/winauth/winauth)开发,可以使您不用启动移动版Steam App也能查看您的令牌,功能类似的软件有[WinAuth](https://github.com/winauth/winauth)、[SteamDesktopAuthenticator](https://github.com/Jessecar96/SteamDesktopAuthenticator)。
---
### 5. 一些游戏工具
目前已有强制游戏无边框窗口化,CSGO修复VAC屏蔽。
这一块是随缘做一些我经常用或者闲着没事捣鼓的功能。
---
## 预计后续添加的功能
### Steam自动挂卡
尝试用社区反代功能结合成就解锁功能来重新实现,目的是实现在软件内登录Steam即可直接获取徽章卡片信息并开始挂卡。
### Steam皮肤设计器
挖坑画大饼,可视化编辑Steam皮肤,而且如果软件能上架Steam的话打算支持创意工坊分享设计的Steam皮肤,短期内肯定做不完。
### 插件形式加载运行ASF
以插件形式支持ASF在工具内运行并增强ASF在Windows Desktop环境下的使用。
### Steam自定义封面管理
增强Steam自定义封面的管理以及从[SteamGridDB](https://www.steamgriddb.com/)快速匹配下载应用封面。
### 更多其它游戏平台功能
### 更多非作弊游戏功能
---
## 运行环境
> 程序使用C# WPF在 .NET Framework4.7.2环境下开发,如果无法运行请下载安装[.NET Framework 4.7.2](https://dotnet.microsoft.com/download/dotnet-framework/net472)。
---
## License
> [GPL-3.0 License](LICENSE.txt)
> 根据GPLv3许可发布的开源/免费软件。
---
## 感谢以下开源项目
* [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json)
* [MetroRadiance](https://github.com/Grabacr07/MetroRadiance)
* [MetroTrilithon](https://github.com/Grabacr07/MetroTrilithon)
* [Livet](https://github.com/runceel/Livet)
* [StatefulModel](https://github.com/ugaya40/StatefulModel)
* [StatefulModel](https://github.com/Grabacr07/MetroTrilithonhttps://github.com/ugaya40/StatefulModel)
* [Hardcodet.NotifyIcon](https://github.com/HavenDV/Hardcodet.NotifyIcon.Wpf.NetCore)
* [System.Reactive](https://github.com/dotnet/reactive)
* [Titanium-Web-Proxy](https://github.com/justcoding121/Titanium-Web-Proxy)
@@ -91,22 +83,4 @@
* [SteamAchievementManager](https://github.com/gibbed/SteamAchievementManager)
* [ArchiSteamFarm](https://github.com/JustArchiNET/ArchiSteamFarm)
* [WinAuth](https://github.com/winauth/winauth)
* [SteamDesktopAuthenticator](https://github.com/Jessecar96/SteamDesktopAuthenticator)
* [Gameloop.Vdf](https://github.com/shravan2x/Gameloop.Vdf)
* [Costura.Fody](https://github.com/Fody/Costura)
* [MessagePack-CSharp](https://github.com/neuecc/MessagePack-CSharp)
* [Nito.Comparers](https://github.com/StephenCleary/Comparers)
* [Crc32.NET](https://github.com/force-net/Crc32.NET)
* [gfoidl.Base64](https://github.com/gfoidl/Base64)
* [sqlite-net-pcl](https://github.com/praeclarum/sqlite-net)
* [Polly](https://github.com/App-vNext/Polly)
* [NLog](https://github.com/nlog/NLog)
* [NUnit](https://github.com/nunit/nunit)
* [ReactiveUI](https://github.com/reactiveui/reactiveui)
* [MessageBox.Avalonia](https://github.com/AvaloniaUtils/MessageBox.Avalonia)
* [AvaloniaUI](https://github.com/AvaloniaUI/Avalonia)
* [EntityFrameworkCore](https://github.com/dotnet/efcore)
* [ASP.NET Core](https://github.com/dotnet/aspnetcore)
* [Xamarin.Essentials](https://github.com/xamarin/essentials)
* [.NET Extensions](https://github.com/dotnet/extensions)
* [.NET Runtime](https://github.com/dotnet/runtime)
* [SteamDesktopAuthenticator](https://github.com/Jessecar96/SteamDesktopAuthenticator)
+249
View File
@@ -0,0 +1,249 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using WinAuth;
namespace SteamTool.Auth
{
public static class AuthService
{
/// <summary>
/// Import a file containing authenticators in the KeyUriFormat. The file might be plain text, encrypted zip or encrypted pgp.
/// </summary>
/// <param name="parent">parent Form</param>
/// <param name="file">file name to import</param>
/// <returns>list of imported authenticators</returns>
public static List<WinAuthAuthenticator> ImportAuthenticators(string file)
{
List<WinAuthAuthenticator> authenticators = new List<WinAuthAuthenticator>();
StringBuilder lines = new StringBuilder();
bool retry;
do
{
retry = false;
lines.Length = 0;
// read a plain text file
lines.Append(File.ReadAllText(file));
} while (retry);
int linenumber = 0;
try
{
using (var sr = new StringReader(lines.ToString()))
{
string line;
while ((line = sr.ReadLine()) != null)
{
linenumber++;
// ignore blank lines or comments
line = line.Trim();
if (line.Length == 0 || line.IndexOf("#") == 0)
{
continue;
}
// bug if there is a hash before ?
var hash = line.IndexOf("#");
var qm = line.IndexOf("?");
if (hash != -1 && hash < qm)
{
line = line.Substring(0, hash) + "%23" + line.Substring(hash + 1);
}
// parse and validate URI
var uri = new Uri(line);
// we only support "otpauth"
if (uri.Scheme != "otpauth")
{
throw new ApplicationException("Import only supports otpauth://");
}
// we only support totp (not hotp)
if (uri.Host != "totp" && uri.Host != "hotp")
{
throw new ApplicationException("Import only supports otpauth://totp/ or otpauth://hotp/");
}
// get the label and optional issuer
string issuer = string.Empty;
string label = (string.IsNullOrEmpty(uri.LocalPath) == false ? uri.LocalPath.Substring(1) : string.Empty); // skip past initial /
int p = label.IndexOf(":");
if (p != -1)
{
issuer = label.Substring(0, p);
label = label.Substring(p + 1);
}
// + aren't decoded
label = label.Replace("+", " ");
var query = HttpUtility.ParseQueryString(uri.Query);
string secret = query["secret"];
if (string.IsNullOrEmpty(secret) == true)
{
throw new ApplicationException("Authenticator does not contain secret");
}
string counter = query["counter"];
if (uri.Host == "hotp" && string.IsNullOrEmpty(counter) == true)
{
throw new ApplicationException("HOTP authenticator should have a counter");
}
WinAuthAuthenticator importedAuthenticator = new WinAuthAuthenticator
{
AutoRefresh = false
};
//
Authenticator auth;
if (string.Compare(issuer, "BattleNet", true) == 0)
{
string serial = query["serial"];
if (string.IsNullOrEmpty(serial) == true)
{
throw new ApplicationException("Battle.net Authenticator does not have a serial");
}
serial = serial.ToUpper();
if (Regex.IsMatch(serial, @"^[A-Z]{2}-?[\d]{4}-?[\d]{4}-?[\d]{4}$") == false)
{
throw new ApplicationException("Invalid serial for Battle.net Authenticator");
}
auth = new BattleNetAuthenticator();
//char[] decoded = Base32.getInstance().Decode(secret).Select(c => Convert.ToChar(c)).ToArray(); // this is hex string values
//string hex = new string(decoded);
//((BattleNetAuthenticator)auth).SecretKey = Authenticator.StringToByteArray(hex);
((BattleNetAuthenticator)auth).SecretKey = Base32.getInstance().Decode(secret);
((BattleNetAuthenticator)auth).Serial = serial;
issuer = string.Empty;
}
else if (string.Compare(issuer, "Steam", true) == 0)
{
auth = new SteamAuthenticator();
((SteamAuthenticator)auth).SecretKey = Base32.getInstance().Decode(secret);
((SteamAuthenticator)auth).Serial = string.Empty;
((SteamAuthenticator)auth).DeviceId = query["deviceid"] ?? string.Empty;
((SteamAuthenticator)auth).SteamData = query["data"] ?? string.Empty;
issuer = string.Empty;
}
else if (uri.Host == "hotp")
{
auth = new HOTPAuthenticator();
((HOTPAuthenticator)auth).SecretKey = Base32.getInstance().Decode(secret);
((HOTPAuthenticator)auth).Counter = int.Parse(counter);
if (string.IsNullOrEmpty(issuer) == false)
{
auth.Issuer = issuer;
}
}
else // if (string.Compare(issuer, "Google", true) == 0)
{
auth = new GoogleAuthenticator();
((GoogleAuthenticator)auth).Enroll(secret);
if (string.Compare(issuer, "Google", true) == 0)
{
issuer = string.Empty;
}
else if (string.IsNullOrEmpty(issuer) == false)
{
auth.Issuer = issuer;
}
}
int period = 0;
int.TryParse(query["period"], out period);
if (period != 0)
{
auth.Period = period;
}
int digits = 0;
int.TryParse(query["digits"], out digits);
if (digits != 0)
{
auth.CodeDigits = digits;
}
Authenticator.HMACTypes hmactype;
if (Enum.TryParse<Authenticator.HMACTypes>(query["algorithm"], true, out hmactype) == true)
{
auth.HMACType = hmactype;
}
//
if (label.Length != 0)
{
importedAuthenticator.Name = (issuer.Length != 0 ? issuer + " (" + label + ")" : label);
}
else if (issuer.Length != 0)
{
importedAuthenticator.Name = issuer;
}
else
{
importedAuthenticator.Name = "Imported";
}
//
importedAuthenticator.AuthenticatorData = auth;
// sync
importedAuthenticator.Sync();
authenticators.Add(importedAuthenticator);
}
}
return authenticators;
}
catch (UriFormatException ex)
{
throw new ImportException(string.Format("Invalid authenticator at line {0}", linenumber), ex);
}
catch (Exception ex)
{
throw new ImportException(string.Format("Error importing at line {0}:{1}", ex.Message), ex);
}
}
public static string ExportAuthenticators(IList<WinAuthAuthenticator> authenticators, string password)
{
// create file in memory
using (var ms = new MemoryStream())
{
using (var sw = new StreamWriter(ms))
{
List<WinAuthAuthenticator> unprotected = new List<WinAuthAuthenticator>();
foreach (var auth in authenticators)
{
unprotected.Add(auth);
string line = auth.ToUrl();
sw.WriteLine(line);
}
// reprotect
foreach (var auth in unprotected)
{
auth.AuthenticatorData.Protect();
}
// reset and write stream out to disk or as zip
sw.Flush();
ms.Seek(0, SeekOrigin.Begin);
var result = Encoding.UTF8.GetString(ms.ToArray());
return result;
}
}
}
}
}
+19
View File
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SteamTool.Auth
{
/// <summary>
/// Exception class wrapper for importing error
/// </summary>
public class ImportException : ApplicationException
{
public ImportException() : base() { }
public ImportException(string message) : base(message) { }
public ImportException(string message, Exception ex) : base(message, ex) { }
}
}
+18
View File
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Platforms>AnyCPU;x86</Platforms>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\SteamTool.Authenticator\SteamTool.Authenticator.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="PresentationCore">
<HintPath>C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\PresentationCore.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
+22
View File
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace SteamTool.Auth.Win32
{
public class WinAPI
{
public const string LibraryName = "user32.dll";
[DllImport(LibraryName, CharSet = CharSet.Auto)]
internal static extern IntPtr GetOpenClipboardWindow();
[DllImport(LibraryName, SetLastError = true, CharSet = CharSet.Auto)]
internal static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport(LibraryName, CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
}
}
+597
View File
@@ -0,0 +1,597 @@
using SteamTool.Auth.Win32;
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Windows;
using System.Xml;
using WinAuth;
namespace SteamTool.Auth
{
public interface IWinAuthAuthenticatorChangedListener
{
void OnWinAuthAuthenticatorChanged(WinAuthAuthenticator sender, WinAuthAuthenticatorChangedEventArgs e);
}
/// <summary>
/// Wrapper for real authenticator data used to save to file with other application information
/// </summary>
public class WinAuthAuthenticator : ICloneable
{
/// <summary>
/// Event handler fired when property is changed
/// </summary>
public event WinAuthAuthenticatorChangedHandler OnWinAuthAuthenticatorChanged;
/// <summary>
/// Unique Id of authenticator saved in config
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// Index for authenticator when in sorted list
/// </summary>
public int Index { get; set; }
/// <summary>
/// Actual authenticator data
/// </summary>
public Authenticator AuthenticatorData { get; set; }
/// <summary>
/// When this authenticator was created
/// </summary>
public DateTime Created { get; set; }
private string _name;
private bool _autoRefresh;
private bool _allowCopy;
private bool _copyOnCode;
private bool _hideSerial;
/// <summary>
/// Create the authenticator wrapper
/// </summary>
public WinAuthAuthenticator()
{
Id = Guid.NewGuid();
Created = DateTime.Now;
_autoRefresh = true;
}
/// <summary>
/// Clone this authenticator
/// </summary>
/// <returns></returns>
public object Clone()
{
WinAuthAuthenticator clone = this.MemberwiseClone() as WinAuthAuthenticator;
clone.Id = Guid.NewGuid();
clone.OnWinAuthAuthenticatorChanged = null;
clone.AuthenticatorData = (this.AuthenticatorData != null ? this.AuthenticatorData.Clone() as Authenticator : null);
return clone;
}
/// <summary>
/// Mark this authenticator as having changed
/// </summary>
public void MarkChanged()
{
if (OnWinAuthAuthenticatorChanged != null)
{
OnWinAuthAuthenticatorChanged(this, new WinAuthAuthenticatorChangedEventArgs());
}
}
/// <summary>
/// Get/set the name of this authenticator
/// </summary>
public string Name
{
get
{
return _name;
}
set
{
_name = value;
if (OnWinAuthAuthenticatorChanged != null)
{
OnWinAuthAuthenticatorChanged(this, new WinAuthAuthenticatorChangedEventArgs("Name"));
}
}
}
/// <summary>
/// Get/set auto refresh flag
/// </summary>
public bool AutoRefresh
{
get
{
if (this.AuthenticatorData != null && this.AuthenticatorData is HOTPAuthenticator)
{
return false;
}
else
{
return _autoRefresh;
}
}
set
{
// HTOP must always be false
if (this.AuthenticatorData != null && this.AuthenticatorData is HOTPAuthenticator)
{
_autoRefresh = false;
}
else
{
_autoRefresh = value;
}
if (OnWinAuthAuthenticatorChanged != null)
{
OnWinAuthAuthenticatorChanged(this, new WinAuthAuthenticatorChangedEventArgs("AutoRefresh"));
}
}
}
/// <summary>
/// Get/set allow copy flag
/// </summary>
public bool AllowCopy
{
get
{
return _allowCopy;
}
set
{
_allowCopy = value;
if (OnWinAuthAuthenticatorChanged != null)
{
OnWinAuthAuthenticatorChanged(this, new WinAuthAuthenticatorChangedEventArgs("AllowCopy"));
}
}
}
/// <summary>
/// Get/set auto copy flag
/// </summary>
public bool CopyOnCode
{
get
{
return _copyOnCode;
}
set
{
_copyOnCode = value;
if (OnWinAuthAuthenticatorChanged != null)
{
OnWinAuthAuthenticatorChanged(this, new WinAuthAuthenticatorChangedEventArgs("CopyOnCode"));
}
}
}
/// <summary>
/// Get/set hide serial flag
/// </summary>
public bool HideSerial
{
get
{
return _hideSerial;
}
set
{
_hideSerial = value;
if (OnWinAuthAuthenticatorChanged != null)
{
OnWinAuthAuthenticatorChanged(this, new WinAuthAuthenticatorChangedEventArgs("HideSerial"));
}
}
}
public string CurrentCode
{
get
{
if (this.AuthenticatorData == null)
{
return null;
}
string code = this.AuthenticatorData.CurrentCode;
if (this.AuthenticatorData is HOTPAuthenticator)
{
if (OnWinAuthAuthenticatorChanged != null)
{
OnWinAuthAuthenticatorChanged(this, new WinAuthAuthenticatorChangedEventArgs("HOTP", this.AuthenticatorData));
}
}
return code;
}
}
/// <summary>
/// Sync the current authenticator's time with its server
/// </summary>
public void Sync()
{
if (AuthenticatorData != null)
{
try
{
AuthenticatorData.Sync();
}
catch (EncryptedSecretDataException)
{
// reset lastsync to force sync on next decryption
}
}
}
/// <summary>
/// Copy the current code to the clipboard
/// </summary>
public void CopyCodeToClipboard(string code = null, bool showError = false)
{
if (code == null)
{
code = this.CurrentCode;
}
bool clipRetry = false;
do
{
bool failed = false;
// check if the clipboard is locked
IntPtr hWnd = WinAPI.GetOpenClipboardWindow();
if (hWnd != IntPtr.Zero)
{
int len = WinAPI.GetWindowTextLength(hWnd);
if (len == 0)
{
//WinAuthMain.LogException(new ApplicationException("Clipboard in use by another process"));
}
else
{
StringBuilder sb = new StringBuilder(len + 1);
WinAPI.GetWindowText(hWnd, sb, sb.Capacity);
//WinAuthMain.LogException(new ApplicationException("Clipboard in use by '" + sb.ToString() + "'"));
}
failed = true;
}
else
{
// Issue#170: can still get error copying even though it works, so just increase retries and ignore error
try
{
Clipboard.Clear();
// add delay for clip error
System.Threading.Thread.Sleep(100);
Clipboard.SetDataObject(code, true);
}
catch (ExternalException)
{
}
}
if (failed == true && showError == true)
{
// only show an error the first time
//clipRetry = (MessageBox.Show(form, strings.ClipboardInUse,
// WinAuthMain.APPLICATION_NAME,
// MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes);
}
}
while (clipRetry == true);
}
public bool ReadXml(XmlReader reader, string password)
{
bool changed = false;
Guid id;
if (Guid.TryParse(reader.GetAttribute("id"), out id) == true)
{
Id = id;
}
string authenticatorType = reader.GetAttribute("type");
if (string.IsNullOrEmpty(authenticatorType) == false)
{
Type type = typeof(Authenticator).Assembly.GetType(authenticatorType, false, true);
this.AuthenticatorData = Activator.CreateInstance(type) as Authenticator;
}
//string encrypted = reader.GetAttribute("encrypted");
//if (string.IsNullOrEmpty(encrypted) == false)
//{
// // read the encrypted text from the node
// string data = reader.ReadElementContentAsString();
// // decrypt
// Authenticator.PasswordTypes passwordType;
// data = Authenticator.DecryptSequence(data, encrypted, password, out passwordType);
// using (MemoryStream ms = new MemoryStream(Authenticator.StringToByteArray(data)))
// {
// reader = XmlReader.Create(ms);
// ReadXml(reader, password);
// }
// this.PasswordType = passwordType;
// this.Password = password;
// return;
//}
reader.MoveToContent();
if (reader.IsEmptyElement)
{
reader.Read();
return changed;
}
reader.Read();
while (reader.EOF == false)
{
if (reader.IsStartElement())
{
switch (reader.Name)
{
case "name":
Name = reader.ReadElementContentAsString();
break;
case "created":
long t = reader.ReadElementContentAsLong();
t += Convert.ToInt64(new TimeSpan(new DateTime(1970, 1, 1).Ticks).TotalMilliseconds);
t *= TimeSpan.TicksPerMillisecond;
Created = new DateTime(t).ToLocalTime();
break;
case "autorefresh":
_autoRefresh = reader.ReadElementContentAsBoolean();
break;
case "allowcopy":
_allowCopy = reader.ReadElementContentAsBoolean();
break;
case "copyoncode":
_copyOnCode = reader.ReadElementContentAsBoolean();
break;
case "hideserial":
_hideSerial = reader.ReadElementContentAsBoolean();
break;
case "authenticatordata":
try
{
// we don't pass the password as they are locked till clicked
changed = this.AuthenticatorData.ReadXml(reader) || changed;
}
catch (EncryptedSecretDataException)
{
// no action needed
}
catch (BadPasswordException)
{
// no action needed
}
break;
// v2
case "authenticator":
this.AuthenticatorData = Authenticator.ReadXmlv2(reader, password);
break;
// v2
case "servertimediff":
this.AuthenticatorData.ServerTimeDiff = reader.ReadElementContentAsLong();
break;
default:
reader.Skip();
break;
}
}
else
{
reader.Read();
break;
}
}
return changed;
}
/// <summary>
/// Write the data as xml into an XmlWriter
/// </summary>
/// <param name="writer">XmlWriter to write config</param>
public void WriteXmlString(XmlWriter writer)
{
writer.WriteStartElement(typeof(WinAuthAuthenticator).Name);
writer.WriteAttributeString("id", this.Id.ToString());
if (this.AuthenticatorData != null)
{
writer.WriteAttributeString("type", this.AuthenticatorData.GetType().FullName);
}
//if (this.PasswordType != Authenticator.PasswordTypes.None)
//{
// string data;
// using (MemoryStream ms = new MemoryStream())
// {
// XmlWriterSettings settings = new XmlWriterSettings();
// settings.Indent = true;
// settings.Encoding = Encoding.UTF8;
// using (XmlWriter encryptedwriter = XmlWriter.Create(ms, settings))
// {
// Authenticator.PasswordTypes savedpasswordType = PasswordType;
// PasswordType = Authenticator.PasswordTypes.None;
// WriteXmlString(encryptedwriter);
// PasswordType = savedpasswordType;
// }
// //data = Encoding.UTF8.GetString(ms.ToArray());
// data = Authenticator.ByteArrayToString(ms.ToArray());
// }
// string encryptedTypes;
// data = Authenticator.EncryptSequence(data, PasswordType, Password, out encryptedTypes);
// writer.WriteAttributeString("encrypted", encryptedTypes);
// writer.WriteString(data);
// writer.WriteEndElement();
// return;
//}
writer.WriteStartElement("name");
writer.WriteValue(this.Name ?? string.Empty);
writer.WriteEndElement();
writer.WriteStartElement("created");
writer.WriteValue(Convert.ToInt64((this.Created.ToUniversalTime() - new DateTime(1970, 1, 1)).TotalMilliseconds));
writer.WriteEndElement();
writer.WriteStartElement("autorefresh");
writer.WriteValue(this.AutoRefresh);
writer.WriteEndElement();
//
writer.WriteStartElement("allowcopy");
writer.WriteValue(this.AllowCopy);
writer.WriteEndElement();
//
writer.WriteStartElement("copyoncode");
writer.WriteValue(this.CopyOnCode);
writer.WriteEndElement();
//
writer.WriteStartElement("hideserial");
writer.WriteValue(this.HideSerial);
writer.WriteEndElement();
//
// save the authenticator to the config file
if (this.AuthenticatorData != null)
{
this.AuthenticatorData.WriteToWriter(writer);
// save script with password and generated salt
//if (this.AutoLogin != null)
//{
// this.AutoLogin.WriteXmlString(writer, this.AuthenticatorData.PasswordType, this.AuthenticatorData.Password);
//}
}
writer.WriteEndElement();
}
/// <summary>
/// Create a KeyUriFormat compatible URL
/// See https://code.google.com/p/google-authenticator/wiki/KeyUriFormat
/// </summary>
/// <returns>string</returns>
public virtual string ToUrl(bool compat = false)
{
string type = "totp";
string extraparams = string.Empty;
Match match;
string issuer = this.AuthenticatorData.Issuer;
string label = this.Name;
if (string.IsNullOrEmpty(issuer) == true && (match = Regex.Match(label, @"^([^\(]+)\s+\((.*?)\)(.*)")).Success == true)
{
issuer = match.Groups[1].Value;
label = match.Groups[2].Value + match.Groups[3].Value;
}
if (string.IsNullOrEmpty(issuer) == false && (match = Regex.Match(label, @"^" + issuer + @"\s+\((.*?)\)(.*)")).Success == true)
{
label = match.Groups[1].Value + match.Groups[2].Value;
}
if (string.IsNullOrEmpty(issuer) == false)
{
extraparams += "&issuer=" + HttpUtility.UrlEncode(issuer);
}
if (this.AuthenticatorData.HMACType != Authenticator.DEFAULT_HMAC_TYPE)
{
extraparams += "&algorithm=" + this.AuthenticatorData.HMACType.ToString();
}
if (this.AuthenticatorData is BattleNetAuthenticator)
{
extraparams += "&serial=" + HttpUtility.UrlEncode(((BattleNetAuthenticator)this.AuthenticatorData).Serial.Replace("-", ""));
}
else if (this.AuthenticatorData is SteamAuthenticator)
{
if (compat == false)
{
extraparams += "&deviceid=" + HttpUtility.UrlEncode(((SteamAuthenticator)this.AuthenticatorData).DeviceId);
extraparams += "&data=" + HttpUtility.UrlEncode(((SteamAuthenticator)this.AuthenticatorData).SteamData);
}
}
else if (this.AuthenticatorData is HOTPAuthenticator)
{
type = "hotp";
extraparams += "&counter=" + ((HOTPAuthenticator)this.AuthenticatorData).Counter;
}
string secret = HttpUtility.UrlEncode(Base32.getInstance().Encode(this.AuthenticatorData.SecretKey));
if (this.AuthenticatorData.Period != Authenticator.DEFAULT_PERIOD)
{
extraparams += "&period=" + this.AuthenticatorData.Period;
}
var url = string.Format("otpauth://" + type + "/{0}?secret={1}&digits={2}{3}",
(string.IsNullOrEmpty(issuer) == false ? HttpUtility.UrlPathEncode(issuer) + ":" + HttpUtility.UrlPathEncode(label) : HttpUtility.UrlPathEncode(label)),
secret,
this.AuthenticatorData.CodeDigits,
extraparams);
return url;
}
}
/// <summary>
/// Delegate for ConfigChange event
/// </summary>
/// <param name="source"></param>
/// <param name="args"></param>
public delegate void WinAuthAuthenticatorChangedHandler(WinAuthAuthenticator source, WinAuthAuthenticatorChangedEventArgs args);
/// <summary>
/// Change event arguments
/// </summary>
public class WinAuthAuthenticatorChangedEventArgs : EventArgs
{
public string Property { get; private set; }
public Authenticator Authenticator { get; private set; }
/// <summary>
/// Default constructor
/// </summary>
public WinAuthAuthenticatorChangedEventArgs(string property = null, Authenticator authenticator = null)
{
Property = property;
Authenticator = authenticator;
}
}
}
@@ -0,0 +1,880 @@
/*
* Copyright (C) 2015 Colin Mackie.
* This software is distributed under the terms of the GNU General Public License.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Cache;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.XPath;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Macs;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Paddings;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Generators;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
#if NUNIT
using NUnit.Framework;
#endif
#if NETCF
using OpenNETCF.Security.Cryptography;
#endif
namespace WinAuth
{
/// <summary>
/// Class that implements Steam's SteamGuadrd version of the RFC6238 Authenticator
/// </summary>
public class SteamAuthenticator : Authenticator
{
/// <summary>
/// Number of characters in code
/// </summary>
private const int CODE_DIGITS = 5;
/// <summary>
/// Number of minutes to ignore syncing if network error
/// </summary>
private const int SYNC_ERROR_MINUTES = 60;
/// <summary>
/// Number of attempts to activate
/// </summary>
private const int ENROLL_ACTIVATE_RETRIES = 30;
/// <summary>
/// Incorrect activation code
/// </summary>
private const int INVALID_ACTIVATION_CODE = 89;
/// <summary>
/// Time for http request when calling Sync in ms
/// </summary>
private const int SYNC_TIMEOUT = 5000;
/// <summary>
/// Steam issuer for KeyUri
/// </summary>
private const string STEAM_ISSUER = "Steam";
/// <summary>
/// URLs for all mobile services
/// </summary>
private static string COMMUNITY_BASE = "https://steamcommunity.com";
private static string WEBAPI_BASE = "https://api.steampowered.com";
private static string SYNC_URL = "https://api.steampowered.com:443/ITwoFactorService/QueryTime/v0001";
/// <summary>
/// Character set for authenticator code
/// </summary>
private static char[] STEAMCHARS = new char[] {
'2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C',
'D', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q',
'R', 'T', 'V', 'W', 'X', 'Y'};
/// <summary>
/// Enrolling state
/// </summary>
public class EnrollState
{
public string Username { get; set; }
public string Password { get; set; }
public string CaptchaId { get; set; }
public string CaptchaUrl { get; set; }
public string CaptchaText { get; set; }
public string EmailDomain { get; set; }
public string EmailAuthText { get; set; }
public string ActivationCode { get; set; }
public CookieContainer Cookies { get; set; }
public string SteamId { get; set; }
public string OAuthToken { get; set; }
public bool RequiresLogin { get; set; }
public bool RequiresCaptcha { get; set; }
public bool Requires2FA { get; set; }
public bool RequiresEmailAuth { get; set; }
public bool RequiresActivation { get; set; }
public string RevocationCode { get; set; }
public string SecretKey { get; set; }
public bool Success { get; set; }
public string Error { get; set; }
}
#region Authenticator data
/// <summary>
/// Time of last Sync error
/// </summary>
private static DateTime _lastSyncError = DateTime.MinValue;
/// <summary>
/// Returned serial number of authenticator
/// </summary>
public string Serial { get; set; }
/// <summary>
/// Random device ID we created and registered
/// </summary>
public string DeviceId { get; set; }
/// <summary>
/// JSON steam data
/// </summary>
public string SteamData { get; set; }
/// <summary>
/// JSON session data
/// </summary>
public string SessionData { get; set; }
/// <summary>
///
/// </summary>
public string PollData { get; set; }
/// <summary>
/// Current Steam client instance
/// </summary>
public SteamClient Client { get; protected set; }
#endregion
/// <summary>
/// Expanding offsets to retry when creating first code
/// </summary>
private int[] ENROLL_OFFSETS = new int[] { 0, -30, 30, -60, 60, -90, 90, -120, 120};
/// <summary>
/// Create a new Authenticator object
/// </summary>
public SteamAuthenticator()
: base(CODE_DIGITS)
{
Issuer = STEAM_ISSUER;
}
/// <summary>
/// Get/set the combined secret data value
/// </summary>
public override string SecretData
{
get
{
if (this.Client != null && this.Client.Session != null)
{
this.SessionData = this.Client.Session.ToString();
}
//if (Logger != null)
//{
// Logger.Debug("Get Steam data: {0}, Session:{1}", (SteamData ?? string.Empty).Replace("\n"," ").Replace("\r",""), (SessionData ?? string.Empty).Replace("\n", " ").Replace("\r", ""));
//}
// this is the key | serial | deviceid
return base.SecretData
+ "|" + Authenticator.ByteArrayToString(Encoding.UTF8.GetBytes(Serial))
+ "|" + Authenticator.ByteArrayToString(Encoding.UTF8.GetBytes(DeviceId))
+ "|" + Authenticator.ByteArrayToString(Encoding.UTF8.GetBytes(SteamData))
+ "|" + (string.IsNullOrEmpty(SessionData) == false ? Authenticator.ByteArrayToString(Encoding.UTF8.GetBytes(SessionData)) : string.Empty);
}
set
{
// extract key + serial + deviceid
if (string.IsNullOrEmpty(value) == false)
{
string[] parts = value.Split('|');
base.SecretData = value;
Serial = (parts.Length > 1 ? Encoding.UTF8.GetString(Authenticator.StringToByteArray(parts[1])) : null);
DeviceId = (parts.Length > 2 ? Encoding.UTF8.GetString(Authenticator.StringToByteArray(parts[2])) : null);
SteamData = (parts.Length > 3 ? Encoding.UTF8.GetString(Authenticator.StringToByteArray(parts[3])) : string.Empty);
if (string.IsNullOrEmpty(SteamData) == false && SteamData[0] != '{')
{
// convert old recovation code into SteamData json
SteamData = "{\"revocation_code\":\"" + SteamData + "\"}";
}
string session = (parts.Length > 4 ? Encoding.UTF8.GetString(Authenticator.StringToByteArray(parts[4])) : null);
//if (Logger != null)
//{
// Logger.Debug("Set Steam data: {0}, Session:{1}", (SteamData ?? string.Empty).Replace("\n", " ").Replace("\r", ""), (SessionData ?? string.Empty).Replace("\n", " ").Replace("\r", ""));
//}
if (string.IsNullOrEmpty(session) == false)
{
SessionData = session;
}
}
else
{
SecretKey = null;
Serial = null;
DeviceId = null;
SteamData = null;
SessionData = null;
}
}
}
/// <summary>
/// Get (or create) the current Steam client for this Authenticator
/// </summary>
/// <returns>current or new SteamClient</returns>
public SteamClient GetClient()
{
lock (this)
{
if (this.Client == null)
{
this.Client = new SteamClient(this, this.SessionData);
}
return this.Client;
}
}
/// <summary>
/// Perform a request to the Steam WebAPI service
/// </summary>
/// <param name="url">API url</param>
/// <param name="method">GET or POST</param>
/// <param name="data">Name-data pairs</param>
/// <param name="cookies">current cookie container</param>
/// <returns>response body</returns>
private string Request(string url, string method, NameValueCollection data = null, CookieContainer cookies = null, NameValueCollection headers = null, int timeout = 0)
{
// create form-encoded data for query or body
string query = (data == null ? string.Empty : string.Join("&", Array.ConvertAll(data.AllKeys, key => String.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(data[key])))));
if (string.Compare(method, "GET", true) == 0)
{
url += (url.IndexOf("?") == -1 ? "?" : "&") + query;
}
// call the server
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = method;
request.Accept = "text/javascript, text/html, application/xml, text/xml, */*";
request.ServicePoint.Expect100Continue = false;
request.UserAgent = "Mozilla/5.0 (Linux; U; Android 4.1.1; en-us; Google Nexus 4 - 4.1.1 - API 16 - 768x1280 Build/JRO03S) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30";
request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
request.Referer = COMMUNITY_BASE; // + "/mobilelogin?oauth_client_id=DE45CD61&oauth_scope=read_profile%20write_profile%20read_client%20write_client";
if (headers != null)
{
request.Headers.Add(headers);
}
if (cookies != null)
{
request.CookieContainer = cookies;
}
if (timeout != 0)
{
request.Timeout = timeout;
}
if (string.Compare(method, "POST", true) == 0)
{
request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8";
request.ContentLength = query.Length;
StreamWriter requestStream = new StreamWriter(request.GetRequestStream());
requestStream.Write(query);
requestStream.Close();
}
try {
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
LogRequest(method, url, cookies, data, response.StatusCode.ToString() + " " + response.StatusDescription);
// OK?
if (response.StatusCode != HttpStatusCode.OK)
{
throw new InvalidRequestException(string.Format("{0}: {1}", (int)response.StatusCode, response.StatusDescription));
}
// load the response
using (StreamReader responseStream = new StreamReader(response.GetResponseStream()))
{
string responseData = responseStream.ReadToEnd();
LogRequest(method, url, cookies, data, responseData);
return responseData;
}
}
}
catch (Exception ex)
{
LogException(method, url, cookies, data, ex);
if (ex is WebException && ((WebException)ex).Response != null && ((HttpWebResponse)((WebException)ex).Response).StatusCode == HttpStatusCode.Forbidden)
{
throw new UnauthorisedRequestException(ex);
}
throw new InvalidRequestException(ex.Message, ex);
}
}
/// <summary>
/// Enroll the authenticator with the server
/// </summary>
public bool Enroll(EnrollState state)
{
// clear error
state.Error = null;
try
{
var data = new NameValueCollection();
var cookies = state.Cookies = state.Cookies ?? new CookieContainer();
string response;
if (string.IsNullOrEmpty(state.OAuthToken) == true)
{
// get session
if (cookies.Count == 0)
{
cookies.Add(new Uri(COMMUNITY_BASE + "/"), new Cookie("mobileClientVersion", "3067969+%282.1.3%29"));
cookies.Add(new Uri(COMMUNITY_BASE + "/"), new Cookie("mobileClient", "android"));
cookies.Add(new Uri(COMMUNITY_BASE + "/"), new Cookie("steamid", ""));
cookies.Add(new Uri(COMMUNITY_BASE + "/"), new Cookie("steamLogin", ""));
cookies.Add(new Uri(COMMUNITY_BASE + "/"), new Cookie("Steam_Language", "english"));
cookies.Add(new Uri(COMMUNITY_BASE + "/"), new Cookie("dob", ""));
NameValueCollection headers = new NameValueCollection();
headers.Add("X-Requested-With", "com.valvesoftware.android.steam.community");
response = Request("https://steamcommunity.com/mobilelogin?oauth_client_id=DE45CD61&oauth_scope=read_profile%20write_profile%20read_client%20write_client", "GET", null, cookies, headers);
}
// Steam strips any non-ascii chars from username and password
state.Username = Regex.Replace(state.Username, @"[^\u0000-\u007F]", string.Empty);
state.Password = Regex.Replace(state.Password, @"[^\u0000-\u007F]", string.Empty);
// get the user's RSA key
data.Add("username", state.Username);
response = Request(COMMUNITY_BASE + "/mobilelogin/getrsakey", "POST", data, cookies);
var rsaresponse = JObject.Parse(response);
if (rsaresponse.SelectToken("success").Value<bool>() != true)
{
throw new InvalidEnrollResponseException("Cannot get steam information for user: " + state.Username);
}
// encrypt password with RSA key
RNGCryptoServiceProvider random = new RNGCryptoServiceProvider();
byte[] encryptedPassword;
using (var rsa = new RSACryptoServiceProvider())
{
var passwordBytes = Encoding.ASCII.GetBytes(state.Password);
var p = rsa.ExportParameters(false);
p.Exponent = Authenticator.StringToByteArray(rsaresponse.SelectToken("publickey_exp").Value<string>());
p.Modulus = Authenticator.StringToByteArray(rsaresponse.SelectToken("publickey_mod").Value<string>());
rsa.ImportParameters(p);
encryptedPassword = rsa.Encrypt(passwordBytes, false);
}
// login request
data = new NameValueCollection();
data.Add("password", Convert.ToBase64String(encryptedPassword));
data.Add("username", state.Username);
data.Add("twofactorcode", "");
data.Add("emailauth", (state.EmailAuthText != null ? state.EmailAuthText : string.Empty));
data.Add("loginfriendlyname", "#login_emailauth_friendlyname_mobile");
data.Add("captchagid", (state.CaptchaId != null ? state.CaptchaId : "-1"));
data.Add("captcha_text", (state.CaptchaText != null ? state.CaptchaText : "enter above characters"));
data.Add("emailsteamid", (state.EmailAuthText != null ? state.SteamId ?? string.Empty : string.Empty));
data.Add("rsatimestamp", rsaresponse.SelectToken("timestamp").Value<string>());
data.Add("remember_login", "false");
data.Add("oauth_client_id", "DE45CD61");
data.Add("oauth_scope", "read_profile write_profile read_client write_client");
data.Add("donotache", new DateTime().ToUniversalTime().Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds.ToString());
response = Request(COMMUNITY_BASE + "/mobilelogin/dologin/", "POST", data, cookies);
Dictionary<string, object> loginresponse = JsonConvert.DeserializeObject<Dictionary<string, object>>(response);
if (loginresponse.ContainsKey("emailsteamid") == true)
{
state.SteamId = loginresponse["emailsteamid"] as string;
}
// require captcha
if (loginresponse.ContainsKey("captcha_needed") == true && (bool)loginresponse["captcha_needed"] == true)
{
state.RequiresCaptcha = true;
state.CaptchaId = (string)loginresponse["captcha_gid"];
state.CaptchaUrl = COMMUNITY_BASE + "/public/captcha.php?gid=" + state.CaptchaId;
}
else
{
state.RequiresCaptcha = false;
state.CaptchaId = null;
state.CaptchaUrl = null;
state.CaptchaText = null;
}
// require email auth
if (loginresponse.ContainsKey("emailauth_needed") == true && (bool)loginresponse["emailauth_needed"] == true)
{
if (loginresponse.ContainsKey("emaildomain") == true)
{
var emaildomain = (string)loginresponse["emaildomain"];
if (string.IsNullOrEmpty(emaildomain) == false)
{
state.EmailDomain = emaildomain;
}
}
state.RequiresEmailAuth = true;
}
else
{
state.EmailDomain = null;
state.RequiresEmailAuth = false;
}
// require email auth
if (loginresponse.ContainsKey("requires_twofactor") == true && (bool)loginresponse["requires_twofactor"] == true)
{
state.Requires2FA = true;
}
else
{
state.Requires2FA = false;
}
// if we didn't login, return the result
if (loginresponse.ContainsKey("login_complete") == false || (bool)loginresponse["login_complete"] == false || loginresponse.ContainsKey("oauth") == false)
{
if (loginresponse.ContainsKey("oauth") == false)
{
state.Error = "Invalid response from Steam (No OAuth token)";
}
if (loginresponse.ContainsKey("message") == true)
{
state.Error = (string)loginresponse["message"];
}
return false;
}
// get the OAuth token - is stringified json
string oauth = (string)loginresponse["oauth"];
var oauthjson = JObject.Parse(oauth);
state.OAuthToken = oauthjson.SelectToken("oauth_token").Value<string>();
if (oauthjson.SelectToken("steamid") != null)
{
state.SteamId = oauthjson.SelectToken("steamid").Value<string>();
}
}
// login to webapi
data.Clear();
data.Add("access_token", state.OAuthToken);
response = Request(WEBAPI_BASE + "/ISteamWebUserPresenceOAuth/Logon/v0001", "POST", data);
var sessionid = cookies.GetCookies(new Uri(COMMUNITY_BASE + "/"))["sessionid"].Value;
if (state.RequiresActivation == false)
{
data.Clear();
data.Add("op", "has_phone");
data.Add("arg", "null");
data.Add("sessionid", sessionid);
response = Request(COMMUNITY_BASE + "/steamguard/phoneajax", "POST", data, cookies);
var jsonresponse = JObject.Parse(response);
bool hasPhone = jsonresponse.SelectToken("has_phone").Value<Boolean>();
if (hasPhone == false)
{
state.OAuthToken = null; // force new login
state.RequiresLogin = true;
state.Cookies = null;
state.Error = "Your Steam account must have a SMS-capable phone number attached. Go into Account Details of the Steam client or Steam website and click Add a Phone Number.";
return false;
}
//response = Request(COMMUNITY_BASE + "/steamguard/phone_checksms?bForTwoFactor=1&bRevoke2fOnCancel=", "GET", null, cookies);
// add a new authenticator
data.Clear();
string deviceId = BuildRandomId();
data.Add("access_token", state.OAuthToken);
data.Add("steamid", state.SteamId);
data.Add("authenticator_type", "1");
data.Add("device_identifier", deviceId);
data.Add("sms_phone_id", "1");
response = Request(WEBAPI_BASE + "/ITwoFactorService/AddAuthenticator/v0001", "POST", data);
var tfaresponse = JObject.Parse(response);
if (response.IndexOf("status") == -1 && tfaresponse.SelectToken("response.status").Value<int>() == 84)
{
// invalid response
state.OAuthToken = null; // force new login
state.RequiresLogin = true;
state.Cookies = null;
state.Error = "Unable to send SMS. Check your phone is registered on your Steam account.";
return false;
}
if (response.IndexOf("shared_secret") == -1)
{
// invalid response
state.OAuthToken = null; // force new login
state.RequiresLogin = true;
state.Cookies = null;
state.Error = "Invalid response from Steam: " + response;
return false;
}
// save data into this authenticator
var secret = tfaresponse.SelectToken("response.shared_secret").Value<string>();
this.SecretKey = Convert.FromBase64String(secret);
this.Serial = tfaresponse.SelectToken("response.serial_number").Value<string>();
this.DeviceId = deviceId;
state.RevocationCode = tfaresponse.SelectToken("response.revocation_code").Value<string>();
// add the steamid into the data
var steamdata = JObject.Parse(tfaresponse.SelectToken("response").ToString());
if (steamdata.SelectToken("steamid") == null)
{
steamdata.Add("steamid", state.SteamId);
}
if (steamdata.SelectToken("steamguard_scheme") == null)
{
steamdata.Add("steamguard_scheme", "2");
}
this.SteamData = steamdata.ToString(Newtonsoft.Json.Formatting.None);
// calculate server drift
long servertime = tfaresponse.SelectToken("response.server_time").Value<long>() * 1000;
ServerTimeDiff = servertime - CurrentTime;
LastServerTime = DateTime.Now.Ticks;
state.RequiresActivation = true;
return false;
}
// finalize adding the authenticator
data.Clear();
data.Add("access_token", state.OAuthToken);
data.Add("steamid", state.SteamId);
data.Add("activation_code", state.ActivationCode);
// try and authorise
var retries = 0;
while (state.RequiresActivation == true && retries < ENROLL_ACTIVATE_RETRIES)
{
data.Add("authenticator_code", this.CalculateCode(false));
data.Add("authenticator_time", this.ServerTime.ToString());
response = Request(WEBAPI_BASE + "/ITwoFactorService/FinalizeAddAuthenticator/v0001", "POST", data);
var finalizeresponse = JObject.Parse(response);
if (response.IndexOf("status") != -1 && finalizeresponse.SelectToken("response.status").Value<int>() == INVALID_ACTIVATION_CODE)
{
state.Error = "Invalid activation code";
return false;
}
// reset our time
if (response.IndexOf("server_time") != -1)
{
long servertime = finalizeresponse.SelectToken("response.server_time").Value<long>() * 1000;
ServerTimeDiff = servertime - CurrentTime;
LastServerTime = DateTime.Now.Ticks;
}
// check success
if (finalizeresponse.SelectToken("response.success").Value<bool>() == true)
{
if (response.IndexOf("want_more") != -1 && finalizeresponse.SelectToken("response.want_more").Value<bool>() == true)
{
ServerTimeDiff += ((long)this.Period * 1000L);
retries++;
continue;
}
state.RequiresActivation = false;
break;
}
ServerTimeDiff += ((long)this.Period * 1000L);
retries++;
}
if (state.RequiresActivation == true)
{
state.Error = "There was a problem activating. There might be an issue with the Steam servers. Please try again later.";
return false;
}
// mark and successful and return key
state.Success = true;
state.SecretKey = Authenticator.ByteArrayToString(this.SecretKey);
// send confirmation email
data.Clear();
data.Add("access_token", state.OAuthToken);
data.Add("steamid", state.SteamId);
data.Add("email_type", "2");
response = Request(WEBAPI_BASE + "/ITwoFactorService/SendEmail/v0001", "POST", data);
return true;
}
catch (UnauthorisedRequestException ex)
{
throw new InvalidEnrollResponseException("You are not allowed to add an authenticator. Have you enabled 'community-generated content' in Family View?", ex);
}
catch (InvalidRequestException ex)
{
throw new InvalidEnrollResponseException("Error enrolling new authenticator", ex);
}
}
/// <summary>
/// Synchronise this authenticator's time with Steam.
/// </summary>
public override void Sync()
{
// check if data is protected
if (this.SecretKey == null && this.EncryptedData != null)
{
throw new EncryptedSecretDataException();
}
// don't retry for 5 minutes
if (_lastSyncError >= DateTime.Now.AddMinutes(0 - SYNC_ERROR_MINUTES))
{
return;
}
try
{
var response = Request(SYNC_URL, "POST", null, null, null, SYNC_TIMEOUT);
var json = JObject.Parse(response);
// get servertime in ms
long servertime = json.SelectToken("response.server_time").Value<long>() * 1000;
// get the difference between the server time and our current time
ServerTimeDiff = servertime - CurrentTime;
LastServerTime = DateTime.Now.Ticks;
// clear any sync error
_lastSyncError = DateTime.MinValue;
}
catch (Exception ex)
{
// don't retry for a while after error
_lastSyncError = DateTime.Now;
throw ex;
// set to zero to force reset
//ServerTimeDiff = 0;
}
}
/// <summary>
/// Calculate the current code for the authenticator.
/// </summary>
/// <param name="resyncTime">flag to resync time</param>
/// <returns>authenticator code</returns>
protected override string CalculateCode(bool resyncTime = false, long interval = -1)
{
// sync time if required
if (resyncTime == true || ServerTimeDiff == 0)
{
if (interval > 0)
{
ServerTimeDiff = (interval * ((long)this.Period * 1000L)) - CurrentTime;
}
else
{
Sync();
}
}
HMac hmac = new HMac(new Sha1Digest());
hmac.Init(new KeyParameter(SecretKey));
byte[] codeIntervalArray = BitConverter.GetBytes(CodeInterval);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(codeIntervalArray);
}
hmac.BlockUpdate(codeIntervalArray, 0, codeIntervalArray.Length);
byte[] mac = new byte[hmac.GetMacSize()];
hmac.DoFinal(mac, 0);
// the last 4 bits of the mac say where the code starts (e.g. if last 4 bit are 1100, we start at byte 12)
int start = mac[19] & 0x0f;
// extract those 4 bytes
byte[] bytes = new byte[4];
Array.Copy(mac, start, bytes, 0, 4);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(bytes);
}
uint fullcode = BitConverter.ToUInt32(bytes, 0) & 0x7fffffff;
// build the alphanumeric code
StringBuilder code = new StringBuilder();
for (var i=0; i<CODE_DIGITS; i++)
{
code.Append(STEAMCHARS[fullcode % STEAMCHARS.Length]);
fullcode /= (uint)STEAMCHARS.Length;
}
return code.ToString();
}
/// <summary>
/// Create a random Device ID string for Enrolling
/// </summary>
/// <returns>Random string</returns>
private static string BuildRandomId()
{
return "android:" + Guid.NewGuid().ToString();
}
/// <summary>
/// Log an exception from a Request
/// </summary>
/// <param name="method">Get or POST</param>
/// <param name="url">Request URL</param>
/// <param name="cookies">cookie container</param>
/// <param name="request">Request data</param>
/// <param name="ex">Thrown exception</param>
private static void LogException(string method, string url, CookieContainer cookies, NameValueCollection request, Exception ex)
{
StringBuilder data = new StringBuilder();
if (cookies != null)
{
foreach (Cookie cookie in cookies.GetCookies(new Uri(url)))
{
if (data.Length == 0)
{
data.Append("Cookies:");
}
else
{
data.Append("&");
}
data.Append(cookie.Name + "=" + cookie.Value);
}
data.Append(" ");
}
if (request != null)
{
foreach (var key in request.AllKeys)
{
if (data.Length == 0)
{
data.Append("Req:");
}
else
{
data.Append("&");
}
data.Append(key + "=" + request[key]);
}
data.Append(" ");
}
}
/// <summary>
/// Log a normal response
/// </summary>
/// <param name="method">Get or POST</param>
/// <param name="url">Request URL</param>
/// <param name="cookies">cookie container</param>
/// <param name="request">Request data</param>
/// <param name="response">response body</param>
private static void LogRequest(string method, string url, CookieContainer cookies, NameValueCollection request, string response)
{
StringBuilder data = new StringBuilder();
if (cookies != null)
{
foreach (Cookie cookie in cookies.GetCookies(new Uri(url)))
{
if (data.Length == 0)
{
data.Append("Cookies:");
}
else
{
data.Append("&");
}
data.Append(cookie.Name + "=" + cookie.Value);
}
data.Append(" ");
}
if (request != null)
{
foreach (var key in request.AllKeys)
{
if (data.Length == 0)
{
data.Append("Req:");
}
else
{
data.Append("&");
}
data.Append(key + "=" + request[key]);
}
data.Append(" ");
}
}
/// <summary>
/// Our custom exception for the internal Http Request
/// </summary>
class InvalidRequestException : ApplicationException
{
public InvalidRequestException(string msg = null, Exception ex = null) : base(msg, ex) { }
}
/// <summary>
/// 403 forbidden responses
/// </summary>
class UnauthorisedRequestException : InvalidRequestException
{
public UnauthorisedRequestException(Exception ex = null) : base("Unauthorised", ex) { }
}
}
}
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.10" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.8" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="5.0.0" />
</ItemGroup>
+33
View File
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using SteamTools.Model;
using Newtonsoft.Json;
namespace SteamTools.Helper
{
public static class ConfigHelper
{
private const string ConfigFileName = "SteamToolData.dat";
private static readonly string ConfigPath =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ConfigFileName);
public static SteamToolConfig SteamToolConfig { get; set; }
public static void SaveConfig()
{
var jsonData = JsonConvert.SerializeObject(SteamToolConfig);
File.WriteAllText(ConfigPath, jsonData, Encoding.UTF8);
}
public static SteamToolConfig ReadConfig()
{
SteamToolConfig = JsonConvert.DeserializeObject<SteamToolConfig>(File.ReadAllText(ConfigPath, Encoding.UTF8));
return SteamToolConfig;
}
}
}
@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Win32;
namespace SteamTools.Helper
{
public static class RegistryKeyHelper
{
/// <summary>
/// 创建注册表项
/// </summary>
/// <param name="registryKey"></param>
/// <param name="path"></param>
/// <returns></returns>
public static RegistryKey CreateRegistryKey(RegistryKey registryKey, string path)
{
return registryKey.CreateSubKey(path);
}
/// <summary>
/// 删除注册表项
/// </summary>
/// <param name="registryKey"></param>
/// <param name="path"></param>
/// <returns></returns>
public static void DeleteRegistryKey(RegistryKey registryKey, string path)
{
registryKey.DeleteSubKey(path, true); //该方法无返回值,直接调用即可
registryKey.Close();
}
/// <summary>
/// 读取注册表值
/// </summary>
/// <param name="registryKey"></param>
/// <param name="path"></param>
/// <param name="key"></param>
/// <returns></returns>
public static string ReadRegistryKey(RegistryKey registryKey, string path, string key)
{
var myreg = registryKey.OpenSubKey(path);
if (myreg != null)
{
var info = myreg.GetValue(key).ToString();
myreg.Close();
return info;
}
return string.Empty;
}
/// <summary>
/// 新增或修改注册表值
/// </summary>
/// <param name="registryKey"></param>
/// <param name="path"></param>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="registryValueKind"></param>
/// <returns></returns>
public static void AddOrUpdateRegistryKey(RegistryKey registryKey, string path, string key, string value, RegistryValueKind registryValueKind)
{
RegistryKey myreg = registryKey.OpenSubKey(path, true); //该项必须已存在
if (myreg != null)
{
myreg.SetValue(key, value, registryValueKind);
myreg.Close();
}
}
}
}
+107
View File
@@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Gameloop.Vdf.Linq;
using Microsoft.Win32;
using SteamTool.Common.Helper;
using SteamTools.Model;
namespace SteamTools.Helper
{
public class SteamHelper
{
public static readonly SteamHelper Instance = new SteamHelper();
public string SteamPath { get; set; }
public string SteamExePath { get; set; }
private const string SteamRegistryPath = @"SOFTWARE\Valve\Steam";
public SteamHelper()
{
var steamExePath = RegistryKeyHelper.ReadRegistryKey(Registry.CurrentUser, SteamRegistryPath, "SteamExe");
var steamPath = RegistryKeyHelper.ReadRegistryKey(Registry.CurrentUser, SteamRegistryPath, "SteamPath");
if (File.Exists(steamExePath))
{
SteamExePath = steamExePath;
}
if (Directory.Exists(steamPath))
{
SteamPath = steamPath;
}
}
public void KillSteamProcess()
{
Process[] processes = Process.GetProcesses();
foreach (Process process in processes)
{
if (process.ProcessName == "Steam" || process.ProcessName == "SteamService" || process.ProcessName == "steamwebhelper")
{
process.Kill();
}
}
}
public void StartSteam(string param = "")
{
if (!string.IsNullOrEmpty(SteamExePath))
{
Process.Start(SteamExePath, param);
}
}
/// <summary>
/// 获取最后一次自动登陆steam用户名称
/// </summary>
/// <returns></returns>
public string GetLastLoginUserName()
{
return RegistryKeyHelper.ReadRegistryKey(Registry.CurrentUser, SteamRegistryPath, "AutoLoginUser");
}
/// <summary>
/// 获取所有记住登陆steam用户信息
/// </summary>
/// <returns></returns>
public List<SteamUser> GetAllUser()
{
var users = new List<SteamUser>();
if (SteamPath != null)
{
var v = VdfHelper.GetVdfModelByPath(SteamPath + "/config/loginusers.vdf");
foreach (var item in v.Value)
{
var i = item.Value;
var user = new SteamUser
{
AccountName = i.AccountName.ToString(),
PersonaName = i.PersonaName.ToString(),
RememberPassword = Convert.ToBoolean(Convert.ToInt64(i.RememberPassword.ToString())),
MostRecent = Convert.ToBoolean(Convert.ToInt64(i.MostRecent.ToString())),
Timestamp = Convert.ToInt64(i.Timestamp.ToString())
};
user.LastLoginTime= new DateTime(1970, 1, 1, 8, 0, 0).AddSeconds(user.Timestamp);
users.Add(user);
}
}
return users;
}
/// <summary>
/// 设置下次登陆steam用户
/// </summary>
public void SetCurrentUser(string username)
{
RegistryKeyHelper.AddOrUpdateRegistryKey(Registry.CurrentUser, SteamRegistryPath, "AutoLoginUser", username, RegistryValueKind.String);
}
}
}
+35
View File
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Gameloop.Vdf;
using Gameloop.Vdf.Linq;
namespace SteamTool.Common.Helper
{
public static class VdfHelper
{
/// <summary>
/// 获取VDF类型内容
/// </summary>
/// <param name="path">vdf文件内容</param>
/// <returns></returns>
public static dynamic GetVdfModel(string text)
{
return VdfConvert.Deserialize(text);
}
/// <summary>
/// 根据路径获取VDF类型内容
/// </summary>
/// <param name="path">vdf文件路径</param>
/// <returns></returns>
public static dynamic GetVdfModelByPath(string path)
{
string text = File.ReadAllText(path,Encoding.UTF8);
return GetVdfModel(text); ;
}
}
}
+21
View File
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SteamTools.Model
{
public class SteamToolConfig
{
/// <summary>
/// 启动参数
/// </summary>
public string SteamRunParam { get; set; }
/// <summary>
/// 保存用户
/// </summary>
public List<SteamUser> SteamUsers { get; set; }
}
}
+65
View File
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SteamTools.Model
{
public class SteamUser
{
public string SteamId3 { get; set; }
public int SteamId3_Int { get; }
public string SteamId32 { get; set; }
public int SteamId32_Int { get; }
public long SteamId64 { get; set; }
/// <summary>
/// 个人资料链接
/// </summary>
public string ProfileUrl { get; set; }
/// <summary>
/// 昵称
/// </summary>
public string PersonaName { get; set; }
/// <summary>
/// 用户名
/// </summary>
public string AccountName { get; set; }
/// <summary>
/// 是否记住密码
/// </summary>
public bool RememberPassword { get; set; }
/// <summary>
/// 密码
/// </summary>
public string PassWord { get; set; }
/// <summary>
/// 最后登录时间戳
/// </summary>
public long Timestamp { get; set; }
/// <summary>
/// 最后登录时间
/// </summary>
public DateTime LastLoginTime { get; set; }
/// <summary>
/// 最近登录
/// </summary>
public bool MostRecent { get; set; }
/// <summary>
/// 备注
/// </summary>
public string Remark { get; set; }
}
}
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 有关程序集的一般信息由以下
// 控制。更改这些特性值可修改
// 与程序集关联的信息。
[assembly: AssemblyTitle("SteamTool.Common")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SteamTool.Common")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 将 ComVisible 设置为 false 会使此程序集中的类型
//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
//请将此类型的 ComVisible 特性设置为 true。
[assembly: ComVisible(false)]
// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
[assembly: Guid("75e148c7-5f32-4f01-9444-cedefa28748a")]
// 程序集的版本信息由下列四个值组成:
//
// 主版本
// 次版本
// 生成号
// 修订号
//
// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
//通过使用 "*",如下所示:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SteamTool.Common.Service
{
public class SteamToolsService
{
}
}
+74
View File
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{75E148C7-5F32-4F01-9444-CEDEFA28748A}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SteamTool.Common</RootNamespace>
<AssemblyName>SteamTool.Common</AssemblyName>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == '发布|AnyCPU'">
<OutputPath>bin\发布\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Gameloop.Vdf, Version=0.6.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Gameloop.Vdf.0.6.1\lib\net45\Gameloop.Vdf.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Helper\ConfigHelper.cs" />
<Compile Include="Helper\RegistryKeyHelper.cs" />
<Compile Include="Helper\SteamHelper.cs" />
<Compile Include="Helper\VdfHelper.cs" />
<Compile Include="Model\SteamToolConfig.cs" />
<Compile Include="Model\SteamUser.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Service\SteamToolsService.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
+5
View File
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Gameloop.Vdf" version="0.6.1" targetFramework="net46" />
<package id="Newtonsoft.Json" version="12.0.3" targetFramework="net46" />
</packages>
+26
View File
@@ -0,0 +1,26 @@
using SteamTool.Model;
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace SteamTool.Proxy
{
public static class CertificateEx
{
public static string GetPublicPemCertificateString(this X509Certificate2 @this)
{
StringBuilder builder = new StringBuilder();
builder.AppendLine(Const.HostTag);
builder.AppendLine("-----BEGIN CERTIFICATE-----");
builder.AppendLine(
Convert.ToBase64String(@this.RawData, Base64FormattingOptions.InsertLineBreaks));
builder.AppendLine("-----END CERTIFICATE-----");
builder.AppendLine(Const.HostTag);
return builder.ToString();
}
}
}
@@ -9,15 +9,6 @@ namespace SteamTool.Core.Common
{
public static class Common
{
/// <summary>
/// 时间戳转DateTime(系统时区)
/// </summary>
/// <returns></returns>
public static DateTime ToDateTime(this long timestamp)
{
return TimeZoneInfo.ConvertTimeFromUtc(new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(timestamp), TimeZoneInfo.Local);
}
/// <summary>
/// 返回当前时间戳
/// </summary>
@@ -46,16 +37,6 @@ namespace SteamTool.Core.Common
}
}
/// <summary>
/// 移除字符串内所有\r \n \t
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string RemovePattern(this string inputString)
{
return inputString.Replace("\t", "").Replace("\r", "").Replace("\n", "");
}
/// <summary>
/// 获取两个字符串中间的字符串
/// </summary>
@@ -15,10 +15,6 @@ namespace SteamTool.Core.Common
/// <returns></returns>
public static string CompressString(this string text)
{
if (string.IsNullOrEmpty(text))
{
return null;
}
byte[] buffer = Encoding.UTF8.GetBytes(text);
var memoryStream = new MemoryStream();
using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
@@ -44,10 +40,6 @@ namespace SteamTool.Core.Common
/// <returns></returns>
public static string DecompressString(this string compressedText)
{
if (string.IsNullOrEmpty(compressedText))
{
return null;
}
byte[] gZipBuffer = Convert.FromBase64String(compressedText);
using var memoryStream = new MemoryStream();
int dataLength = BitConverter.ToInt32(gZipBuffer, 0);
@@ -1,14 +1,12 @@
using log4net;
using log4net.Config;
using log4net.Repository;
using SteamTool.Core.Properties;
using SteamTool.Model;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
namespace SteamTool.Core.Common
{
@@ -23,16 +21,7 @@ namespace SteamTool.Core.Common
static Logger()
{
ILoggerRepository repository = LogManager.CreateRepository(Assembly.GetCallingAssembly().GetName().Name);
if (File.Exists(Path.Combine(AppContext.BaseDirectory, @"log\Log4net.config")))
{
XmlConfigurator.Configure(repository, new FileInfo(@"log\Log4net.config"));
}
else
{
var doc = new XmlDocument();
doc.LoadXml(Resources.Log4netXml);
XmlConfigurator.Configure(repository, doc.DocumentElement);
}
XmlConfigurator.Configure(repository, new FileInfo(@"log\Log4net.config"));
_textLog = LogManager.GetLogger(repository.Name, "TextLog");
}
@@ -13,25 +13,12 @@ namespace SteamTool.Core
{
public class HostsService
{
public readonly static string HostsPath = @$"{Environment.SystemDirectory}\drivers\etc\hosts";
public readonly static string HostsPath = $@"{Environment.GetFolderPath(Environment.SpecialFolder.System)}\drivers\etc\hosts";
static HostsService()
{
if (!File.Exists(HostsPath))
{
using var fs = File.Create(HostsPath);
fs.Close();
}
}
public OperationResult<List<string>> ReadHostsAllLines()
{
var result = new OperationResult<List<string>>(OperationResultType.Error, Resources.Hosts_ReadError);
if (!File.Exists(HostsPath))
{
result.Message = "hosts file was not found";
return result;
}
try
{
var dataLines = File.ReadAllLines(HostsPath).ToList();
@@ -61,11 +48,7 @@ namespace SteamTool.Core
public OperationResult UpdateHosts(string ip, string domain)
{
var result = new OperationResult(OperationResultType.Error, Resources.Hosts_WirteError);
if (!File.Exists(HostsPath))
{
result.Message = "hosts file was not found";
return result;
}
//操作前取消只读属性
File.SetAttributes(HostsPath, FileAttributes.Normal);
@@ -91,8 +74,8 @@ namespace SteamTool.Core
try
{
dataLines.Add($"{ip} {domain} {Const.HOST_TAG}");
File.WriteAllLines(HostsPath, dataLines, Encoding.UTF8);
dataLines.Add($"{ip} {domain} {Const.HostTag}");
File.WriteAllLines(HostsPath, dataLines);
//File.SetAttributes(HostsPath, FileAttributes.ReadOnly);
result.ResultType = OperationResultType.Success;
@@ -111,12 +94,8 @@ namespace SteamTool.Core
public OperationResult UpdateHosts(List<(string ip, string domain)> hosts)
{
var result = new OperationResult(OperationResultType.Error, Resources.Hosts_WirteError);
if (!File.Exists(HostsPath))
{
result.Message = "hosts file was not found";
return result;
}
if (hosts.Count == 0)
if (hosts.Count == 0)
{
return result;
}
@@ -149,11 +128,11 @@ namespace SteamTool.Core
foreach (var (ip, domain) in hosts)
{
dataLines.Add($"{ip} {domain} {Const.HOST_TAG}");
dataLines.Add($"{ip} {domain} {Const.HostTag}");
}
try
{
File.WriteAllLines(HostsPath, dataLines, Encoding.UTF8);
File.WriteAllLines(HostsPath, dataLines);
//File.SetAttributes(HostsPath, FileAttributes.ReadOnly);
result.ResultType = OperationResultType.Success;
result.Message = Resources.Hosts_UpdateSuccess;
@@ -171,33 +150,29 @@ namespace SteamTool.Core
public OperationResult RemoveHosts(string ip, string domain)
{
var result = new OperationResult(OperationResultType.Error, Resources.Hosts_WirteError);
if (!File.Exists(HostsPath))
{
result.Message = "hosts file was not found";
return result;
}
try
{
//操作前取消只读属性
File.SetAttributes(HostsPath, FileAttributes.Normal);
//避免重复写入
var dataLines = File.ReadAllLines(HostsPath, Encoding.Default).ToList().FindAll(s =>
{
var temp = s.Trim().Split(' ').ToList();
temp = temp.FindAll(s => !string.IsNullOrEmpty(s));
//操作前取消只读属性
File.SetAttributes(HostsPath, FileAttributes.Normal);
//避免重复写入
var dataLines = File.ReadAllLines(HostsPath, Encoding.Default).ToList().FindAll(s =>
{
var temp = s.Trim().Split(' ').ToList();
temp = temp.FindAll(s => !string.IsNullOrEmpty(s));
//一行内至少要有两列数据
if (temp.Count >= 2)
{
if (!temp[0].StartsWith("#"))
{
if (!temp[0].StartsWith("#"))
{
return !(temp.Contains(ip) && temp.Contains(domain));
}
return !(temp.Contains(ip) && temp.Contains(domain));
}
return true;
});
}
return true;
});
File.WriteAllLines(HostsPath, dataLines, Encoding.UTF8);
try
{
File.WriteAllLines(HostsPath, dataLines);
//File.SetAttributes(HostsPath, FileAttributes.ReadOnly);
result.ResultType = OperationResultType.Success;
@@ -216,31 +191,29 @@ namespace SteamTool.Core
public OperationResult RemoveHostsByTag()
{
var result = new OperationResult(OperationResultType.Error, Resources.Hosts_WirteError);
if (!File.Exists(HostsPath))
//操作前取消只读属性
File.SetAttributes(HostsPath, FileAttributes.Normal);
//避免重复写入
var dataLines = File.ReadAllLines(HostsPath, Encoding.Default).ToList().FindAll(s =>
{
result.Message = "hosts file was not found";
return result;
}
var temp = s.Trim().Split(' ').ToList();
temp = temp.FindAll(s => !string.IsNullOrEmpty(s));
//一行内至少要有两列数据
if (temp.Count >= 2)
{
if (!temp[0].StartsWith("#"))
{
return !temp.Contains(Const.HostTag);
}
}
return true;
});
try
{
//操作前取消只读属性
File.SetAttributes(HostsPath, FileAttributes.Normal);
var dataLines = File.ReadAllLines(HostsPath, Encoding.Default).ToList().FindAll(s =>
{
var temp = s.Trim().Split(' ').ToList().FindAll(w => !string.IsNullOrEmpty(w));
//有效数据一行内至少要有两列数据
if (temp.Count >= 2)
{
if (!temp[0].StartsWith("#"))
{
return !temp.Contains(Const.HOST_TAG);
}
}
return true;
});
File.WriteAllLines(HostsPath, dataLines, Encoding.UTF8);
File.WriteAllLines(HostsPath, dataLines);
//File.SetAttributes(HostsPath, FileAttributes.ReadOnly);
result.ResultType = OperationResultType.Success;
result.Message = Resources.Hosts_UpdateSuccess;
@@ -255,7 +228,7 @@ namespace SteamTool.Core
return result;
}
public void StartNotepadEditHosts()
public void StartNotepadEditHosts()
{
System.Diagnostics.Process.Start("notepad.exe", HostsPath);
}
@@ -1,6 +1,5 @@
using Newtonsoft.Json;
using Ninject.Modules;
using SteamTool.Core.Common;
using System;
using System.Collections.Generic;
using System.Dynamic;
@@ -16,51 +15,27 @@ namespace SteamTool.Core
{
private readonly HttpClient _client = new HttpClient();
public string UserAgent { get; set; } = "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko";
public string UserAgent { get; set; } = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36";
public string Accept { get; set; } = "application/json";
private async Task<string> SendAsync(HttpRequestMessage request)
{
try
{
var response = await _client.SendAsync(request).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
}
catch (Exception ex)
{
Logger.Error(ex);
}
return string.Empty;
}
private async Task<Stream> SendAsyncAsStream(HttpRequestMessage request)
{
try
{
var response = await _client.SendAsync(request).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
}
}
catch (Exception ex)
{
Logger.Error(ex);
}
return null;
}
public async Task<string> Get(string url)
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("User-Agent", UserAgent);
request.Headers.Add("Accept", Accept);
return await SendAsync(request);
var response = await _client.SendAsync(request).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
else
{
return null;
}
}
public async Task<string> Get(string url, Dictionary<string, string> keyValuePairs)
{
@@ -78,7 +53,20 @@ namespace SteamTool.Core
{
request.Headers.Add("Accept", Accept);
}
return await SendAsync(request);
var response = await _client.SendAsync(request).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
else
{
return null;
}
}
public async Task<Stream> GetStream(string url, Dictionary<string, string> keyValuePairs)
{
@@ -97,7 +85,18 @@ namespace SteamTool.Core
request.Headers.Add("Accept", Accept);
}
return await SendAsyncAsStream(request);
var response = await _client.SendAsync(request).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
}
else
{
return null;
}
}
public async Task<string> Post(string url, string content)
@@ -108,7 +107,18 @@ namespace SteamTool.Core
};
request.Headers.Add("User-Agent", UserAgent);
request.Headers.Add("Accept", Accept);
return await SendAsync(request);
var response = await _client.SendAsync(request).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
else
{
return null;
}
}
public async Task<string> Post(string url, Dictionary<string, string> keyValuePairs, string content)
{
@@ -129,7 +139,18 @@ namespace SteamTool.Core
request.Headers.Add("Accept", Accept);
}
return await SendAsync(request);
var response = await _client.SendAsync(request).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
else
{
return null;
}
}
public async Task<Stream> PostStream(string url, Dictionary<string, string> keyValuePairs, Stream content)
{
@@ -150,7 +171,18 @@ namespace SteamTool.Core
request.Headers.Add("Accept", Accept);
}
return await SendAsyncAsStream(request);
var response = await _client.SendAsync(request).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
}
else
{
return null;
}
}
}
}
@@ -19,11 +19,11 @@ namespace SteamTool.Core
public SteamToolCore()
{
Kernel.Bind<ConfigService>().ToConstant(new ConfigService());
Kernel.Bind<RegistryKeyService>().ToConstant(new RegistryKeyService());
Kernel.Bind<RegistryKeyService>().To<RegistryKeyService>();
Kernel.Bind<SteamToolService>().ToSelf();
Kernel.Bind<VdfService>().To<VdfService>();
Kernel.Bind<HttpServices>().ToConstant(new HttpServices());
Kernel.Bind<HostsService>().ToConstant(new HostsService());
Kernel.Bind<HostsService>().To<HostsService>();
}
public TInterface Get<TInterface>()
@@ -22,7 +22,7 @@ namespace SteamTool.Core.Properties {
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources {
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
@@ -36,7 +36,7 @@ namespace SteamTool.Core.Properties {
/// 返回此类使用的缓存的 ResourceManager 实例。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SteamTool.Core.Properties.Resources", typeof(Resources).Assembly);
@@ -47,11 +47,11 @@ namespace SteamTool.Core.Properties {
}
/// <summary>
/// 重写当前线程的 CurrentUICulture 属性,对
/// 使用此强类型资源类的所有资源查找执行重写。
/// 重写当前线程的 CurrentUICulture 属性
/// 重写当前线程的 CurrentUICulture 属性。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
@@ -74,7 +74,7 @@ namespace SteamTool.Core.Properties {
///# Additionally, comments (such as these) may be inserted on individual
///# lines or following th [字符串的其余部分被截断]&quot;; 的本地化字符串。
/// </summary>
public static string DefaultHosts {
internal static string DefaultHosts {
get {
return ResourceManager.GetString("DefaultHosts", resourceCulture);
}
@@ -83,7 +83,7 @@ namespace SteamTool.Core.Properties {
/// <summary>
/// 查找类似 写入的Hosts已经存在 的本地化字符串。
/// </summary>
public static string Hosts_Exists {
internal static string Hosts_Exists {
get {
return ResourceManager.GetString("Hosts_Exists", resourceCulture);
}
@@ -92,7 +92,7 @@ namespace SteamTool.Core.Properties {
/// <summary>
/// 查找类似 读取Hosts时发生错误 的本地化字符串。
/// </summary>
public static string Hosts_ReadError {
internal static string Hosts_ReadError {
get {
return ResourceManager.GetString("Hosts_ReadError", resourceCulture);
}
@@ -101,7 +101,7 @@ namespace SteamTool.Core.Properties {
/// <summary>
/// 查找类似 读取Hosts成功 的本地化字符串。
/// </summary>
public static string Hosts_ReadSuccess {
internal static string Hosts_ReadSuccess {
get {
return ResourceManager.GetString("Hosts_ReadSuccess", resourceCulture);
}
@@ -110,7 +110,7 @@ namespace SteamTool.Core.Properties {
/// <summary>
/// 查找类似 写入Hosts成功 的本地化字符串。
/// </summary>
public static string Hosts_UpdateSuccess {
internal static string Hosts_UpdateSuccess {
get {
return ResourceManager.GetString("Hosts_UpdateSuccess", resourceCulture);
}
@@ -119,46 +119,10 @@ namespace SteamTool.Core.Properties {
/// <summary>
/// 查找类似 写入Hosts时发生错误 的本地化字符串。
/// </summary>
public static string Hosts_WirteError {
internal static string Hosts_WirteError {
get {
return ResourceManager.GetString("Hosts_WirteError", resourceCulture);
}
}
/// <summary>
/// 查找类似 &lt;?xml version=&quot;1.0&quot;?&gt;
///&lt;configuration&gt;
/// &lt;configSections&gt;
/// &lt;section name=&quot;log4net&quot; type=&quot;log4net.Config.Log4NetConfigurationSectionHandler, log4net&quot;/&gt;
/// &lt;/configSections&gt;
/// &lt;log4net&gt;
/// &lt;logger name=&quot;TextLog&quot;&gt;
/// &lt;level value=&quot;ALL&quot; /&gt;
/// &lt;appender-ref ref=&quot;RollingLogFileAppender&quot; /&gt;
/// &lt;/logger&gt;
/// &lt;!--写文本--&gt;
/// &lt;appender name=&quot;RollingLogFileAppender&quot; type=&quot;log4net.Appender.RollingFileAppender&quot;&gt;
/// &lt;param name=&quot;File&quot; value=&quot;log\\&quot; /&gt;
/// &lt;param name=&quot;AppendToFile&quot; value=&quot;true&quot; [字符串的其余部分被截断]&quot;; 的本地化字符串。
/// </summary>
public static string Log4netXml {
get {
return ResourceManager.GetString("Log4netXml", resourceCulture);
}
}
/// <summary>
/// 查找类似 echo &quot;Steam++替换升级中...&quot;
///taskkill /f /im {0}
///xcopy /y /c /h /r /s &quot;{1}\*.*&quot; &quot;{2}&quot;
///rmdir /s /q {1}
///{3}
///del %0 的本地化字符串。
/// </summary>
public static string ProgramUpdateCmd {
get {
return ResourceManager.GetString("ProgramUpdateCmd", resourceCulture);
}
}
}
}
@@ -154,38 +154,4 @@
<data name="Hosts_WirteError" xml:space="preserve">
<value>写入Hosts时发生错误</value>
</data>
<data name="Log4netXml" xml:space="preserve">
<value>&lt;?xml version="1.0"?&gt;
&lt;configuration&gt;
&lt;configSections&gt;
&lt;section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/&gt;
&lt;/configSections&gt;
&lt;log4net&gt;
&lt;logger name="TextLog"&gt;
&lt;level value="ALL" /&gt;
&lt;appender-ref ref="RollingLogFileAppender" /&gt;
&lt;/logger&gt;
&lt;!--写文本--&gt;
&lt;appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender"&gt;
&lt;param name="File" value="log\\" /&gt;
&lt;param name="AppendToFile" value="true" /&gt;
&lt;param name="MaxSizeRollBackups" value="200" /&gt;
&lt;param name="StaticLogFileName" value="false" /&gt;
&lt;param name="DatePattern" value="yyyyMM\\yyyyMMdd'.log'" /&gt;
&lt;param name="RollingStyle" value="Date" /&gt;
&lt;layout type="log4net.Layout.PatternLayout,log4net"&gt;
&lt;param name="ConversionPattern" value="%d %-5p &gt;&gt; %m%n" /&gt;
&lt;/layout&gt;
&lt;/appender&gt;
&lt;/log4net&gt;
&lt;/configuration&gt;</value>
</data>
<data name="ProgramUpdateCmd" xml:space="preserve">
<value>echo "Steam++替换升级中..."
taskkill /f /im {0}
xcopy /y /c /h /r /s "{1}\*.*" "{2}"
rmdir /s /q {1}
{3}
del %0</value>
</data>
</root>
@@ -12,22 +12,14 @@
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Ninject" Version="3.3.4" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.10" />
<PackageReference Include="SharpZipLib" Version="1.3.1" />
<PackageReference Include="System.Management" Version="5.0.0" />
<PackageReference Include="TaskScheduler" Version="2.9.1" />
<PackageReference Include="TaskScheduler" Version="2.9.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SteamTool.Model\SteamTool.Model.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Interop.IWshRuntimeLibrary">
<HintPath>..\SteamTools\obj\x86\Debug\net472\Interop.IWshRuntimeLibrary.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
@@ -38,7 +30,7 @@
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
@@ -35,13 +35,6 @@ namespace SteamTool.Core
public string SteamExePath { get; set; }
public const string UserDataDirectory = @"\userdata";
/*
Windows: ~\Steam\config\loginusers.vdf
Linux: ~/.steam/steam/config/loginusers.vdf
Mac: ~/Library/Application Support/Steam/config/loginusers.vdf
*/
public const string UserVdfPath = @"\config\loginusers.vdf";
private const string SteamRegistryPath = @"SOFTWARE\Valve\Steam";
@@ -50,13 +43,15 @@ namespace SteamTool.Core
{
var steamExePath = registryKeyService.ReadRegistryKey(Registry.CurrentUser, SteamRegistryPath, "SteamExe");
var steamPath = registryKeyService.ReadRegistryKey(Registry.CurrentUser, SteamRegistryPath, "SteamPath");
//steamExePath = Path.GetFullPath(steamExePath);
//steamPath = Path.GetFullPath(steamPath);
if (File.Exists(steamExePath))
{
SteamExePath = Path.GetFullPath(steamExePath);
SteamExePath = steamExePath;
}
if (Directory.Exists(steamPath))
{
SteamPath = Path.GetFullPath(steamPath);
SteamPath = steamPath;
}
}
@@ -65,7 +60,7 @@ namespace SteamTool.Core
Process[] processes = Process.GetProcesses();
foreach (Process process in processes)
{
if (process.ProcessName.Equals("steam", StringComparison.OrdinalIgnoreCase) || process.ProcessName.Equals("steamservice", StringComparison.OrdinalIgnoreCase) || process.ProcessName.Equals("steamwebhelper", StringComparison.OrdinalIgnoreCase))
if (process.ProcessName.ToLower() == "steam" || process.ProcessName.ToLower() == "steamService" || process.ProcessName.ToLower() == "steamwebhelper")
{
process.Kill();
}
@@ -93,7 +88,7 @@ namespace SteamTool.Core
/// 获取所有记住登陆steam用户信息
/// </summary>
/// <returns></returns>
public List<SteamUser> GetRememberUserList()
public List<SteamUser> GetAllUser()
{
var users = new List<SteamUser>();
if (File.Exists(SteamPath + UserVdfPath))
@@ -103,30 +98,27 @@ namespace SteamTool.Core
foreach (var item in v.Value)
{
var i = item.Value;
var user = new SteamUser(item.ToString())
var user = new SteamUser
{
SteamId64 = Convert.ToInt64(item.Key.ToString()),
AccountName = i.AccountName?.ToString(),
SteamID = i.PersonaName?.ToString(),
PersonaName = i.PersonaName?.ToString(),
RememberPassword = Convert.ToBoolean(Convert.ToInt64(i.RememberPassword?.ToString())),
Timestamp = Convert.ToInt64(i.Timestamp?.ToString())
};
user.LastLoginTime = user.Timestamp.ToDateTime();
//老版本Steam数据 小写mostrecent 支持
user.MostRecent = i.mostrecent != null ?
Convert.ToBoolean(Convert.ToByte(i.mostrecent.ToString())) :
Convert.ToBoolean(Convert.ToByte(i.MostRecent.ToString()));
user.WantsOfflineMode = i.WantsOfflineMode != null ?
Convert.ToBoolean(Convert.ToByte(i.WantsOfflineMode.ToString())) : false;
// 因为警告这个东西应该都不需要所以直接默认跳过好了
user.SkipOfflineModeWarning = true;
//user.SkipOfflineModeWarning = i.SkipOfflineModeWarning != null ?
// Convert.ToBoolean(Convert.ToByte(i.SkipOfflineModeWarning.ToString())) : false;
user.LastLoginTime = new DateTime(1970, 1, 1, 8, 0, 0).AddSeconds(user.Timestamp);
if (i.mostrecent != null)//老版本Steam数据 小写mostrecent 支持
{
user.MostRecent = Convert.ToBoolean(Convert.ToInt64(i.mostrecent.ToString()));
}
else if (i.MostRecent != null)
{
user.MostRecent = Convert.ToBoolean(Convert.ToInt64(i.MostRecent.ToString()));
}
else
{
user.MostRecent = false;
}
users.Add(user);
}
}
@@ -142,6 +134,7 @@ namespace SteamTool.Core
registryKeyService.AddOrUpdateRegistryKey(Registry.CurrentUser, SteamRegistryPath, "AutoLoginUser", username, RegistryValueKind.String);
}
public List<SteamApp> GetAppListJson(string filepath)
{
if (!File.Exists(filepath))
@@ -157,15 +150,11 @@ namespace SteamTool.Core
}
string json = File.ReadAllText(filepath, Encoding.UTF8);
if (string.IsNullOrEmpty(json))
{
return null;
}
var apps = JsonConvert.DeserializeObject<SteamApps>(json);
return apps.AppList.Apps;
}
public bool UpdateAppListJson(List<SteamApp> apps, string filepath)
{
var json = JsonConvert.SerializeObject(apps);
@@ -195,21 +184,28 @@ namespace SteamTool.Core
return true;
}
public void SetWindowsStartupAutoRun(bool IsAutoRun, string Name = "Steam++")
{
// Get the service on the local machine
if (IsAutoRun)
{
using TaskDefinition td = TaskService.Instance.NewTask();
// Create a new task definition and assign properties
td.RegistrationInfo.Description = Name + "System Boot Run";
td.Settings.Priority = System.Diagnostics.ProcessPriorityClass.Normal;
td.Settings.ExecutionTimeLimit = new TimeSpan(0);
td.Settings.AllowHardTerminate = false;
td.Settings.StopIfGoingOnBatteries = false;
td.Settings.DisallowStartIfOnBatteries = false;
// Create a trigger that will fire after the system boot
td.Triggers.Add(new LogonTrigger());
// Create an action that will launch Notepad whenever the trigger fires
td.Actions.Add(new ExecAction(Assembly.GetCallingAssembly().Location, "-minimized", Path.GetDirectoryName(Assembly.GetCallingAssembly().Location)));
if (IsAdministrator)
td.Principal.RunLevel = TaskRunLevel.Highest;
// Register the task in the root folder
TaskService.Instance.RootFolder.RegisterTaskDefinition(Name, td);
//TaskService.Instance.RootFolder.AddTask(Name, QuickTriggerType.Boot, Assembly.GetCallingAssembly().Location, "-a");
}
@@ -218,20 +214,5 @@ namespace SteamTool.Core
TaskService.Instance.RootFolder.DeleteTask(Name);
}
}
public void DeleteSteamLocalUserData(SteamUser user)
{
vdfService.DeleteVdfValueByKey(SteamPath + UserVdfPath, user.SteamId64.ToString());
var temp = SteamPath + Path.Combine(UserDataDirectory, user.SteamId3_Int.ToString());
if (Directory.Exists(temp))
{
Directory.Delete(temp, true);
}
}
public void UpdateSteamLocalUserData(SteamUser user)
{
vdfService.UpdateVdfValueByReplace(SteamPath + UserVdfPath, user.OriginVdfString, user.CurrentVdfString);
}
}
}
+36
View File
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Gameloop.Vdf;
using Gameloop.Vdf.Linq;
namespace SteamTool.Core
{
public class VdfService
{
/// <summary>
/// 获取VDF类型内容
/// </summary>
/// <param name="path">vdf文件内容</param>
/// <returns></returns>
public dynamic GetVdfModel(string text)
{
return VdfConvert.Deserialize(text);
}
/// <summary>
/// 根据路径获取VDF类型内容
/// </summary>
/// <param name="path">vdf文件路径</param>
/// <returns></returns>
public dynamic GetVdfModelByPath(string path)
{
string text = File.ReadAllText(path,Encoding.UTF8);
return GetVdfModel(text); ;
}
}
}
@@ -5,51 +5,31 @@ namespace SteamTool.Model
public class Const
{
public const string APP_LIST_FILE = "apps.json";
public const string AUTHDATA_FILE = "authenticators.dat";
public const string SETTINGS_FILE = "settings.json";
public const string SCRIPT_DIR = "scripts";
public const string LOG_DIR = "log";
public const string HOST_TAG = "#Steam++";
public const string HostTag = "#S302";
public const string REWARDMELIST_URL = "https://gitee.com/rmbgame/steam-tools_-data/raw/master/RewardRecord.json";
//public const string REWARDMELIST_URL = "https://raw.githubusercontent.com/rmbadmin/SteamTools/develop/Data/RewardRecord.json";
public const string GITHUB_LATEST_RELEASEAPI_URL = "https://api.github.com/repos/rmbadmin/SteamTools/releases/latest";
public const string GITHUB_RELEASEAPI_URL = "https://api.github.com/repos/rmbadmin/SteamTools/releases";
public const string GITHUB_URL = "https://github.com/rmbadmin/SteamTools";
public const string GITHUB_RELEASES_URL = "https://github.com/rmbadmin/SteamTools/releases";
public const string MY_PROFILE_URL = "https://steamcommunity.com/profiles/76561198289531723/";
public const string MY_WISHLIST_URL = "https://store.steampowered.com/wishlist/profiles/76561198289531723/";
public const string MY_MINIPROFILE_URL = "https://steamcommunity.com/miniprofile/329265995";
public const string STEAM_LOGIN_URL = "https://steamcommunity.com/login/home/?goto=my/profile";
public const string STEAM_BADGES_URL = "https://steamcommunity.com/profiles/{0}/badges/";
public const string STEAMAPP_LIST_URL = "https://api.steampowered.com/ISteamApps/GetAppList/v2";
public const string STORE_APP_URL = "https://store.steampowered.com/app/{0}";
public const string STEAMAPP_LOGO_URL = "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/{0}/{1}.jpg";
public const string STEAMAPP_ICON_URL = "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/{0}/{1}.jpg";
public const string STEAMAPP_ICON_URL = "http://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/{0}/{1}";
public const string STEAMAPP_CAPSULE_URL = "https://steamcdn-a.akamaihd.net/steam/apps/{0}/capsule_184x69.jpg";
public const string STEAMAPP_HEADIMAGE_URL = "https://steamcdn-a.akamaihd.net/steam/apps/{0}/header.jpg";
public const string STEAM_MEDIA_URL = "https://steamcdn-a.akamaihd.net/steam/apps/{0}/movie_max.webm";
public const string STEAM_VIDEO_URL = "https://store.steampowered.com/video/watch/{0}/";
public const string STEAM_REGISTERKEY_URL = "https://store.steampowered.com/account/registerkey?key={0}";
public const string STEAM_FRIENDMESSAGESLOG_URL = "https://help.steampowered.com/zh-cn/accountdata/GetFriendMessagesLog";
public const string STEAM_INSTALL_URL = "steam://install/{0}";
public const string STEAM_RUNGAME_URL = "steam://rungameid/{0}";
public const string STEAM_OPENURL = "steam://openurl/{0}";
public const string STEAM_RUN_KEYURL = "steam://openurl/https://store.steampowered.com/account/registerkey?key={0}";
public const string STEAM_RUN_KeyURL = "steam://openurl/https://store.steampowered.com/account/registerkey?key={0}";
public const string STEAMDB_USERINFO_URL = "https://api.steamdb.ml/v1/users/{0}";
public const string STEAMDB_APPINFO_URL = "https://api.steamdb.ml/v1/apps/{0}";
//https://steamcommunity.com/profiles/76561198289531723?xml=1
public const string STEAM_USERINFO_XML_URL = "https://steamcommunity.com/profiles/{0}?xml=1";
public const string STEAMCN_USERINFO_XML_URL = "https://my.steamchina.com/profiles/76561198289531723?xml=1";
public const string JOINQQGROUP_URL = "https://qm.qq.com/cgi-bin/qm/qr?k=EvP8VZ-ByGjMlbLvrj4x4YJ8C_bhTisG&authKey=toKraTwFoTlJKXSuBv7A87W0qKb0vSRrozVLXW0dVcvOmEP98T/t+MZK9qFi26fb&noverify=0";
}
}
@@ -16,7 +16,7 @@ namespace SteamTool.Model
public bool IsHidden { get; set; }
public bool IsAchieved { get; set; }
public bool IsChecked { get; set; }
public long UnlockTimeUnix { get; set; }
public int UnlockTimeUnix { get; set; }
public DateTime UnlockTime { get; set; }
public string IconUrl => IsAchieved ? string.Format(Const.STEAMAPP_ICON_URL, AppId, IconNormal) : string.Format(Const.STEAMAPP_ICON_URL, AppId, IconLocked);
public int Permission { get; set; }
@@ -18,8 +18,6 @@ namespace SteamTool.Model
public bool IsInstalled { get; set; }
public string InstalledDir { get; set; }
public string Name { get; set; }
public string Logo { get; set; }
@@ -32,7 +30,7 @@ namespace SteamTool.Model
public string HeaderLogoUrl => string.Format(Const.STEAMAPP_CAPSULE_URL, AppId);
public string IconUrl => string.IsNullOrEmpty(Icon) ? null : string.Format(Const.STEAMAPP_ICON_URL, AppId, Icon);
public string IconUrl => string.IsNullOrEmpty(Icon) ? null : string.Format(Const.STEAMAPP_LOGO_URL, AppId, Icon);
public Process Process { get; set; }
+87
View File
@@ -0,0 +1,87 @@
using System;
namespace SteamTool.Model
{
public class SteamUser
{
public string SteamId3 { get; set; }
public long SteamId3_Int => ((SteamId64 >> (ushort)0) & 0xFFFFFFFF);
public string SteamId32 { get; set; }
public long SteamId32_Int { get; set; }
public long SteamId64 { get; set; }
/// <summary>
/// 个人资料链接
/// </summary>
public string ProfileUrl => string.Format("https://steamcommunity.com/profiles/{0}", SteamId64);
/// <summary>
/// 在线状态
/// </summary>
public string OnlineState { get; set; }
/// <summary>
/// 公开状态
/// friendsonly
/// public
/// </summary>
public string PrivacyState { get; set; }
/// <summary>
/// 头像链接
/// </summary>
public string AvatarIcon { get; set; }
/// <summary>
/// 头像链接
/// </summary>
public string AvatarMedium { get; set; }
/// <summary>
/// 头像链接
/// </summary>
public string AvatarFull { get; set; }
/// <summary>
/// 昵称
/// </summary>
public string SteamID { get; set; }
/// <summary>
/// 用户名
/// </summary>
public string AccountName { get; set; }
/// <summary>
/// 是否记住密码
/// </summary>
public bool RememberPassword { get; set; }
/// <summary>
/// 密码
/// </summary>
public string PassWord { get; set; }
/// <summary>
/// 最后登录时间戳
/// </summary>
public long Timestamp { get; set; }
/// <summary>
/// 最后登录时间
/// </summary>
public DateTime LastLoginTime { get; set; }
/// <summary>
/// 最近登录
/// </summary>
public bool MostRecent { get; set; }
/// <summary>
/// 备注
/// </summary>
public string Remark { get; set; }
}
}
@@ -9,10 +9,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
<PackageReference Include="coverlet.collector" Version="3.0.2">
<PackageReference Include="coverlet.collector" Version="1.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -13,10 +13,6 @@ using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
using SteamTool.Model;
using System.Security.Cryptography;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.X509;
namespace SteamTool.Proxy
{
@@ -30,26 +26,18 @@ namespace SteamTool.Proxy
public IReadOnlyCollection<ProxyDomainModel> ProxyDomains { get; set; }
public List<ProxyScript> Scripts { get; set; }
public bool IsEnableScript { get; set; }
public bool IsOnlyWorkSteamBrowser { get; set; }
public string CertificateName { get; }
public HttpProxy(IReadOnlyCollection<ProxyDomainModel> proxyDomains, string certificateName)
{
ProxyDomains = proxyDomains;
CertificateName = certificateName;
//proxyServer.CertificateManager.PfxPassword = $"{CertificateName}";
proxyServer.ThreadPoolWorkerThread = Environment.ProcessorCount * 8;
proxyServer.CertificateManager.PfxFilePath = Path.Combine(AppContext.BaseDirectory, $@"{CertificateName}.Certificate.pfx");
proxyServer.CertificateManager.RootCertificateIssuerName = $"{CertificateName} Certificate Authority";
proxyServer.CertificateManager.RootCertificateName = $"{CertificateName} Certificate";
proxyServer.CertificateManager.CertificateEngine = Titanium.Web.Proxy.Network.CertificateEngine.BouncyCastle;
}
public bool ProxyRunning => proxyServer.ProxyRunning;
public async Task OnRequest(object sender, SessionEventArgs e)
{
#if DEBUG
#region 测试用
/*
if (e.HttpClient.Request.RequestUri.AbsoluteUri.Contains("steampowered.com"))
@@ -73,21 +61,9 @@ namespace SteamTool.Proxy
}
*/
#endregion
Debug.WriteLine("OnRequest " + e.HttpClient.Request.RequestUri.AbsoluteUri);
Debug.WriteLine("OnRequest HTTP " + e.HttpClient.Request.HttpVersion);
//Logger.Info("OnRequest" + e.HttpClient.Request.RequestUri.AbsoluteUri);
#if DEBUG
Debug.WriteLine("OnRequest" + e.HttpClient.Request.RequestUri.AbsoluteUri);
#endif
// Dns.GetHostAddressesAsync(e.HttpClient.Request.Host).ContinueWith(s =>
//{
// //部分运营商将奇怪的域名解析到127.0.0.1 再此排除这些不支持的代理域名
// if (IPAddress.IsLoopback(s.Result.FirstOrDefault())
// && ProxyDomains.Count(w => w.IsEnable && w.Hosts.Contains(e.HttpClient.Request.Host)) == 0)
// {
// e.Ok($"URL : {e.HttpClient.Request.RequestUri.AbsoluteUri} \r\n not support proxy");
// return;
// }
//});
//Logger.Info("Steam++ OnRequest: " + e.HttpClient.Request.RequestUri.AbsoluteUri);
foreach (var item in ProxyDomains)
{
if (!item.IsEnable)
@@ -98,34 +74,25 @@ namespace SteamTool.Proxy
{
if (e.HttpClient.Request.RequestUri.AbsoluteUri.Contains(host))
{
if (e.HttpClient.Request.RequestUri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase))
{
e.Redirect(e.HttpClient.Request.RequestUri.AbsoluteUri.Remove(0, 4).Insert(0, "https"));
return;
}
if (item.IsRedirect)
{
e.Redirect(e.HttpClient.Request.RequestUri.AbsoluteUri.Replace(e.HttpClient.Request.RequestUri.Host, item.ToDomain));
return;
}
IPAddress ip = null;
IPAddress iP = null;
if (!string.IsNullOrEmpty(item.ProxyIPAddres))
{
ip = IPAddress.Parse(item.ProxyIPAddres);
iP = IPAddress.Parse(item.ProxyIPAddres);
}
else
{
var iPs = await Dns.GetHostAddressesAsync(item.ToDomain);
ip = iPs.FirstOrDefault();
//Logger.Info("Proxy IP: " + iP);
if (iPs.Length > 0)
{
iP = iPs[0];
}
}
if (ip != null)
if (iP != null)
{
e.HttpClient.UpStreamEndPoint = new IPEndPoint(IPAddress.Parse(ip.ToString()), item.Port);
e.HttpClient.UpStreamEndPoint = new IPEndPoint(IPAddress.Parse(iP.ToString()), item.Port);
}
if (e.HttpClient.ConnectRequest?.ClientHelloInfo != null)
{
//Logger.Info("ClientHelloInfo Info: " + e.HttpClient.ConnectRequest.ClientHelloInfo);
if (!string.IsNullOrEmpty(item.ServerName))
{
var sni = e.HttpClient.ConnectRequest.ClientHelloInfo.Extensions["server_name"];
@@ -137,36 +104,20 @@ namespace SteamTool.Proxy
e.HttpClient.ConnectRequest.ClientHelloInfo.Extensions.Remove("server_name");
}
}
return;
}
}
}
//没有匹配到的结果直接返回不支持,避免出现Loopback死循环内存溢出
e.Ok($"URL : {e.HttpClient.Request.RequestUri.AbsoluteUri} {Environment.NewLine}not support proxy");
return;
}
public async Task OnResponse(object sender, SessionEventArgs e)
{
// read response headers
//var responseHeaders = e.HttpClient.Response.Headers;
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
#if DEBUG
Debug.WriteLine("OnResponse" + e.HttpClient.Request.RequestUri.AbsoluteUri);
Logger.Info("OnResponse" + e.HttpClient.Request.RequestUri.AbsoluteUri);
#endif
if (IsEnableScript)
{
if (IsOnlyWorkSteamBrowser)
{
var ua = e.HttpClient.Request.Headers.GetHeaders("User-Agent");
if (ua.Any())
{
if (!ua.First().Value.Contains("Valve Steam"))
{
return;
}
}
else
return;
}
foreach (var script in Scripts)
{
if (script.Enable)
@@ -187,12 +138,12 @@ namespace SteamTool.Proxy
if (e.HttpClient.Request.RequestUri.AbsoluteUri.IsWildcard(host))
{
var doc = await e.GetResponseBodyAsString();
if (script.Require.Length > 0 || script.Grant.Length > 0)
if (script.Require.Length > 0)
{
//var headIndex = doc.LastIndexOf("</head>", StringComparison.OrdinalIgnoreCase);
//doc = doc.Insert(headIndex, "<meta http-equiv=\"Content-Security-Policy\" content=\"default - src 'self' data: gap: https://ssl.gstatic.com 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *\">");
var t = e.HttpClient.Response.Headers.GetFirstHeader("Content-Security-Policy");
if (!string.IsNullOrEmpty(t?.Value))
if (!string.IsNullOrEmpty(t.Value))
{
//var tt = t.Value.Split(';');
//for (var i = 0; i < tt.Length; i++)
@@ -213,7 +164,7 @@ namespace SteamTool.Proxy
//e.HttpClient.Response.Headers.AddHeader("Content-Security-Policy", result);
}
#if DEBUG
Debug.WriteLine(e.HttpClient.Request.RequestUri.AbsoluteUri);
Debug.WriteLine(t);
#endif
foreach (var req in script.Require)
{
@@ -237,23 +188,26 @@ namespace SteamTool.Proxy
close:;
}
}
}
//// 允许重写默认的证书验证逻辑
//public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
//{
// // 根据证书错误,设置IsValid为真/假
// //if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
// e.IsValid = true;
// return Task.CompletedTask;
//}
// 允许重写默认的证书验证逻辑
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{
// 根据证书错误,设置IsValid为真/假
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
e.IsValid = true;
return Task.CompletedTask;
}
//// 允许在相互身份验证期间重写默认客户端证书选择逻辑
//public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
//{
// // set e.clientCertificate to override
// return Task.CompletedTask;
//}
// 允许在相互身份验证期间重写默认客户端证书选择逻辑
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
{
// set e.clientCertificate to override
return Task.CompletedTask;
}
public bool SetupCertificate()
{
@@ -262,20 +216,20 @@ namespace SteamTool.Proxy
//proxyServer.CertificateManager
// .CreateServerCertificate($"{Assembly.GetCallingAssembly().GetName().Name} Certificate")
// .ContinueWith(c => proxyServer.CertificateManager.RootCertificate = c.Result);
proxyServer.CertificateManager.PfxFilePath = $@"{CertificateName}.Certificate.pfx";
//proxyServer.CertificateManager.PfxPassword = $"{CertificateName}";
proxyServer.CertificateManager.RootCertificateIssuerName = CertificateName;
proxyServer.CertificateManager.RootCertificateName = $"{CertificateName} Certificate";
proxyServer.CertificateManager.CertificateEngine = Titanium.Web.Proxy.Network.CertificateEngine.DefaultWindows;
// 可选地设置证书引擎
// 在Mono之下,只有BouncyCastle将得到支持
//proxyServer.CertificateManager.CertificateEngine = Titanium.Web.Proxy.Network.CertificateEngine.DefaultWindows;
//proxyServer.CertificateManager.SaveFakeCertificates = true;
var result = proxyServer.CertificateManager.CreateRootCertificate(true);
if (!result)
{
Logger.Error("创建证书失败");
}
proxyServer.CertificateManager.RootCertificate.SaveCerCertificateFile(Path.Combine(AppContext.BaseDirectory, $@"{CertificateName}.Certificate.cer"));
//proxyServer.CertificateManager.CertificateEngine = Network.CertificateEngine.BouncyCastle;
proxyServer.CertificateManager.SaveFakeCertificates = false;
proxyServer.CertificateManager.CreateRootCertificate(true);
proxyServer.CertificateManager.EnsureRootCertificate();
//proxyServer.CertificateManager.TrustRootCertificate(true);
return IsCertificateInstalled(proxyServer.CertificateManager.RootCertificate);
}
@@ -283,36 +237,104 @@ namespace SteamTool.Proxy
{
if (ProxyRunning)
return false;
try
using (var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser))
{
//using (var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser))
//{
// store.Open(OpenFlags.MaxAllowed);
// var test = store.Certificates.Find(X509FindType.FindByIssuerName, CertificateName, true);
// foreach (var item in test)
// {
// store.Remove(item);
// }
//}
//proxyServer.CertificateManager.ClearRootCertificate();
proxyServer.CertificateManager.RemoveTrustedRootCertificate();
//proxyServer.CertificateManager.RemoveTrustedRootCertificateAsAdmin();
//proxyServer.CertificateManager.CertificateStorage.Clear();
}
catch (Exception ex)
{
if (ex is CryptographicException)
store.Open(OpenFlags.MaxAllowed);
//var test = store.Certificates.Find(X509FindType.FindByIssuerName, "titan", true);
var test = store.Certificates.Find(X509FindType.FindByIssuerName, CertificateName, true);
foreach (var item in test)
{
//取消删除证书
}
else
{
throw;
store.Remove(item);
}
//if (store.Certificates.Contains(proxyServer.CertificateManager.RootCertificate))
// store.Remove(proxyServer.CertificateManager.RootCertificate);
}
proxyServer.CertificateManager.ClearRootCertificate();
proxyServer.CertificateManager.RemoveTrustedRootCertificate(true);
proxyServer.CertificateManager.RemoveTrustedRootCertificateAsAdmin(true);
proxyServer.CertificateManager.CertificateStorage.Clear();
return true;
}
public bool StartProxy(bool IsProxyGOG = false)
{
if (!IsCertificateInstalled(proxyServer.CertificateManager.RootCertificate))
{
var isOk = SetupCertificate();
if (!isOk)
{
return false;
}
if (PortInUse(443))
{
return false;
}
}
if (IsProxyGOG) { WirtePemCertificateToGoGSteamPlugins(); }
#region 写入Hosts
var hosts = new List<(string, string)>();
foreach (var proxyDomain in ProxyDomains)
{
if (proxyDomain.IsEnable)
{
foreach (var host in proxyDomain.Hosts)
{
hosts.Add((IPAddress.Loopback.ToString(), host));
}
}
}
hostsService.UpdateHosts(hosts);
#endregion
#region 启动代理
proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse;
//proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
//proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
//var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 443, true)
//{
// // 在所有https请求上使用自颁发的通用证书
// // 通过不为每个启用http的域创建证书来优化性能
// // 当代理客户端不需要证书信任时非常有用
// //GenericCertificate = new X509Certificate2(Path.Combine(AppContext.BaseDirectory, "genericcert.pfx"), "password")
//};
//当接收到连接请求时触发
//explicitEndPoint.BeforeTunnelConnectRequest += OnBeforeTunnelRequest;
//explicit endpoint 是客户端知道代理存在的地方
//因此,客户端以代理友好的方式发送请求
//proxyServer.AddEndPoint(explicitEndPoint);
// 透明endpoint 对于反向代理很有用(客户端不知道代理的存在)
// 透明endpoint 通常需要一个网络路由器端口来转发HTTP(S)包或DNS
// 发送数据到此endpoint
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 443, true)
{
//GenericCertificate = proxyServer.CertificateManager.RootCertificate
//GenericCertificateName= "steamcommunity-a.akamaihd.net"
};
proxyServer.AddEndPoint(transparentEndPoint);
try
{
proxyServer.Start();
}
catch (Exception ex)
{
Logger.Error(ex);
return false;
}
//proxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//proxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
#endregion
#if DEBUG
foreach (var endPoint in proxyServer.ProxyEndPoints)
Debug.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
#endif
return true;
}
public static bool PortInUse(int port)
{
bool inUse = false;
@@ -332,100 +354,14 @@ namespace SteamTool.Proxy
return inUse;
}
public bool StartProxy(bool IsProxyGOG = false)
{
if (!IsCertificateInstalled(proxyServer.CertificateManager.RootCertificate))
{
var isOk = SetupCertificate();
if (!isOk)
{
return false;
}
}
if (PortInUse(443))
{
return false;
}
if (IsProxyGOG) { WirtePemCertificateToGoGSteamPlugins(); }
#region 写入Hosts
var hosts = new List<(string, string)>();
foreach (var proxyDomain in ProxyDomains)
{
if (proxyDomain.IsEnable)
{
foreach (var host in proxyDomain.Hosts)
{
if (host.Contains(" "))
{
var h = host.Split(' ');
hosts.Add((h[0], h[1]));
}
else
hosts.Add((IPAddress.Loopback.ToString(), host));
}
}
}
hostsService.UpdateHosts(hosts);
#endregion
#region 启动代理
proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse;
//proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
//proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
//var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8888, true)
//{
// // 在所有https请求上使用自颁发的通用证书
// // 当代理客户端不需要证书信任时非常有用
// //GenericCertificate = new X509Certificate2(Path.Combine(AppContext.BaseDirectory, "genericcert.pfx"), "password")
//};
//当接收到连接请求时触发
//explicitEndPoint.BeforeTunnelConnectRequest += OnBeforeTunnelRequest;
//explicit endpoint 是客户端知道代理存在的地方
//proxyServer.AddEndPoint(explicitEndPoint);
proxyServer.AddEndPoint(new TransparentProxyEndPoint(IPAddress.Any, 443, true)
{
// 通过不启用为每个http的域创建证书来优化性能
//GenericCertificate = proxyServer.CertificateManager.RootCertificate
});
proxyServer.AddEndPoint(new TransparentProxyEndPoint(IPAddress.Any, 80, true));
proxyServer.ExceptionFunc = ((Exception exception) =>
{
Logger.Error(exception);
});
try
{
proxyServer.Start();
}
catch (Exception ex)
{
Logger.Error(ex);
return false;
}
//proxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//proxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
#endregion
#if DEBUG
foreach (var endPoint in proxyServer.ProxyEndPoints)
Debug.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
#endif
return true;
}
public void StopProxy()
{
if (proxyServer.ProxyRunning)
{
proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse;
//proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
//proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
proxyServer.Stop();
hostsService.RemoveHostsByTag();
}
@@ -446,14 +382,14 @@ namespace SteamTool.Proxy
if (File.Exists(certifi))
{
var file = File.ReadAllText(certifi);
var s = file.Substring(Const.HOST_TAG, Const.HOST_TAG, true);
var s = file.Substring(Const.HostTag, Const.HostTag, true);
if (string.IsNullOrEmpty(s))
{
File.AppendAllText(certifi, Environment.NewLine + pem);
File.AppendAllText(certifi, "\r\n" + pem);
}
else if (s.Trim() != pem.Trim())
{
var index = file.IndexOf(Const.HOST_TAG);
var index = file.IndexOf(Const.HostTag);
File.WriteAllText(certifi, file.Remove(index, s.Length) + pem);
}
return true;
@@ -9,6 +9,8 @@ namespace SteamTool.Proxy
{
public class ProxyDomainModel
{
public int Index { get; set; }
/// <summary>
/// 显示在UI上的名称
/// </summary>
@@ -17,7 +19,7 @@ namespace SteamTool.Proxy
/// <summary>
/// 代理域名合集
/// </summary>
public IReadOnlyCollection<string> Domains { get; set; }
public List<string> Domains { get; set; }
/// <summary>
/// 端口
@@ -44,12 +46,7 @@ namespace SteamTool.Proxy
/// </summary>
public bool IsEnable { get; set; }
/// <summary>
/// 启用重定向
/// </summary>
public bool IsRedirect { get; set; }
public IReadOnlyCollection<string> Hosts { get; set; }
public IEnumerable<string> Hosts { get; set; }
public DomainTag DomainTag { get; set; }
}
@@ -57,8 +54,14 @@ namespace SteamTool.Proxy
[TypeConverter(typeof(EnumDescriptionTypeConverter))]
public enum DomainTag : byte
{
/// <summary>
/// steam社区
/// </summary>
[Description("steam社区")]
SteamCommunity = 0,
/// <summary>
/// steam商店
/// </summary>
[Description("steam商店")]
SteamStore = 1,
[Description("steam部分图片修复")]
@@ -69,27 +72,13 @@ namespace SteamTool.Proxy
Discord = 4,
[Description("Twitch直播")]
Twitch = 5,
[Description("Origin下载(Akamai)")]
[Description("Origin下载加速(Akamai)")]
OriginGameDownload = 6,
[Description("GOG.COM")]
GOG = 7,
[Description("Uplay更新防劫持")]
UplayUpdate = 7,
[Description("Google(Recaptcha)")]
GoogleCode = 8,
[Description("Github图片及资源修复")]
GithubContent = 9,
[Description("Pixiv")]
Pixiv = 10,
[Description("Discord更新下载")]
DiscordNet = 11,
[Description("Uplay更新防劫持")]
UplayUpdate = 12,
[Description("Twitch聊天")]
TwitchChat = 13,
[Description("Github Gist")]
GithubGist = 14,
[Description("Pixiv图片")]
PixivImage = 15,
[Description("steam图片上传(聊天\\截图)")]
SteamImageUpload = 16,
[Description("GOG.COM")]
GOG = 8,
}
}
@@ -69,33 +69,6 @@ namespace SteamTool.Proxy.Properties {
}
}
/// <summary>
/// 查找类似 Discord图片及更新下载 的本地化字符串。
/// </summary>
public static string DiscordNet {
get {
return ResourceManager.GetString("DiscordNet", resourceCulture);
}
}
/// <summary>
/// 查找类似 Github图片资源修复 的本地化字符串。
/// </summary>
public static string GithubContent {
get {
return ResourceManager.GetString("GithubContent", resourceCulture);
}
}
/// <summary>
/// 查找类似 Github Gist 的本地化字符串。
/// </summary>
public static string GithubGist {
get {
return ResourceManager.GetString("GithubGist", resourceCulture);
}
}
/// <summary>
/// 查找类似 GOG Galaxy 的本地化字符串。
/// </summary>
@@ -132,24 +105,6 @@ namespace SteamTool.Proxy.Properties {
}
}
/// <summary>
/// 查找类似 Pixiv 的本地化字符串。
/// </summary>
public static string Pixiv {
get {
return ResourceManager.GetString("Pixiv", resourceCulture);
}
}
/// <summary>
/// 查找类似 Pixiv图片 的本地化字符串。
/// </summary>
public static string PixivImgae {
get {
return ResourceManager.GetString("PixivImgae", resourceCulture);
}
}
/// <summary>
/// 查找类似 Steam好友聊天 的本地化字符串。
/// </summary>
@@ -177,15 +132,6 @@ namespace SteamTool.Proxy.Properties {
}
}
/// <summary>
/// 查找类似 Steam图片上传(聊天\截图) 的本地化字符串。
/// </summary>
public static string SteamImageUpload {
get {
return ResourceManager.GetString("SteamImageUpload", resourceCulture);
}
}
/// <summary>
/// 查找类似 Steam商店 的本地化字符串。
/// </summary>
@@ -204,15 +150,6 @@ namespace SteamTool.Proxy.Properties {
}
}
/// <summary>
/// 查找类似 Twitch聊天 的本地化字符串。
/// </summary>
public static string TwitchChat {
get {
return ResourceManager.GetString("TwitchChat", resourceCulture);
}
}
/// <summary>
/// 查找类似 Uplay更新 的本地化字符串。
/// </summary>
@@ -120,15 +120,6 @@
<data name="Discord" xml:space="preserve">
<value>Discord语音</value>
</data>
<data name="DiscordNet" xml:space="preserve">
<value>Discord图片及更新下载</value>
</data>
<data name="GithubContent" xml:space="preserve">
<value>Github图片资源修复</value>
</data>
<data name="GithubGist" xml:space="preserve">
<value>Github Gist</value>
</data>
<data name="GOG" xml:space="preserve">
<value>GOG Galaxy</value>
</data>
@@ -141,12 +132,6 @@
<data name="OriginDownload" xml:space="preserve">
<value>Origin游戏下载(Akamai)</value>
</data>
<data name="Pixiv" xml:space="preserve">
<value>Pixiv</value>
</data>
<data name="PixivImgae" xml:space="preserve">
<value>Pixiv图片</value>
</data>
<data name="SteamChat" xml:space="preserve">
<value>Steam好友聊天</value>
</data>
@@ -156,18 +141,12 @@
<data name="SteamImage" xml:space="preserve">
<value>Steam图片</value>
</data>
<data name="SteamImageUpload" xml:space="preserve">
<value>Steam图片上传(聊天\截图)</value>
</data>
<data name="SteamStore" xml:space="preserve">
<value>Steam商店</value>
</data>
<data name="Twitch" xml:space="preserve">
<value>Twitch直播</value>
</data>
<data name="TwitchChat" xml:space="preserve">
<value>Twitch聊天</value>
</data>
<data name="UplayUpdate" xml:space="preserve">
<value>Uplay更新</value>
</data>
@@ -9,9 +9,9 @@
<ItemGroup>
<PackageReference Include="BrotliSharpLib" Version="0.3.3" />
<PackageReference Include="DnsClient" Version="1.4.0" />
<PackageReference Include="DnsClient" Version="1.3.2" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.10" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.8" />
<PackageReference Include="System.Buffers" Version="4.5.1" />
<PackageReference Include="System.Memory" Version="4.5.4" />
<PackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Version="4.3.0" />
@@ -21,7 +21,12 @@
<ItemGroup>
<ProjectReference Include="..\SteamTool.Core\SteamTool.Core.csproj" />
<ProjectReference Include="..\..\references\Titanium-Web-Proxy\src\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Titanium.Web.Proxy">
<HintPath>..\Lib\Titanium.Web.Proxy.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>

Some files were not shown because too many files have changed in this diff Show More