├── .gitattributes ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── Steam Desktop Authenticator ├── App.config ├── CaptchaForm.Designer.cs ├── CaptchaForm.cs ├── CaptchaForm.resx ├── CommandLineOptions.cs ├── ConfirmationButton.cs ├── ConfirmationFormWeb.Designer.cs ├── ConfirmationFormWeb.cs ├── ConfirmationFormWeb.resx ├── FileEncryptor.cs ├── ImportAccountForm.Designer.cs ├── ImportAccountForm.cs ├── ImportAccountForm.resx ├── InputForm.Designer.cs ├── InputForm.cs ├── InputForm.resx ├── ListInputForm.Designer.cs ├── ListInputForm.cs ├── ListInputForm.resx ├── LoginForm.Designer.cs ├── LoginForm.cs ├── LoginForm.resx ├── MaFileEncryptedException.cs ├── MainForm.Designer.cs ├── MainForm.cs ├── MainForm.resx ├── Manifest.cs ├── ManifestParseException.cs ├── PhoneInputForm.Designer.cs ├── PhoneInputForm.cs ├── PhoneInputForm.resx ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── SettingsForm.Designer.cs ├── SettingsForm.cs ├── SettingsForm.resx ├── Steam Desktop Authenticator.csproj ├── Steam Desktop Authenticator.sln ├── TradePopupForm.Designer.cs ├── TradePopupForm.cs ├── TradePopupForm.resx ├── UserFormAuthenticator.cs ├── WelcomeForm.Designer.cs ├── WelcomeForm.cs ├── WelcomeForm.resx ├── icon.ico └── packages.config └── icon.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Coverlet is a free, cross platform Code Coverage Tool 141 | coverage*[.json, .xml, .info] 142 | 143 | # Visual Studio code coverage results 144 | *.coverage 145 | *.coveragexml 146 | 147 | # NCrunch 148 | _NCrunch_* 149 | .*crunch*.local.xml 150 | nCrunchTemp_* 151 | 152 | # MightyMoose 153 | *.mm.* 154 | AutoTest.Net/ 155 | 156 | # Web workbench (sass) 157 | .sass-cache/ 158 | 159 | # Installshield output folder 160 | [Ee]xpress/ 161 | 162 | # DocProject is a documentation generator add-in 163 | DocProject/buildhelp/ 164 | DocProject/Help/*.HxT 165 | DocProject/Help/*.HxC 166 | DocProject/Help/*.hhc 167 | DocProject/Help/*.hhk 168 | DocProject/Help/*.hhp 169 | DocProject/Help/Html2 170 | DocProject/Help/html 171 | 172 | # Click-Once directory 173 | publish/ 174 | 175 | # Publish Web Output 176 | *.[Pp]ublish.xml 177 | *.azurePubxml 178 | # Note: Comment the next line if you want to checkin your web deploy settings, 179 | # but database connection strings (with potential passwords) will be unencrypted 180 | *.pubxml 181 | *.publishproj 182 | 183 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 184 | # checkin your Azure Web App publish settings, but sensitive information contained 185 | # in these scripts will be unencrypted 186 | PublishScripts/ 187 | 188 | # NuGet Packages 189 | *.nupkg 190 | # NuGet Symbol Packages 191 | *.snupkg 192 | # The packages folder can be ignored because of Package Restore 193 | **/[Pp]ackages/* 194 | # except build/, which is used as an MSBuild target. 195 | !**/[Pp]ackages/build/ 196 | # Uncomment if necessary however generally it will be regenerated when needed 197 | #!**/[Pp]ackages/repositories.config 198 | # NuGet v3's project.json files produces more ignorable files 199 | *.nuget.props 200 | *.nuget.targets 201 | 202 | # Microsoft Azure Build Output 203 | csx/ 204 | *.build.csdef 205 | 206 | # Microsoft Azure Emulator 207 | ecf/ 208 | rcf/ 209 | 210 | # Windows Store app package directories and files 211 | AppPackages/ 212 | BundleArtifacts/ 213 | Package.StoreAssociation.xml 214 | _pkginfo.txt 215 | *.appx 216 | *.appxbundle 217 | *.appxupload 218 | 219 | # Visual Studio cache files 220 | # files ending in .cache can be ignored 221 | *.[Cc]ache 222 | # but keep track of directories ending in .cache 223 | !?*.[Cc]ache/ 224 | 225 | # Others 226 | ClientBin/ 227 | ~$* 228 | *~ 229 | *.dbmdl 230 | *.dbproj.schemaview 231 | *.jfm 232 | *.pfx 233 | *.publishsettings 234 | orleans.codegen.cs 235 | 236 | # Including strong name files can present a security risk 237 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 238 | #*.snk 239 | 240 | # Since there are multiple workflows, uncomment next line to ignore bower_components 241 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 242 | #bower_components/ 243 | 244 | # RIA/Silverlight projects 245 | Generated_Code/ 246 | 247 | # Backup & report files from converting an old project file 248 | # to a newer Visual Studio version. Backup files are not needed, 249 | # because we have git ;-) 250 | _UpgradeReport_Files/ 251 | Backup*/ 252 | UpgradeLog*.XML 253 | UpgradeLog*.htm 254 | ServiceFabricBackup/ 255 | *.rptproj.bak 256 | 257 | # SQL Server files 258 | *.mdf 259 | *.ldf 260 | *.ndf 261 | 262 | # Business Intelligence projects 263 | *.rdl.data 264 | *.bim.layout 265 | *.bim_*.settings 266 | *.rptproj.rsuser 267 | *- [Bb]ackup.rdl 268 | *- [Bb]ackup ([0-9]).rdl 269 | *- [Bb]ackup ([0-9][0-9]).rdl 270 | 271 | # Microsoft Fakes 272 | FakesAssemblies/ 273 | 274 | # GhostDoc plugin setting file 275 | *.GhostDoc.xml 276 | 277 | # Node.js Tools for Visual Studio 278 | .ntvs_analysis.dat 279 | node_modules/ 280 | 281 | # Visual Studio 6 build log 282 | *.plg 283 | 284 | # Visual Studio 6 workspace options file 285 | *.opt 286 | 287 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 288 | *.vbw 289 | 290 | # Visual Studio LightSwitch build output 291 | **/*.HTMLClient/GeneratedArtifacts 292 | **/*.DesktopClient/GeneratedArtifacts 293 | **/*.DesktopClient/ModelManifest.xml 294 | **/*.Server/GeneratedArtifacts 295 | **/*.Server/ModelManifest.xml 296 | _Pvt_Extensions 297 | 298 | # Paket dependency manager 299 | .paket/paket.exe 300 | paket-files/ 301 | 302 | # FAKE - F# Make 303 | .fake/ 304 | 305 | # CodeRush personal settings 306 | .cr/personal 307 | 308 | # Python Tools for Visual Studio (PTVS) 309 | __pycache__/ 310 | *.pyc 311 | 312 | # Cake - Uncomment if you are using it 313 | # tools/** 314 | # !tools/packages.config 315 | 316 | # Tabs Studio 317 | *.tss 318 | 319 | # Telerik's JustMock configuration file 320 | *.jmconfig 321 | 322 | # BizTalk build output 323 | *.btp.cs 324 | *.btm.cs 325 | *.odx.cs 326 | *.xsd.cs 327 | 328 | # OpenCover UI analysis results 329 | OpenCover/ 330 | 331 | # Azure Stream Analytics local run output 332 | ASALocalRun/ 333 | 334 | # MSBuild Binary and Structured Log 335 | *.binlog 336 | 337 | # NVidia Nsight GPU debugger configuration file 338 | *.nvuser 339 | 340 | # MFractors (Xamarin productivity tool) working folder 341 | .mfractor/ 342 | 343 | # Local History for Visual Studio 344 | .localhistory/ 345 | 346 | # BeatPulse healthcheck temp database 347 | healthchecksdb 348 | 349 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 350 | MigrationBackup/ 351 | 352 | # Ionide (cross platform F# VS Code tools) working folder 353 | .ionide/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/SteamAuth"] 2 | path = lib/SteamAuth 3 | url = https://github.com/geel9/SteamAuth 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jesse Cardone 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 | Steam Desktop Authenticator 5 |

6 |

7 | A desktop implementation of Steam's mobile authenticator app.
8 | We are not affiliated with Steam or Scrap.TF in any way! This project is run by community volunteers. 9 |

10 |

11 | WARNING: Recently there have been fake versions of SDA floating around that will steal your Steam account. Never download SDA from any place other than this github repo! 12 |

13 |

14 | Download here 15 |

16 |

Supports Windows 7 and up.

17 |
18 | 19 | 20 | **DISCLAIMER: We provide no support for you when using Steam Desktop Authenticator! This project is run by community volunteers and is not affiliated with Steam or Scrap.TF. You use this program at your own risk, and accept the responsibility to make backups and prevent unauthorized access to your computer!** 21 | 22 | **REMEMBER: Always make backups of your `maFiles` directory! If you lose your encryption key or delete `maFiles` by accident AND you didn't save your revocation code, you are screwed.** 23 | 24 | **FINALLY: Using this application is a bad idea, because it COMPLETELY DEFEATS THE PURPOSE of two-factor authentication! If your desktop is infected with a virus, it will be able to hijack the authenticator app and completely subvert the protection. THIS APPLICATION DOES NOT PROTECT YOUR ACCOUNT; IT ONLY ALLOWS YOU TO USE STEAM FEATURES THAT REQUIRE THE AUTHENTICATOR WITHOUT HAVING A PHONE. If you have a phone that supports the Mobile Authenticator, you really shouldn't use this application!** 25 | 26 | IF you lost your `maFiles` OR lost your encryption key, go [here](https://store.steampowered.com/twofactor/manage) and click "Remove Authenticator" then enter your revocation code that you wrote down when you first added your account to SDA. 27 | 28 | If you did not follow the directions and did not write your revocation code down, you're well and truly screwed. The only option is beg to [Steam Support](https://support.steampowered.com/) and say you lost your mobile authenticator and the revocation code. 29 | 30 | ## Detailed setup instructions 31 | - Download & Install [.NET Framework 4.7.2](https://www.microsoft.com/net/download/dotnet-framework-runtime/net472) if you're using Windows 7. Windows 8 and above should do this automatically for you. 32 | - Visit [the releases page](https://github.com/SteamDesktopAuthenticatorr/SteamDesktopAuthenticator/releases) and download the latest .zip (not the source code one). 33 | - Extract the files somewhere very safe on your computer. If you lose the files you can lose access to your Steam account. 34 | - Run `Steam Desktop Authenticator.exe` and click the button to set up a new account. 35 | - Login to Steam and follow the instructions to set it up. **Note: you still need a mobile phone that can receive SMS.** 36 | - You may be asked to set up encryption, this is to make sure if someone gains access to your computer they can't steal your Steam account from this program. This is optional but highly recommended. 37 | - Select your account from the list to view the current login code, and click `Trade Confirmations` to see pending trade confirmations. 38 | - For your safety, remember to get Steam Guard backup codes! Follow [this link](https://store.steampowered.com/twofactor/manage) and click "Get Backup Codes," then print out that page and save it in a safe place. You can use these codes if you lose access to your authenticator. 39 | 40 | [How to update SDA.](https://github.com/SteamDesktopAuthenticatorr/SteamDesktopAuthenticator/wiki/Updating) 41 | 42 | [How to use SDA on multiple computers.](https://github.com/SteamDesktopAuthenticatorr/SteamDesktopAuthenticator/wiki/Using-SDA-on-multiple-computers) 43 | 44 | 45 | ## Command line options 46 | ``` 47 | -k [encryption key] 48 | Set your encryption key when opened 49 | -s 50 | Auto-minimize to tray when opened 51 | ``` 52 | 53 | ## Troubleshooting 54 | - **Trade confirmation list is just white or a blank screen** 55 | - First open the "Selected Account" menu, then click "Force session refresh". If it still doesn't work after that, open the "Selected Account" menu again, then click "Login again" and login to your Steam account. 56 | 57 | If your problem doesn't appear on the list or none of the solutions worked, submit an issue on the issue tracker. When posting logs in an issue, please upload it to some site like [Pastebin](http://www.pastebin.com). 58 | 59 | ## Building on linux 60 | - First, you will need to install the `mono` and `monodevelop` packages, usually available from your standard package repository. 61 | - Open monodevelop and select File -> Open. Navigate to the folder where you cloned this program to and open the file "Steam Desktop Authenticator/Steam Desktop Authenticator.sln" 62 | - If you initialized submodules correctly, you should see two tree hirarchies on the left side of the screen, one labeled **SteamDesktopAuthenticator** and the other **SteamAuth**. (If you didn't, an error will be displayed; go update them!) For both of them, select "Packages", right click on "Newtonsoft.Json", and click update. Remember to do this for **both SteamDesktopAuthenticator and SteamAuth** 63 | - Select Project->Active Configuration->Release (this will make this application run faster) 64 | - Select Build->Build All. The package should now build successfully. 65 | - The resulting executable and files will be in "Steam Desktop Authenticator/bin/Release" 66 | -------------------------------------------------------------------------------- /Steam Desktop Authenticator/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Steam Desktop Authenticator/CaptchaForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace Steam_Desktop_Authenticator 2 | { 3 | partial class CaptchaForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(CaptchaForm)); 32 | this.labelText = new System.Windows.Forms.Label(); 33 | this.txtBox = new System.Windows.Forms.TextBox(); 34 | this.btnAccept = new System.Windows.Forms.Button(); 35 | this.btnCancel = new System.Windows.Forms.Button(); 36 | this.pictureBoxCaptcha = new System.Windows.Forms.PictureBox(); 37 | ((System.ComponentModel.ISupportInitialize)(this.pictureBoxCaptcha)).BeginInit(); 38 | this.SuspendLayout(); 39 | // 40 | // labelText 41 | // 42 | this.labelText.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 43 | this.labelText.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 44 | this.labelText.ForeColor = System.Drawing.SystemColors.ControlText; 45 | this.labelText.Location = new System.Drawing.Point(-1, 14); 46 | this.labelText.Name = "labelText"; 47 | this.labelText.Size = new System.Drawing.Size(233, 18); 48 | this.labelText.TabIndex = 0; 49 | this.labelText.Text = "Please enter the following captcha code:"; 50 | this.labelText.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; 51 | // 52 | // txtBox 53 | // 54 | this.txtBox.Font = new System.Drawing.Font("Segoe UI", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 55 | this.txtBox.Location = new System.Drawing.Point(12, 84); 56 | this.txtBox.Name = "txtBox"; 57 | this.txtBox.Size = new System.Drawing.Size(206, 33); 58 | this.txtBox.TabIndex = 1; 59 | // 60 | // btnAccept 61 | // 62 | this.btnAccept.Location = new System.Drawing.Point(11, 123); 63 | this.btnAccept.Name = "btnAccept"; 64 | this.btnAccept.Size = new System.Drawing.Size(98, 28); 65 | this.btnAccept.TabIndex = 2; 66 | this.btnAccept.Text = "Submit"; 67 | this.btnAccept.UseVisualStyleBackColor = true; 68 | this.btnAccept.Click += new System.EventHandler(this.btnAccept_Click); 69 | // 70 | // btnCancel 71 | // 72 | this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; 73 | this.btnCancel.Location = new System.Drawing.Point(120, 123); 74 | this.btnCancel.Name = "btnCancel"; 75 | this.btnCancel.Size = new System.Drawing.Size(98, 28); 76 | this.btnCancel.TabIndex = 3; 77 | this.btnCancel.Text = "Cancel"; 78 | this.btnCancel.UseVisualStyleBackColor = true; 79 | this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); 80 | // 81 | // pictureBoxCaptcha 82 | // 83 | this.pictureBoxCaptcha.Location = new System.Drawing.Point(12, 37); 84 | this.pictureBoxCaptcha.Name = "pictureBoxCaptcha"; 85 | this.pictureBoxCaptcha.Size = new System.Drawing.Size(206, 40); 86 | this.pictureBoxCaptcha.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage; 87 | this.pictureBoxCaptcha.TabIndex = 4; 88 | this.pictureBoxCaptcha.TabStop = false; 89 | // 90 | // CaptchaForm 91 | // 92 | this.AcceptButton = this.btnAccept; 93 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 94 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 95 | this.CancelButton = this.btnCancel; 96 | this.ClientSize = new System.Drawing.Size(232, 164); 97 | this.Controls.Add(this.pictureBoxCaptcha); 98 | this.Controls.Add(this.btnCancel); 99 | this.Controls.Add(this.btnAccept); 100 | this.Controls.Add(this.txtBox); 101 | this.Controls.Add(this.labelText); 102 | this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 103 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 104 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 105 | this.Name = "CaptchaForm"; 106 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 107 | ((System.ComponentModel.ISupportInitialize)(this.pictureBoxCaptcha)).EndInit(); 108 | this.ResumeLayout(false); 109 | this.PerformLayout(); 110 | 111 | } 112 | 113 | #endregion 114 | 115 | private System.Windows.Forms.Label labelText; 116 | public System.Windows.Forms.TextBox txtBox; 117 | private System.Windows.Forms.Button btnAccept; 118 | private System.Windows.Forms.Button btnCancel; 119 | private System.Windows.Forms.PictureBox pictureBoxCaptcha; 120 | } 121 | } -------------------------------------------------------------------------------- /Steam Desktop Authenticator/CaptchaForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | 11 | namespace Steam_Desktop_Authenticator 12 | { 13 | public partial class CaptchaForm : Form 14 | { 15 | public bool Canceled = false; 16 | public string CaptchaGID = ""; 17 | public string CaptchaURL = ""; 18 | public string CaptchaCode 19 | { 20 | get 21 | { 22 | return this.txtBox.Text; 23 | } 24 | } 25 | 26 | public CaptchaForm(string GID) 27 | { 28 | this.CaptchaGID = GID; 29 | this.CaptchaURL = "https://steamcommunity.com/public/captcha.php?gid=" + GID; 30 | InitializeComponent(); 31 | this.pictureBoxCaptcha.Load(CaptchaURL); 32 | } 33 | 34 | private void btnAccept_Click(object sender, EventArgs e) 35 | { 36 | this.Canceled = false; 37 | this.Close(); 38 | } 39 | 40 | private void btnCancel_Click(object sender, EventArgs e) 41 | { 42 | this.Canceled = true; 43 | this.Close(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Steam Desktop Authenticator/CommandLineOptions.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using CommandLine.Text; 3 | 4 | namespace Steam_Desktop_Authenticator 5 | { 6 | class CommandLineOptions 7 | { 8 | [Option('k', "encryption-key", Required = false, 9 | HelpText = "Encryption key for manifest")] 10 | public string EncryptionKey { get; set; } 11 | 12 | [Option('s', "silent", Required = false, 13 | HelpText = "Start minimized")] 14 | public bool Silent { get; set; } 15 | 16 | [ParserState] 17 | public IParserState LastParserState { get; set; } 18 | 19 | [HelpOption] 20 | public string GetUsage() 21 | { 22 | return HelpText.AutoBuild(this, 23 | (HelpText current) => HelpText.DefaultParsingErrorsHandler(this, current)); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Steam Desktop Authenticator/ConfirmationButton.cs: -------------------------------------------------------------------------------- 1 | using SteamAuth; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Forms; 8 | 9 | namespace Steam_Desktop_Authenticator 10 | { 11 | public class ConfirmationButton : Button 12 | { 13 | public Confirmation Confirmation { get; set; } 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Steam Desktop Authenticator/ConfirmationFormWeb.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace Steam_Desktop_Authenticator 2 | { 3 | partial class ConfirmationFormWeb 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ConfirmationFormWeb)); 32 | this.splitContainer1 = new System.Windows.Forms.SplitContainer(); 33 | this.btnRefresh = new System.Windows.Forms.Button(); 34 | ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); 35 | this.splitContainer1.Panel1.SuspendLayout(); 36 | this.splitContainer1.SuspendLayout(); 37 | this.SuspendLayout(); 38 | // 39 | // splitContainer1 40 | // 41 | this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; 42 | this.splitContainer1.IsSplitterFixed = true; 43 | this.splitContainer1.Location = new System.Drawing.Point(0, 0); 44 | this.splitContainer1.Name = "splitContainer1"; 45 | this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; 46 | // 47 | // splitContainer1.Panel1 48 | // 49 | this.splitContainer1.Panel1.Controls.Add(this.btnRefresh); 50 | this.splitContainer1.Size = new System.Drawing.Size(431, 641); 51 | this.splitContainer1.SplitterDistance = 30; 52 | this.splitContainer1.SplitterWidth = 1; 53 | this.splitContainer1.TabIndex = 0; 54 | // 55 | // btnRefresh 56 | // 57 | this.btnRefresh.Dock = System.Windows.Forms.DockStyle.Fill; 58 | this.btnRefresh.Location = new System.Drawing.Point(0, 0); 59 | this.btnRefresh.Name = "btnRefresh"; 60 | this.btnRefresh.Size = new System.Drawing.Size(431, 30); 61 | this.btnRefresh.TabIndex = 0; 62 | this.btnRefresh.Text = "Refresh"; 63 | this.btnRefresh.UseVisualStyleBackColor = true; 64 | this.btnRefresh.Click += new System.EventHandler(this.btnRefresh_Click); 65 | // 66 | // ConfirmationFormWeb 67 | // 68 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 69 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 70 | this.ClientSize = new System.Drawing.Size(431, 641); 71 | this.Controls.Add(this.splitContainer1); 72 | this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 73 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 74 | this.Name = "ConfirmationFormWeb"; 75 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 76 | this.Text = "Trade Confirmations"; 77 | this.Shown += new System.EventHandler(this.ConfirmationFormWeb_Shown); 78 | this.splitContainer1.Panel1.ResumeLayout(false); 79 | ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); 80 | this.splitContainer1.ResumeLayout(false); 81 | this.ResumeLayout(false); 82 | 83 | } 84 | 85 | #endregion 86 | 87 | private System.Windows.Forms.SplitContainer splitContainer1; 88 | private System.Windows.Forms.Button btnRefresh; 89 | } 90 | } -------------------------------------------------------------------------------- /Steam Desktop Authenticator/ConfirmationFormWeb.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Threading.Tasks; 4 | using System.Windows.Forms; 5 | using SteamAuth; 6 | using System.Drawing.Drawing2D; 7 | 8 | namespace Steam_Desktop_Authenticator 9 | { 10 | public partial class ConfirmationFormWeb : Form 11 | { 12 | private SteamGuardAccount steamAccount; 13 | 14 | public ConfirmationFormWeb(SteamGuardAccount steamAccount) 15 | { 16 | InitializeComponent(); 17 | this.steamAccount = steamAccount; 18 | this.Text = String.Format("Trade Confirmations - {0}", steamAccount.AccountName); 19 | } 20 | private async Task LoadData() 21 | { 22 | this.splitContainer1.Panel2.Controls.Clear(); 23 | 24 | // Check for a valid refresh token first 25 | if (steamAccount.Session.IsRefreshTokenExpired()) 26 | { 27 | MessageBox.Show("Your session has expired. Use the login again button under the selected account menu.", "Trade Confirmations", MessageBoxButtons.OK, MessageBoxIcon.Error); 28 | this.Close(); 29 | return; 30 | } 31 | 32 | // Check for a valid access token, refresh it if needed 33 | if (steamAccount.Session.IsAccessTokenExpired()) 34 | { 35 | try 36 | { 37 | await steamAccount.Session.RefreshAccessToken(); 38 | } 39 | catch (Exception ex) 40 | { 41 | MessageBox.Show(ex.Message, "Steam Login Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 42 | this.Close(); 43 | return; 44 | } 45 | } 46 | 47 | try 48 | { 49 | var confirmations = await steamAccount.FetchConfirmationsAsync(); 50 | 51 | if (confirmations == null || confirmations.Length == 0) 52 | { 53 | Label errorLabel = new Label() { Text = "Nothing to confirm/cancel", AutoSize = true, ForeColor = Color.Black, Location = new Point(150, 20) }; 54 | this.splitContainer1.Panel2.Controls.Add(errorLabel); 55 | } 56 | 57 | foreach (var confirmation in confirmations) 58 | { 59 | Panel panel = new Panel() { Dock = DockStyle.Top, Height = 120 }; 60 | panel.Paint += (s, e) => 61 | { 62 | using (LinearGradientBrush brush = new LinearGradientBrush(panel.ClientRectangle, Color.Black, Color.DarkCyan, 90F)) 63 | { 64 | e.Graphics.FillRectangle(brush, panel.ClientRectangle); 65 | } 66 | }; 67 | 68 | if (!string.IsNullOrEmpty(confirmation.Icon)) 69 | { 70 | PictureBox pictureBox = new PictureBox() { Width = 60, Height = 60, Location = new Point(20, 20), SizeMode = PictureBoxSizeMode.Zoom }; 71 | pictureBox.Load(confirmation.Icon); 72 | panel.Controls.Add(pictureBox); 73 | } 74 | 75 | Label nameLabel = new Label() 76 | { 77 | Text = $"{confirmation.Headline}\n{confirmation.Creator.ToString()}", 78 | AutoSize = true, 79 | ForeColor = Color.Snow, 80 | Location = new Point(90, 20), 81 | BackColor = Color.Transparent 82 | }; 83 | panel.Controls.Add(nameLabel); 84 | 85 | ConfirmationButton acceptButton = new ConfirmationButton() 86 | { 87 | Text = confirmation.Accept, 88 | Location = new Point(90, 50), 89 | FlatStyle = FlatStyle.Flat, 90 | FlatAppearance = { BorderSize = 0 }, 91 | BackColor = Color.Black, 92 | ForeColor = Color.Snow, 93 | Confirmation = confirmation 94 | }; 95 | acceptButton.Click += btnAccept_Click; 96 | panel.Controls.Add(acceptButton); 97 | 98 | ConfirmationButton cancelButton = new ConfirmationButton() 99 | { 100 | Text = confirmation.Cancel, 101 | Location = new Point(180, 50), 102 | FlatStyle = FlatStyle.Flat, 103 | FlatAppearance = { BorderSize = 0 }, 104 | BackColor = Color.Black, 105 | ForeColor = Color.Snow, 106 | Confirmation = confirmation 107 | }; 108 | cancelButton.Click += btnCancel_Click; 109 | panel.Controls.Add(cancelButton); 110 | 111 | Label summaryLabel = new Label() 112 | { 113 | Text = String.Join("\n", confirmation.Summary), 114 | AutoSize = true, 115 | ForeColor = Color.Snow, 116 | Location = new Point(90, 80), 117 | BackColor = Color.Transparent 118 | }; 119 | panel.Controls.Add(summaryLabel); 120 | 121 | this.splitContainer1.Panel2.Controls.Add(panel); 122 | } 123 | } 124 | catch (Exception ex) 125 | { 126 | Label errorLabel = new Label() { Text = "Something went wrong:\n" + ex.Message, AutoSize = true, ForeColor = Color.Red, Location = new Point(20, 20) }; 127 | this.splitContainer1.Panel2.Controls.Add(errorLabel); 128 | } 129 | } 130 | 131 | private async void btnAccept_Click(object sender, EventArgs e) 132 | { 133 | var button = (ConfirmationButton)sender; 134 | var confirmation = button.Confirmation; 135 | bool result = await steamAccount.AcceptConfirmation(confirmation); 136 | 137 | await this.LoadData(); 138 | } 139 | 140 | private async void btnCancel_Click(object sender, EventArgs e) 141 | { 142 | var button = (ConfirmationButton)sender; 143 | var confirmation = button.Confirmation; 144 | bool result = await steamAccount.DenyConfirmation(confirmation); 145 | 146 | await this.LoadData(); 147 | } 148 | 149 | 150 | private async void btnRefresh_Click(object sender, EventArgs e) 151 | { 152 | this.btnRefresh.Enabled = false; 153 | this.btnRefresh.Text = "Refreshing..."; 154 | 155 | await this.LoadData(); 156 | 157 | this.btnRefresh.Enabled = true; 158 | this.btnRefresh.Text = "Refresh"; 159 | } 160 | 161 | private async void ConfirmationFormWeb_Shown(object sender, EventArgs e) 162 | { 163 | this.btnRefresh.Enabled = false; 164 | this.btnRefresh.Text = "Refreshing..."; 165 | 166 | await this.LoadData(); 167 | 168 | this.btnRefresh.Enabled = true; 169 | this.btnRefresh.Text = "Refresh"; 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /Steam Desktop Authenticator/FileEncryptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Security.Cryptography; 7 | using System.IO; 8 | 9 | namespace Steam_Desktop_Authenticator 10 | { 11 | /// 12 | /// This class provides the controls that will encrypt and decrypt the *.maFile files 13 | /// 14 | /// Passwords entered will be passed into 100k rounds of PBKDF2 (RFC2898) with a cryptographically random salt. 15 | /// The generated key will then be passed into AES-256 (RijndalManaged) which will encrypt the data 16 | /// in cypher block chaining (CBC) mode, and then write both the PBKDF2 salt and encrypted data onto the disk. 17 | /// 18 | public static class FileEncryptor 19 | { 20 | private const int PBKDF2_ITERATIONS = 50000; //Set to 50k to make program not unbearably slow. May increase in future. 21 | private const int SALT_LENGTH = 8; 22 | private const int KEY_SIZE_BYTES = 32; 23 | private const int IV_LENGTH = 16; 24 | 25 | /// 26 | /// Returns an 8-byte cryptographically random salt in base64 encoding 27 | /// 28 | /// 29 | public static string GetRandomSalt() 30 | { 31 | byte[] salt = new byte[SALT_LENGTH]; 32 | using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) 33 | { 34 | rng.GetBytes(salt); 35 | } 36 | return Convert.ToBase64String(salt); 37 | } 38 | 39 | /// 40 | /// Returns a 16-byte cryptographically random initialization vector (IV) in base64 encoding 41 | /// 42 | /// 43 | public static string GetInitializationVector() 44 | { 45 | byte[] IV = new byte[IV_LENGTH]; 46 | using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) 47 | { 48 | rng.GetBytes(IV); 49 | } 50 | return Convert.ToBase64String(IV); 51 | } 52 | 53 | 54 | /// 55 | /// Generates an encryption key derived using a password, a random salt, and specified number of rounds of PBKDF2 56 | /// 57 | /// TODO: pass in password via SecureString? 58 | /// 59 | /// 60 | /// 61 | /// 62 | private static byte[] GetEncryptionKey(string password, string salt) 63 | { 64 | if (string.IsNullOrEmpty(password)) 65 | { 66 | throw new ArgumentException("Password is empty"); 67 | } 68 | if (string.IsNullOrEmpty(salt)) 69 | { 70 | throw new ArgumentException("Salt is empty"); 71 | } 72 | using (Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, Convert.FromBase64String(salt), PBKDF2_ITERATIONS)) 73 | { 74 | return pbkdf2.GetBytes(KEY_SIZE_BYTES); 75 | } 76 | } 77 | 78 | /// 79 | /// Tries to decrypt and return data given an encrypted base64 encoded string. Must use the same 80 | /// password, salt, IV, and ciphertext that was used during the original encryption of the data. 81 | /// 82 | /// 83 | /// 84 | /// Initialization Vector 85 | /// 86 | /// 87 | public static string DecryptData(string password, string passwordSalt, string IV, string encryptedData) 88 | { 89 | if (string.IsNullOrEmpty(password)) 90 | { 91 | throw new ArgumentException("Password is empty"); 92 | } 93 | if (string.IsNullOrEmpty(passwordSalt)) 94 | { 95 | throw new ArgumentException("Salt is empty"); 96 | } 97 | if (string.IsNullOrEmpty(IV)) 98 | { 99 | throw new ArgumentException("Initialization Vector is empty"); 100 | } 101 | if (string.IsNullOrEmpty(encryptedData)) 102 | { 103 | throw new ArgumentException("Encrypted data is empty"); 104 | } 105 | 106 | byte[] cipherText = Convert.FromBase64String(encryptedData); 107 | byte[] key = GetEncryptionKey(password, passwordSalt); 108 | string plaintext = null; 109 | 110 | using (RijndaelManaged aes256 = new RijndaelManaged()) 111 | { 112 | aes256.IV = Convert.FromBase64String(IV); 113 | aes256.Key = key; 114 | aes256.Padding = PaddingMode.PKCS7; 115 | aes256.Mode = CipherMode.CBC; 116 | 117 | //create decryptor to perform the stream transform 118 | ICryptoTransform decryptor = aes256.CreateDecryptor(aes256.Key, aes256.IV); 119 | 120 | //wrap in a try since a bad password yields a bad key, which would throw an exception on decrypt 121 | try 122 | { 123 | using (MemoryStream msDecrypt = new MemoryStream(cipherText)) 124 | { 125 | using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) 126 | { 127 | using (StreamReader srDecrypt = new StreamReader(csDecrypt)) 128 | { 129 | plaintext = srDecrypt.ReadToEnd(); 130 | } 131 | } 132 | } 133 | } 134 | catch (CryptographicException) 135 | { 136 | plaintext = null; 137 | } 138 | } 139 | return plaintext; 140 | } 141 | 142 | /// 143 | /// Encrypts a string given a password, salt, and initialization vector, then returns result in base64 encoded string. 144 | /// 145 | /// To retrieve this data, you must decrypt with the same password, salt, IV, and cyphertext that was used during encryption 146 | /// 147 | /// 148 | /// 149 | /// 150 | /// 151 | /// 152 | public static string EncryptData(string password, string passwordSalt, string IV, string plaintext) 153 | { 154 | if (string.IsNullOrEmpty(password)) 155 | { 156 | throw new ArgumentException("Password is empty"); 157 | } 158 | if (string.IsNullOrEmpty(passwordSalt)) 159 | { 160 | throw new ArgumentException("Salt is empty"); 161 | } 162 | if (string.IsNullOrEmpty(IV)) 163 | { 164 | throw new ArgumentException("Initialization Vector is empty"); 165 | } 166 | if (string.IsNullOrEmpty(plaintext)) 167 | { 168 | throw new ArgumentException("Plaintext data is empty"); 169 | } 170 | byte[] key = GetEncryptionKey(password, passwordSalt); 171 | byte[] ciphertext; 172 | 173 | using (RijndaelManaged aes256 = new RijndaelManaged()) 174 | { 175 | aes256.Key = key; 176 | aes256.IV = Convert.FromBase64String(IV); 177 | aes256.Padding = PaddingMode.PKCS7; 178 | aes256.Mode = CipherMode.CBC; 179 | 180 | ICryptoTransform encryptor = aes256.CreateEncryptor(aes256.Key, aes256.IV); 181 | 182 | using (MemoryStream msEncrypt = new MemoryStream()) 183 | { 184 | using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) 185 | { 186 | using (StreamWriter swEncypt = new StreamWriter(csEncrypt)) 187 | { 188 | swEncypt.Write(plaintext); 189 | } 190 | ciphertext = msEncrypt.ToArray(); 191 | } 192 | } 193 | } 194 | return Convert.ToBase64String(ciphertext); 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /Steam Desktop Authenticator/ImportAccountForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace Steam_Desktop_Authenticator 2 | { 3 | partial class ImportAccountForm 4 | { 5 | #region Windows Form Designer generated code 6 | 7 | /// 8 | /// Required method for Designer support - do not modify 9 | /// the contents of this method with the code editor. 10 | /// 11 | private void InitializeComponent() 12 | { 13 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ImportAccountForm)); 14 | this.labelText = new System.Windows.Forms.Label(); 15 | this.txtBox = new System.Windows.Forms.TextBox(); 16 | this.btnImport = new System.Windows.Forms.Button(); 17 | this.btnCancel = new System.Windows.Forms.Button(); 18 | this.label1 = new System.Windows.Forms.Label(); 19 | this.SuspendLayout(); 20 | // 21 | // labelText 22 | // 23 | this.labelText.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 24 | this.labelText.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 25 | this.labelText.ForeColor = System.Drawing.SystemColors.ControlText; 26 | this.labelText.Location = new System.Drawing.Point(15, 14); 27 | this.labelText.Name = "labelText"; 28 | this.labelText.Size = new System.Drawing.Size(317, 25); 29 | this.labelText.TabIndex = 0; 30 | this.labelText.Text = "Enter your encryption passkey if your .maFile is encrypted:"; 31 | // 32 | // txtBox 33 | // 34 | this.txtBox.Font = new System.Drawing.Font("Segoe UI", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 35 | this.txtBox.Location = new System.Drawing.Point(15, 42); 36 | this.txtBox.Name = "txtBox"; 37 | this.txtBox.Size = new System.Drawing.Size(307, 33); 38 | this.txtBox.TabIndex = 1; 39 | // 40 | // btnImport 41 | // 42 | this.btnImport.Location = new System.Drawing.Point(135, 131); 43 | this.btnImport.Name = "btnImport"; 44 | this.btnImport.Size = new System.Drawing.Size(187, 28); 45 | this.btnImport.TabIndex = 3; 46 | this.btnImport.Text = "Select .maFile file to Import"; 47 | this.btnImport.UseVisualStyleBackColor = true; 48 | this.btnImport.Click += new System.EventHandler(this.btnImport_Click); 49 | // 50 | // btnCancel 51 | // 52 | this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; 53 | this.btnCancel.Location = new System.Drawing.Point(41, 131); 54 | this.btnCancel.Name = "btnCancel"; 55 | this.btnCancel.Size = new System.Drawing.Size(88, 28); 56 | this.btnCancel.TabIndex = 4; 57 | this.btnCancel.Text = "Cancel"; 58 | this.btnCancel.UseVisualStyleBackColor = true; 59 | this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); 60 | // 61 | // label1 62 | // 63 | this.label1.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 64 | this.label1.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 65 | this.label1.ForeColor = System.Drawing.SystemColors.ControlText; 66 | this.label1.Location = new System.Drawing.Point(15, 83); 67 | this.label1.Name = "label1"; 68 | this.label1.Size = new System.Drawing.Size(307, 40); 69 | this.label1.TabIndex = 2; 70 | this.label1.Text = "If you import an encrypted .maFile, the manifest file must be next to it."; 71 | // 72 | // ImportAccountForm 73 | // 74 | this.AcceptButton = this.btnImport; 75 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 76 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 77 | this.CancelButton = this.btnCancel; 78 | this.ClientSize = new System.Drawing.Size(336, 171); 79 | this.Controls.Add(this.label1); 80 | this.Controls.Add(this.btnCancel); 81 | this.Controls.Add(this.btnImport); 82 | this.Controls.Add(this.txtBox); 83 | this.Controls.Add(this.labelText); 84 | this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 85 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 86 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 87 | this.MaximizeBox = false; 88 | this.Name = "ImportAccountForm"; 89 | this.ShowInTaskbar = false; 90 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 91 | this.Text = "Import Account"; 92 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Import_maFile_Form_FormClosing); 93 | this.ResumeLayout(false); 94 | this.PerformLayout(); 95 | 96 | } 97 | 98 | #endregion 99 | 100 | private System.Windows.Forms.Label labelText; 101 | private System.Windows.Forms.TextBox txtBox; 102 | private System.Windows.Forms.Button btnImport; 103 | private System.Windows.Forms.Button btnCancel; 104 | private System.Windows.Forms.Label label1; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Steam Desktop Authenticator/ImportAccountForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | using SteamAuth; 11 | using Newtonsoft.Json; 12 | using Newtonsoft.Json.Linq; 13 | using System.IO; 14 | 15 | namespace Steam_Desktop_Authenticator 16 | { 17 | public partial class ImportAccountForm : Form 18 | { 19 | private Manifest mManifest; 20 | 21 | public ImportAccountForm() 22 | { 23 | InitializeComponent(); 24 | this.mManifest = Manifest.GetManifest(); 25 | } 26 | 27 | private void btnImport_Click(object sender, EventArgs e) 28 | { 29 | // check if data already added is encripted 30 | #region check if data already added is encripted 31 | string ContiuneImport = "0"; 32 | 33 | string ManifestFile = "maFiles/manifest.json"; 34 | if (File.Exists(ManifestFile)) 35 | { 36 | string AppManifestContents = File.ReadAllText(ManifestFile); 37 | AppManifest AppManifestData = JsonConvert.DeserializeObject(AppManifestContents); 38 | bool AppManifestData_encrypted = AppManifestData.Encrypted; 39 | if (AppManifestData_encrypted == true) 40 | { 41 | MessageBox.Show("You can't import an .maFile because the existing account in the app is encrypted.\nDecrypt it and try again."); 42 | this.Close(); 43 | } 44 | else if (AppManifestData_encrypted == false) 45 | { 46 | ContiuneImport = "1"; 47 | } 48 | else 49 | { 50 | MessageBox.Show("invalid value for variable 'encrypted' inside manifest.json"); 51 | this.Close(); 52 | } 53 | } 54 | else 55 | { 56 | MessageBox.Show("An Error occurred, Restart the program!"); 57 | } 58 | #endregion 59 | 60 | // Continue 61 | #region Continue 62 | if (ContiuneImport == "1") 63 | { 64 | this.Close(); 65 | 66 | // read EncriptionKey from imput box 67 | string ImportUsingEncriptionKey = txtBox.Text; 68 | 69 | // Open file browser > to select the file 70 | OpenFileDialog openFileDialog1 = new OpenFileDialog(); 71 | 72 | // Set filter options and filter index. 73 | openFileDialog1.Filter = "maFiles (.maFile)|*.maFile|All Files (*.*)|*.*"; 74 | openFileDialog1.FilterIndex = 1; 75 | openFileDialog1.Multiselect = false; 76 | 77 | // Call the ShowDialog method to show the dialog box. 78 | DialogResult userClickedOK = openFileDialog1.ShowDialog(); 79 | 80 | // Process input if the user clicked OK. 81 | if (userClickedOK == DialogResult.OK) 82 | { 83 | // Open the selected file to read. 84 | System.IO.Stream fileStream = openFileDialog1.OpenFile(); 85 | string fileContents = null; 86 | 87 | using (System.IO.StreamReader reader = new System.IO.StreamReader(fileStream)) 88 | { 89 | fileContents = reader.ReadToEnd(); 90 | } 91 | fileStream.Close(); 92 | 93 | try 94 | { 95 | if (ImportUsingEncriptionKey == "") 96 | { 97 | // Import maFile 98 | //------------------------------------------- 99 | #region Import maFile 100 | SteamGuardAccount maFile = JsonConvert.DeserializeObject(fileContents); 101 | 102 | if (maFile.Session == null || maFile.Session.SteamID == 0 || maFile.Session.IsAccessTokenExpired()) 103 | { 104 | // Have the user to relogin to steam to get a new session 105 | LoginForm loginForm = new LoginForm(LoginForm.LoginType.Import, maFile); 106 | loginForm.ShowDialog(); 107 | 108 | if (loginForm.Session == null || loginForm.Session.SteamID == 0) 109 | { 110 | MessageBox.Show("Login failed. Try to import this account again.", "Account Import", MessageBoxButtons.OK, MessageBoxIcon.Error); 111 | return; 112 | } 113 | 114 | // Save new session to the maFile 115 | maFile.Session = loginForm.Session; 116 | } 117 | 118 | // Save account 119 | mManifest.SaveAccount(maFile, false); 120 | MessageBox.Show("Account Imported!", "Account Import", MessageBoxButtons.OK); 121 | #endregion 122 | } 123 | else 124 | { 125 | // Import Encripted maFile 126 | //------------------------------------------- 127 | #region Import Encripted maFile 128 | //Read manifest.json encryption_iv encryption_salt 129 | string ImportFileName_Found = "0"; 130 | string Salt_Found = null; 131 | string IV_Found = null; 132 | string ReadManifestEx = "0"; 133 | 134 | //No directory means no manifest file anyways. 135 | ImportManifest newImportManifest = new ImportManifest(); 136 | newImportManifest.Encrypted = false; 137 | newImportManifest.Entries = new List(); 138 | 139 | // extract folder path 140 | string fullPath = openFileDialog1.FileName; 141 | string fileName = openFileDialog1.SafeFileName; 142 | string path = fullPath.Replace(fileName, ""); 143 | 144 | // extract fileName 145 | string ImportFileName = fullPath.Replace(path, ""); 146 | 147 | string ImportManifestFile = path + "manifest.json"; 148 | 149 | 150 | if (File.Exists(ImportManifestFile)) 151 | { 152 | string ImportManifestContents = File.ReadAllText(ImportManifestFile); 153 | 154 | 155 | try 156 | { 157 | ImportManifest account = JsonConvert.DeserializeObject(ImportManifestContents); 158 | //bool Import_encrypted = account.Encrypted; 159 | 160 | List newEntries = new List(); 161 | 162 | foreach (var entry in account.Entries) 163 | { 164 | string FileName = entry.Filename; 165 | string encryption_iv = entry.IV; 166 | string encryption_salt = entry.Salt; 167 | 168 | if (ImportFileName == FileName) 169 | { 170 | ImportFileName_Found = "1"; 171 | IV_Found = entry.IV; 172 | Salt_Found = entry.Salt; 173 | } 174 | } 175 | } 176 | catch (Exception) 177 | { 178 | ReadManifestEx = "1"; 179 | MessageBox.Show("Invalid content inside manifest.json!\nImport Failed."); 180 | } 181 | 182 | 183 | // DECRIPT & Import 184 | //-------------------- 185 | #region DECRIPT & Import 186 | if (ReadManifestEx == "0") 187 | { 188 | if (ImportFileName_Found == "1" && Salt_Found != null && IV_Found != null) 189 | { 190 | string decryptedText = FileEncryptor.DecryptData(ImportUsingEncriptionKey, Salt_Found, IV_Found, fileContents); 191 | 192 | if (decryptedText == null) 193 | { 194 | MessageBox.Show("Decryption Failed.\nImport Failed."); 195 | } 196 | else 197 | { 198 | string fileText = decryptedText; 199 | 200 | SteamGuardAccount maFile = JsonConvert.DeserializeObject(fileText); 201 | if (maFile.Session == null || maFile.Session.SteamID == 0 || maFile.Session.IsAccessTokenExpired()) 202 | { 203 | // Have the user to relogin to steam to get a new session 204 | LoginForm loginForm = new LoginForm(LoginForm.LoginType.Import, maFile); 205 | loginForm.ShowDialog(); 206 | 207 | if (loginForm.Session == null || loginForm.Session.SteamID == 0) 208 | { 209 | MessageBox.Show("Login failed. Try to import this account again.", "Account Import", MessageBoxButtons.OK, MessageBoxIcon.Error); 210 | return; 211 | } 212 | 213 | // Save new session to the maFile 214 | maFile.Session = loginForm.Session; 215 | } 216 | 217 | // Save account 218 | mManifest.SaveAccount(maFile, false); 219 | MessageBox.Show("Account Imported!\nYour Account in now Decrypted!", "Account Import", MessageBoxButtons.OK); 220 | } 221 | } 222 | else 223 | { 224 | if (ImportFileName_Found == "0") 225 | { 226 | MessageBox.Show("Account not found inside manifest.json.\nImport Failed."); 227 | } 228 | else if (Salt_Found == null && IV_Found == null) 229 | { 230 | MessageBox.Show("manifest.json does not contain encrypted data.\nYour account may be unencrypted!\nImport Failed."); 231 | } 232 | else 233 | { 234 | if (IV_Found == null) 235 | { 236 | MessageBox.Show("manifest.json does not contain: encryption_iv\nImport Failed."); 237 | } 238 | else if (IV_Found == null) 239 | { 240 | MessageBox.Show("manifest.json does not contain: encryption_salt\nImport Failed."); 241 | } 242 | } 243 | } 244 | } 245 | #endregion //DECRIPT & Import END 246 | 247 | 248 | } 249 | else 250 | { 251 | MessageBox.Show("manifest.json is missing!\nImport Failed."); 252 | } 253 | #endregion //Import Encripted maFile END 254 | } 255 | 256 | } 257 | catch (Exception) 258 | { 259 | MessageBox.Show("This file is not a valid SteamAuth maFile.\nImport Failed."); 260 | } 261 | } 262 | } 263 | #endregion // Continue End 264 | } 265 | 266 | private void btnCancel_Click(object sender, EventArgs e) 267 | { 268 | this.Close(); 269 | } 270 | 271 | private void Import_maFile_Form_FormClosing(object sender, FormClosingEventArgs e) 272 | { 273 | } 274 | } 275 | 276 | 277 | public class AppManifest 278 | { 279 | [JsonProperty("encrypted")] 280 | public bool Encrypted { get; set; } 281 | } 282 | 283 | 284 | public class ImportManifest 285 | { 286 | [JsonProperty("encrypted")] 287 | public bool Encrypted { get; set; } 288 | 289 | [JsonProperty("entries")] 290 | public List Entries { get; set; } 291 | } 292 | 293 | public class ImportManifestEntry 294 | { 295 | [JsonProperty("encryption_iv")] 296 | public string IV { get; set; } 297 | 298 | [JsonProperty("encryption_salt")] 299 | public string Salt { get; set; } 300 | 301 | [JsonProperty("filename")] 302 | public string Filename { get; set; } 303 | 304 | [JsonProperty("steamid")] 305 | public ulong SteamID { get; set; } 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /Steam Desktop Authenticator/InputForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace Steam_Desktop_Authenticator 2 | { 3 | partial class InputForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(InputForm)); 32 | this.labelText = new System.Windows.Forms.Label(); 33 | this.txtBox = new System.Windows.Forms.TextBox(); 34 | this.btnAccept = new System.Windows.Forms.Button(); 35 | this.btnCancel = new System.Windows.Forms.Button(); 36 | this.SuspendLayout(); 37 | // 38 | // labelText 39 | // 40 | this.labelText.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 41 | this.labelText.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 42 | this.labelText.ForeColor = System.Drawing.SystemColors.ControlText; 43 | this.labelText.Location = new System.Drawing.Point(12, 9); 44 | this.labelText.Name = "labelText"; 45 | this.labelText.Size = new System.Drawing.Size(383, 159); 46 | this.labelText.TabIndex = 0; 47 | this.labelText.Text = "Sample Text~~~"; 48 | // 49 | // txtBox 50 | // 51 | this.txtBox.Font = new System.Drawing.Font("Segoe UI", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 52 | this.txtBox.Location = new System.Drawing.Point(12, 171); 53 | this.txtBox.Name = "txtBox"; 54 | this.txtBox.Size = new System.Drawing.Size(383, 33); 55 | this.txtBox.TabIndex = 1; 56 | // 57 | // btnAccept 58 | // 59 | this.btnAccept.Location = new System.Drawing.Point(15, 210); 60 | this.btnAccept.Name = "btnAccept"; 61 | this.btnAccept.Size = new System.Drawing.Size(98, 28); 62 | this.btnAccept.TabIndex = 2; 63 | this.btnAccept.Text = "Accept"; 64 | this.btnAccept.UseVisualStyleBackColor = true; 65 | this.btnAccept.Click += new System.EventHandler(this.btnAccept_Click); 66 | // 67 | // btnCancel 68 | // 69 | this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; 70 | this.btnCancel.Location = new System.Drawing.Point(297, 210); 71 | this.btnCancel.Name = "btnCancel"; 72 | this.btnCancel.Size = new System.Drawing.Size(98, 28); 73 | this.btnCancel.TabIndex = 3; 74 | this.btnCancel.Text = "Cancel"; 75 | this.btnCancel.UseVisualStyleBackColor = true; 76 | this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); 77 | // 78 | // InputForm 79 | // 80 | this.AcceptButton = this.btnAccept; 81 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 82 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 83 | this.CancelButton = this.btnCancel; 84 | this.ClientSize = new System.Drawing.Size(407, 250); 85 | this.Controls.Add(this.btnCancel); 86 | this.Controls.Add(this.btnAccept); 87 | this.Controls.Add(this.txtBox); 88 | this.Controls.Add(this.labelText); 89 | this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 90 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 91 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 92 | this.MaximizeBox = false; 93 | this.Name = "InputForm"; 94 | this.ShowInTaskbar = false; 95 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 96 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.InputForm_FormClosing); 97 | this.ResumeLayout(false); 98 | this.PerformLayout(); 99 | 100 | } 101 | 102 | #endregion 103 | 104 | private System.Windows.Forms.Label labelText; 105 | public System.Windows.Forms.TextBox txtBox; 106 | private System.Windows.Forms.Button btnAccept; 107 | private System.Windows.Forms.Button btnCancel; 108 | } 109 | } -------------------------------------------------------------------------------- /Steam Desktop Authenticator/InputForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | 11 | namespace Steam_Desktop_Authenticator 12 | { 13 | public partial class InputForm : Form 14 | { 15 | public bool Canceled = false; 16 | private bool userClosed = true; 17 | 18 | public InputForm(string label, bool password = false) 19 | { 20 | InitializeComponent(); 21 | this.labelText.Text = label; 22 | 23 | if (password) 24 | { 25 | this.txtBox.PasswordChar = '*'; 26 | } 27 | } 28 | 29 | private void btnAccept_Click(object sender, EventArgs e) 30 | { 31 | if (string.IsNullOrEmpty(this.txtBox.Text)) 32 | { 33 | this.Canceled = true; 34 | this.userClosed = false; 35 | this.Close(); 36 | } 37 | else 38 | { 39 | this.Canceled = false; 40 | this.userClosed = false; 41 | this.Close(); 42 | } 43 | } 44 | 45 | private void btnCancel_Click(object sender, EventArgs e) 46 | { 47 | this.Canceled = true; 48 | this.userClosed = false; 49 | this.Close(); 50 | } 51 | 52 | private void InputForm_FormClosing(object sender, FormClosingEventArgs e) 53 | { 54 | if (this.userClosed) 55 | { 56 | // Set Canceled = true when the user hits the X button. 57 | this.Canceled = true; 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Steam Desktop Authenticator/ListInputForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace Steam_Desktop_Authenticator 2 | { 3 | partial class ListInputForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ListInputForm)); 32 | this.lbItems = new System.Windows.Forms.ListBox(); 33 | this.btnAccept = new System.Windows.Forms.Button(); 34 | this.btnCancel = new System.Windows.Forms.Button(); 35 | this.SuspendLayout(); 36 | // 37 | // lbItems 38 | // 39 | this.lbItems.FormattingEnabled = true; 40 | this.lbItems.Location = new System.Drawing.Point(23, 63); 41 | this.lbItems.Name = "lbItems"; 42 | this.lbItems.Size = new System.Drawing.Size(248, 82); 43 | this.lbItems.TabIndex = 0; 44 | // 45 | // btnAccept 46 | // 47 | this.btnAccept.Location = new System.Drawing.Point(277, 63); 48 | this.btnAccept.Name = "btnAccept"; 49 | this.btnAccept.Size = new System.Drawing.Size(75, 23); 50 | this.btnAccept.TabIndex = 1; 51 | this.btnAccept.Text = "Accept"; 52 | this.btnAccept.UseVisualStyleBackColor = true; 53 | this.btnAccept.Click += new System.EventHandler(this.btnAccept_Click); 54 | // 55 | // btnCancel 56 | // 57 | this.btnCancel.Location = new System.Drawing.Point(277, 122); 58 | this.btnCancel.Name = "btnCancel"; 59 | this.btnCancel.Size = new System.Drawing.Size(75, 23); 60 | this.btnCancel.TabIndex = 2; 61 | this.btnCancel.Text = "Cancel"; 62 | this.btnCancel.UseVisualStyleBackColor = true; 63 | this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); 64 | // 65 | // ListInputForm 66 | // 67 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 68 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 69 | this.ClientSize = new System.Drawing.Size(372, 172); 70 | this.Controls.Add(this.btnCancel); 71 | this.Controls.Add(this.btnAccept); 72 | this.Controls.Add(this.lbItems); 73 | this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 74 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 75 | this.Name = "ListInputForm"; 76 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 77 | this.Text = "Select one"; 78 | this.Load += new System.EventHandler(this.ListInputForm_Load); 79 | this.ResumeLayout(false); 80 | 81 | } 82 | 83 | #endregion 84 | 85 | private System.Windows.Forms.ListBox lbItems; 86 | private System.Windows.Forms.Button btnAccept; 87 | private System.Windows.Forms.Button btnCancel; 88 | } 89 | } -------------------------------------------------------------------------------- /Steam Desktop Authenticator/ListInputForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | 11 | namespace Steam_Desktop_Authenticator 12 | { 13 | public partial class ListInputForm : Form 14 | { 15 | public ListInputForm(List options) 16 | { 17 | Items = options; 18 | InitializeComponent(); 19 | } 20 | 21 | public int SelectedIndex; 22 | List Items; 23 | 24 | private void ListInputForm_Load(object sender, EventArgs e) 25 | { 26 | foreach (var item in Items) 27 | { 28 | lbItems.Items.Add(item); 29 | } 30 | } 31 | 32 | private void btnAccept_Click(object sender, EventArgs e) 33 | { 34 | if (lbItems.SelectedIndex != -1) 35 | { 36 | SelectedIndex = lbItems.SelectedIndex; 37 | this.Close(); 38 | } 39 | } 40 | 41 | private void btnCancel_Click(object sender, EventArgs e) 42 | { 43 | this.Close(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Steam Desktop Authenticator/LoginForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace Steam_Desktop_Authenticator 2 | { 3 | partial class LoginForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(LoginForm)); 32 | this.label1 = new System.Windows.Forms.Label(); 33 | this.txtUsername = new System.Windows.Forms.TextBox(); 34 | this.txtPassword = new System.Windows.Forms.TextBox(); 35 | this.label2 = new System.Windows.Forms.Label(); 36 | this.btnSteamLogin = new System.Windows.Forms.Button(); 37 | this.labelLoginExplanation = new System.Windows.Forms.Label(); 38 | this.SuspendLayout(); 39 | // 40 | // label1 41 | // 42 | this.label1.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 43 | this.label1.ForeColor = System.Drawing.SystemColors.ControlText; 44 | this.label1.Location = new System.Drawing.Point(12, 30); 45 | this.label1.Name = "label1"; 46 | this.label1.Size = new System.Drawing.Size(86, 25); 47 | this.label1.TabIndex = 0; 48 | this.label1.Text = "Username:"; 49 | this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 50 | // 51 | // txtUsername 52 | // 53 | this.txtUsername.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 54 | this.txtUsername.Location = new System.Drawing.Point(101, 31); 55 | this.txtUsername.Name = "txtUsername"; 56 | this.txtUsername.Size = new System.Drawing.Size(220, 25); 57 | this.txtUsername.TabIndex = 1; 58 | // 59 | // txtPassword 60 | // 61 | this.txtPassword.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 62 | this.txtPassword.Location = new System.Drawing.Point(101, 63); 63 | this.txtPassword.Name = "txtPassword"; 64 | this.txtPassword.PasswordChar = '*'; 65 | this.txtPassword.Size = new System.Drawing.Size(220, 25); 66 | this.txtPassword.TabIndex = 3; 67 | // 68 | // label2 69 | // 70 | this.label2.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 71 | this.label2.ForeColor = System.Drawing.SystemColors.ControlText; 72 | this.label2.Location = new System.Drawing.Point(12, 62); 73 | this.label2.Name = "label2"; 74 | this.label2.Size = new System.Drawing.Size(83, 25); 75 | this.label2.TabIndex = 2; 76 | this.label2.Text = "Password:"; 77 | this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 78 | // 79 | // btnSteamLogin 80 | // 81 | this.btnSteamLogin.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 82 | this.btnSteamLogin.Location = new System.Drawing.Point(224, 147); 83 | this.btnSteamLogin.Name = "btnSteamLogin"; 84 | this.btnSteamLogin.Size = new System.Drawing.Size(110, 33); 85 | this.btnSteamLogin.TabIndex = 4; 86 | this.btnSteamLogin.Text = "Login"; 87 | this.btnSteamLogin.UseVisualStyleBackColor = true; 88 | this.btnSteamLogin.Click += new System.EventHandler(this.btnSteamLogin_Click); 89 | // 90 | // labelLoginExplanation 91 | // 92 | this.labelLoginExplanation.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 93 | this.labelLoginExplanation.Location = new System.Drawing.Point(15, 98); 94 | this.labelLoginExplanation.Name = "labelLoginExplanation"; 95 | this.labelLoginExplanation.Size = new System.Drawing.Size(306, 46); 96 | this.labelLoginExplanation.TabIndex = 5; 97 | this.labelLoginExplanation.Text = "This will activate Steam Desktop Authenticator on your Steam account. This requir" + 98 | "es a phone number that can receive SMS."; 99 | // 100 | // LoginForm 101 | // 102 | this.AcceptButton = this.btnSteamLogin; 103 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 104 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 105 | this.ClientSize = new System.Drawing.Size(346, 193); 106 | this.Controls.Add(this.labelLoginExplanation); 107 | this.Controls.Add(this.btnSteamLogin); 108 | this.Controls.Add(this.txtPassword); 109 | this.Controls.Add(this.label2); 110 | this.Controls.Add(this.txtUsername); 111 | this.Controls.Add(this.label1); 112 | this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 113 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 114 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 115 | this.ImeMode = System.Windows.Forms.ImeMode.On; 116 | this.MaximizeBox = false; 117 | this.Name = "LoginForm"; 118 | this.ShowIcon = false; 119 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 120 | this.Text = "Steam Login"; 121 | this.Load += new System.EventHandler(this.LoginForm_Load); 122 | this.ResumeLayout(false); 123 | this.PerformLayout(); 124 | 125 | } 126 | 127 | #endregion 128 | 129 | private System.Windows.Forms.Label label1; 130 | private System.Windows.Forms.TextBox txtUsername; 131 | private System.Windows.Forms.TextBox txtPassword; 132 | private System.Windows.Forms.Label label2; 133 | private System.Windows.Forms.Button btnSteamLogin; 134 | private System.Windows.Forms.Label labelLoginExplanation; 135 | } 136 | } -------------------------------------------------------------------------------- /Steam Desktop Authenticator/LoginForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using System.Windows.Forms; 4 | using SteamAuth; 5 | using SteamKit2; 6 | using SteamKit2.Authentication; 7 | using SteamKit2.Internal; 8 | 9 | namespace Steam_Desktop_Authenticator 10 | { 11 | public partial class LoginForm : Form 12 | { 13 | public SteamGuardAccount account; 14 | public LoginType LoginReason; 15 | public SessionData Session; 16 | 17 | public LoginForm(LoginType loginReason = LoginType.Initial, SteamGuardAccount account = null) 18 | { 19 | InitializeComponent(); 20 | this.LoginReason = loginReason; 21 | this.account = account; 22 | 23 | try 24 | { 25 | if (this.LoginReason != LoginType.Initial) 26 | { 27 | txtUsername.Text = account.AccountName; 28 | txtUsername.Enabled = false; 29 | } 30 | 31 | if (this.LoginReason == LoginType.Refresh) 32 | { 33 | labelLoginExplanation.Text = "Your Steam credentials have expired. For trade and market confirmations to work properly, please login again."; 34 | } 35 | else if (this.LoginReason == LoginType.Import) 36 | { 37 | labelLoginExplanation.Text = "Please login to your Steam account import it."; 38 | } 39 | } 40 | catch (Exception) 41 | { 42 | MessageBox.Show("Failed to find your account. Try closing and re-opening SDA.", "Login Failed", MessageBoxButtons.OK, MessageBoxIcon.Error); 43 | this.Close(); 44 | } 45 | } 46 | 47 | public void SetUsername(string username) 48 | { 49 | txtUsername.Text = username; 50 | } 51 | 52 | public string FilterPhoneNumber(string phoneNumber) 53 | { 54 | return phoneNumber.Replace("-", "").Replace("(", "").Replace(")", ""); 55 | } 56 | 57 | public bool PhoneNumberOkay(string phoneNumber) 58 | { 59 | if (phoneNumber == null || phoneNumber.Length == 0) return false; 60 | if (phoneNumber[0] != '+') return false; 61 | return true; 62 | } 63 | 64 | private void ResetLoginButton() 65 | { 66 | btnSteamLogin.Enabled = true; 67 | btnSteamLogin.Text = "Login"; 68 | } 69 | 70 | private async void btnSteamLogin_Click(object sender, EventArgs e) 71 | { 72 | // Disable button while we login 73 | btnSteamLogin.Enabled = false; 74 | btnSteamLogin.Text = "Logging in..."; 75 | 76 | string username = txtUsername.Text; 77 | string password = txtPassword.Text; 78 | 79 | // Start a new SteamClient instance 80 | SteamClient steamClient = new SteamClient(); 81 | 82 | // Connect to Steam 83 | steamClient.Connect(); 84 | 85 | // Really basic way to wait until Steam is connected 86 | while (!steamClient.IsConnected) 87 | await Task.Delay(500); 88 | 89 | // Create a new auth session 90 | CredentialsAuthSession authSession; 91 | try 92 | { 93 | authSession = await steamClient.Authentication.BeginAuthSessionViaCredentialsAsync(new AuthSessionDetails 94 | { 95 | Username = username, 96 | Password = password, 97 | IsPersistentSession = false, 98 | PlatformType = EAuthTokenPlatformType.k_EAuthTokenPlatformType_MobileApp, 99 | ClientOSType = EOSType.Android9, 100 | Authenticator = new UserFormAuthenticator(this.account), 101 | }); 102 | } 103 | catch (Exception ex) 104 | { 105 | MessageBox.Show(ex.Message, "Steam Login Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 106 | this.Close(); 107 | return; 108 | } 109 | 110 | // Starting polling Steam for authentication response 111 | AuthPollResult pollResponse; 112 | try 113 | { 114 | pollResponse = await authSession.PollingWaitForResultAsync(); 115 | } 116 | catch (Exception ex) 117 | { 118 | MessageBox.Show(ex.Message, "Steam Login Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 119 | this.Close(); 120 | return; 121 | } 122 | 123 | // Build a SessionData object 124 | SessionData sessionData = new SessionData() 125 | { 126 | SteamID = authSession.SteamID.ConvertToUInt64(), 127 | AccessToken = pollResponse.AccessToken, 128 | RefreshToken = pollResponse.RefreshToken, 129 | }; 130 | 131 | //Login succeeded 132 | this.Session = sessionData; 133 | 134 | // If we're only logging in for an account import, stop here 135 | if (LoginReason == LoginType.Import) 136 | { 137 | this.Close(); 138 | return; 139 | } 140 | 141 | // If we're only logging in for a session refresh then save it and exit 142 | if (LoginReason == LoginType.Refresh) 143 | { 144 | Manifest man = Manifest.GetManifest(); 145 | account.FullyEnrolled = true; 146 | account.Session = sessionData; 147 | HandleManifest(man, true); 148 | this.Close(); 149 | return; 150 | } 151 | 152 | // Show a dialog to make sure they really want to add their authenticator 153 | var result = MessageBox.Show("Steam account login succeeded. Press OK to continue adding SDA as your authenticator.", "Steam Login", MessageBoxButtons.OKCancel, MessageBoxIcon.Information); 154 | if (result == DialogResult.Cancel) 155 | { 156 | MessageBox.Show("Adding authenticator aborted.", "Steam Login", MessageBoxButtons.OK, MessageBoxIcon.Error); 157 | ResetLoginButton(); 158 | return; 159 | } 160 | 161 | // Begin linking mobile authenticator 162 | AuthenticatorLinker linker = new AuthenticatorLinker(sessionData); 163 | 164 | AuthenticatorLinker.LinkResult linkResponse = AuthenticatorLinker.LinkResult.GeneralFailure; 165 | while (linkResponse != AuthenticatorLinker.LinkResult.AwaitingFinalization) 166 | { 167 | try 168 | { 169 | linkResponse = await linker.AddAuthenticator(); 170 | } 171 | catch (Exception ex) 172 | { 173 | MessageBox.Show("Error adding your authenticator: " + ex.Message, "Steam Login", MessageBoxButtons.OK, MessageBoxIcon.Error); 174 | ResetLoginButton(); 175 | return; 176 | } 177 | 178 | switch (linkResponse) 179 | { 180 | case AuthenticatorLinker.LinkResult.MustProvidePhoneNumber: 181 | 182 | // Show the phone input form 183 | PhoneInputForm phoneInputForm = new PhoneInputForm(account); 184 | phoneInputForm.ShowDialog(); 185 | if (phoneInputForm.Canceled) 186 | { 187 | this.Close(); 188 | return; 189 | } 190 | 191 | linker.PhoneNumber = phoneInputForm.PhoneNumber; 192 | linker.PhoneCountryCode = phoneInputForm.CountryCode; 193 | break; 194 | 195 | case AuthenticatorLinker.LinkResult.AuthenticatorPresent: 196 | MessageBox.Show("This account already has an authenticator linked. You must remove that authenticator to add SDA as your authenticator.", "Steam Login", MessageBoxButtons.OK, MessageBoxIcon.Error); 197 | this.Close(); 198 | return; 199 | 200 | case AuthenticatorLinker.LinkResult.FailureAddingPhone: 201 | MessageBox.Show("Failed to add your phone number. Please try again or use a different phone number.", "Steam Login", MessageBoxButtons.OK, MessageBoxIcon.Error); 202 | linker.PhoneNumber = null; 203 | break; 204 | 205 | case AuthenticatorLinker.LinkResult.MustRemovePhoneNumber: 206 | linker.PhoneNumber = null; 207 | break; 208 | 209 | case AuthenticatorLinker.LinkResult.MustConfirmEmail: 210 | MessageBox.Show("Please check your email, and click the link Steam sent you before continuing.", "Steam Login", MessageBoxButtons.OK, MessageBoxIcon.Information); 211 | break; 212 | 213 | case AuthenticatorLinker.LinkResult.GeneralFailure: 214 | MessageBox.Show("Error adding your authenticator.", "Steam Login Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 215 | this.Close(); 216 | return; 217 | } 218 | } // End while loop checking for AwaitingFinalization 219 | 220 | Manifest manifest = Manifest.GetManifest(); 221 | string passKey = null; 222 | if (manifest.Entries.Count == 0) 223 | { 224 | passKey = manifest.PromptSetupPassKey("Please enter an encryption passkey. Leave blank or hit cancel to not encrypt (VERY INSECURE)."); 225 | } 226 | else if (manifest.Entries.Count > 0 && manifest.Encrypted) 227 | { 228 | bool passKeyValid = false; 229 | while (!passKeyValid) 230 | { 231 | InputForm passKeyForm = new InputForm("Please enter your current encryption passkey."); 232 | passKeyForm.ShowDialog(); 233 | if (!passKeyForm.Canceled) 234 | { 235 | passKey = passKeyForm.txtBox.Text; 236 | passKeyValid = manifest.VerifyPasskey(passKey); 237 | if (!passKeyValid) 238 | { 239 | MessageBox.Show("That passkey is invalid. Please enter the same passkey you used for your other accounts."); 240 | } 241 | } 242 | else 243 | { 244 | this.Close(); 245 | return; 246 | } 247 | } 248 | } 249 | 250 | //Save the file immediately; losing this would be bad. 251 | if (!manifest.SaveAccount(linker.LinkedAccount, passKey != null, passKey)) 252 | { 253 | manifest.RemoveAccount(linker.LinkedAccount); 254 | MessageBox.Show("Unable to save mobile authenticator file. The mobile authenticator has not been linked."); 255 | this.Close(); 256 | return; 257 | } 258 | 259 | MessageBox.Show("The Mobile Authenticator has not yet been linked. Before finalizing the authenticator, please write down your revocation code: " + linker.LinkedAccount.RevocationCode); 260 | 261 | AuthenticatorLinker.FinalizeResult finalizeResponse = AuthenticatorLinker.FinalizeResult.GeneralFailure; 262 | while (finalizeResponse != AuthenticatorLinker.FinalizeResult.Success) 263 | { 264 | InputForm smsCodeForm = new InputForm("Please input the SMS code sent to your phone."); 265 | smsCodeForm.ShowDialog(); 266 | if (smsCodeForm.Canceled) 267 | { 268 | manifest.RemoveAccount(linker.LinkedAccount); 269 | this.Close(); 270 | return; 271 | } 272 | 273 | InputForm confirmRevocationCode = new InputForm("Please enter your revocation code to ensure you've saved it."); 274 | confirmRevocationCode.ShowDialog(); 275 | if (confirmRevocationCode.txtBox.Text.ToUpper() != linker.LinkedAccount.RevocationCode) 276 | { 277 | MessageBox.Show("Revocation code incorrect; the authenticator has not been linked."); 278 | manifest.RemoveAccount(linker.LinkedAccount); 279 | this.Close(); 280 | return; 281 | } 282 | 283 | string smsCode = smsCodeForm.txtBox.Text; 284 | finalizeResponse = await linker.FinalizeAddAuthenticator(smsCode); 285 | 286 | switch (finalizeResponse) 287 | { 288 | case AuthenticatorLinker.FinalizeResult.BadSMSCode: 289 | continue; 290 | 291 | case AuthenticatorLinker.FinalizeResult.UnableToGenerateCorrectCodes: 292 | MessageBox.Show("Unable to generate the proper codes to finalize this authenticator. The authenticator should not have been linked. In the off-chance it was, please write down your revocation code, as this is the last chance to see it: " + linker.LinkedAccount.RevocationCode); 293 | manifest.RemoveAccount(linker.LinkedAccount); 294 | this.Close(); 295 | return; 296 | 297 | case AuthenticatorLinker.FinalizeResult.GeneralFailure: 298 | MessageBox.Show("Unable to finalize this authenticator. The authenticator should not have been linked. In the off-chance it was, please write down your revocation code, as this is the last chance to see it: " + linker.LinkedAccount.RevocationCode); 299 | manifest.RemoveAccount(linker.LinkedAccount); 300 | this.Close(); 301 | return; 302 | } 303 | } 304 | 305 | //Linked, finally. Re-save with FullyEnrolled property. 306 | manifest.SaveAccount(linker.LinkedAccount, passKey != null, passKey); 307 | MessageBox.Show("Mobile authenticator successfully linked. Please write down your revocation code: " + linker.LinkedAccount.RevocationCode); 308 | this.Close(); 309 | } 310 | 311 | private void HandleManifest(Manifest man, bool IsRefreshing = false) 312 | { 313 | string passKey = null; 314 | if (man.Entries.Count == 0) 315 | { 316 | passKey = man.PromptSetupPassKey("Please enter an encryption passkey. Leave blank or hit cancel to not encrypt (VERY INSECURE)."); 317 | } 318 | else if (man.Entries.Count > 0 && man.Encrypted) 319 | { 320 | bool passKeyValid = false; 321 | while (!passKeyValid) 322 | { 323 | InputForm passKeyForm = new InputForm("Please enter your current encryption passkey."); 324 | passKeyForm.ShowDialog(); 325 | if (!passKeyForm.Canceled) 326 | { 327 | passKey = passKeyForm.txtBox.Text; 328 | passKeyValid = man.VerifyPasskey(passKey); 329 | if (!passKeyValid) 330 | { 331 | MessageBox.Show("That passkey is invalid. Please enter the same passkey you used for your other accounts.", "Steam Login", MessageBoxButtons.OK, MessageBoxIcon.Error); 332 | } 333 | } 334 | else 335 | { 336 | this.Close(); 337 | return; 338 | } 339 | } 340 | } 341 | 342 | man.SaveAccount(account, passKey != null, passKey); 343 | if (IsRefreshing) 344 | { 345 | MessageBox.Show("Your session was refreshed.", "Steam Login", MessageBoxButtons.OK, MessageBoxIcon.Information); 346 | } 347 | else 348 | { 349 | MessageBox.Show("Mobile authenticator successfully linked. Please write down your revocation code: " + account.RevocationCode, "Steam Login", MessageBoxButtons.OK); 350 | } 351 | this.Close(); 352 | } 353 | 354 | private void LoginForm_Load(object sender, EventArgs e) 355 | { 356 | if (account != null && account.AccountName != null) 357 | { 358 | txtUsername.Text = account.AccountName; 359 | } 360 | } 361 | 362 | public enum LoginType 363 | { 364 | Initial, 365 | Refresh, 366 | Import 367 | } 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /Steam Desktop Authenticator/MaFileEncryptedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Steam_Desktop_Authenticator 8 | { 9 | class MaFileEncryptedException : Exception 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Steam Desktop Authenticator/MainForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Windows.Forms; 4 | using SteamAuth; 5 | using System.Collections.Generic; 6 | using System.Text.RegularExpressions; 7 | using System.Net; 8 | using Newtonsoft.Json; 9 | using System.Threading; 10 | using System.Drawing; 11 | using System.Linq; 12 | 13 | namespace Steam_Desktop_Authenticator 14 | { 15 | public partial class MainForm : Form 16 | { 17 | private SteamGuardAccount currentAccount = null; 18 | private SteamGuardAccount[] allAccounts; 19 | private List updatedSessions = new List(); 20 | private Manifest manifest; 21 | private static SemaphoreSlim confirmationsSemaphore = new SemaphoreSlim(1, 1); 22 | 23 | private long steamTime = 0; 24 | private long currentSteamChunk = 0; 25 | private string passKey = null; 26 | private bool startSilent = false; 27 | 28 | // Forms 29 | private TradePopupForm popupFrm = new TradePopupForm(); 30 | 31 | public MainForm() 32 | { 33 | InitializeComponent(); 34 | } 35 | 36 | public void SetEncryptionKey(string key) 37 | { 38 | passKey = key; 39 | } 40 | 41 | public void StartSilent(bool silent) 42 | { 43 | startSilent = silent; 44 | } 45 | 46 | // Form event handlers 47 | 48 | private void MainForm_Shown(object sender, EventArgs e) 49 | { 50 | this.labelVersion.Text = String.Format("v{0}", Application.ProductVersion); 51 | try 52 | { 53 | this.manifest = Manifest.GetManifest(); 54 | } 55 | catch (ManifestParseException) 56 | { 57 | MessageBox.Show("Unable to read your settings. Try restating SDA.", "Steam Desktop Authenticator", MessageBoxButtons.OK, MessageBoxIcon.Error); 58 | this.Close(); 59 | } 60 | 61 | // Make sure we don't show that welcome dialog again 62 | this.manifest.FirstRun = false; 63 | this.manifest.Save(); 64 | 65 | // Tick first time manually to sync time 66 | timerSteamGuard_Tick(new object(), EventArgs.Empty); 67 | 68 | if (manifest.Encrypted) 69 | { 70 | if (passKey == null) 71 | { 72 | passKey = manifest.PromptForPassKey(); 73 | if (passKey == null) 74 | { 75 | Application.Exit(); 76 | } 77 | } 78 | 79 | btnManageEncryption.Text = "Manage Encryption"; 80 | } 81 | else 82 | { 83 | btnManageEncryption.Text = "Setup Encryption"; 84 | } 85 | 86 | btnManageEncryption.Enabled = manifest.Entries.Count > 0; 87 | 88 | loadSettings(); 89 | loadAccountsList(); 90 | 91 | checkForUpdates(); 92 | 93 | if (startSilent) 94 | { 95 | this.WindowState = FormWindowState.Minimized; 96 | } 97 | } 98 | 99 | private void MainForm_Load(object sender, EventArgs e) 100 | { 101 | trayIcon.Icon = this.Icon; 102 | } 103 | 104 | private void MainForm_Resize(object sender, EventArgs e) 105 | { 106 | if (this.WindowState == FormWindowState.Minimized) 107 | { 108 | this.Hide(); 109 | } 110 | } 111 | 112 | private void MainForm_FormClosing(object sender, FormClosingEventArgs e) 113 | { 114 | Application.Exit(); 115 | } 116 | 117 | 118 | // UI Button handlers 119 | 120 | private void btnSteamLogin_Click(object sender, EventArgs e) 121 | { 122 | var loginForm = new LoginForm(); 123 | loginForm.ShowDialog(); 124 | this.loadAccountsList(); 125 | } 126 | 127 | private void btnTradeConfirmations_Click(object sender, EventArgs e) 128 | { 129 | if (currentAccount == null) return; 130 | 131 | string oText = btnTradeConfirmations.Text; 132 | btnTradeConfirmations.Text = "Loading..."; 133 | btnTradeConfirmations.Text = oText; 134 | 135 | ConfirmationFormWeb confirms = new ConfirmationFormWeb(currentAccount); 136 | confirms.Show(); 137 | } 138 | 139 | private void btnManageEncryption_Click(object sender, EventArgs e) 140 | { 141 | if (manifest.Encrypted) 142 | { 143 | InputForm currentPassKeyForm = new InputForm("Enter current passkey", true); 144 | currentPassKeyForm.ShowDialog(); 145 | 146 | if (currentPassKeyForm.Canceled) 147 | { 148 | return; 149 | } 150 | 151 | string curPassKey = currentPassKeyForm.txtBox.Text; 152 | 153 | InputForm changePassKeyForm = new InputForm("Enter new passkey, or leave blank to remove encryption."); 154 | changePassKeyForm.ShowDialog(); 155 | 156 | if (changePassKeyForm.Canceled && !string.IsNullOrEmpty(changePassKeyForm.txtBox.Text)) 157 | { 158 | return; 159 | } 160 | 161 | InputForm changePassKeyForm2 = new InputForm("Confirm new passkey, or leave blank to remove encryption."); 162 | changePassKeyForm2.ShowDialog(); 163 | 164 | if (changePassKeyForm2.Canceled && !string.IsNullOrEmpty(changePassKeyForm.txtBox.Text)) 165 | { 166 | return; 167 | } 168 | 169 | string newPassKey = changePassKeyForm.txtBox.Text; 170 | string confirmPassKey = changePassKeyForm2.txtBox.Text; 171 | 172 | if (newPassKey != confirmPassKey) 173 | { 174 | MessageBox.Show("Passkeys do not match."); 175 | return; 176 | } 177 | 178 | if (newPassKey.Length == 0) 179 | { 180 | newPassKey = null; 181 | } 182 | 183 | string action = newPassKey == null ? "remove" : "change"; 184 | if (!manifest.ChangeEncryptionKey(curPassKey, newPassKey)) 185 | { 186 | MessageBox.Show("Unable to " + action + " passkey."); 187 | } 188 | else 189 | { 190 | MessageBox.Show("Passkey successfully " + action + "d."); 191 | this.loadAccountsList(); 192 | } 193 | } 194 | else 195 | { 196 | passKey = manifest.PromptSetupPassKey(); 197 | this.loadAccountsList(); 198 | } 199 | } 200 | 201 | private void labelUpdate_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 202 | { 203 | if (newVersion == null || currentVersion == null) 204 | { 205 | checkForUpdates(); 206 | } 207 | else 208 | { 209 | compareVersions(); 210 | } 211 | } 212 | 213 | private void btnCopy_Click(object sender, EventArgs e) 214 | { 215 | CopyLoginToken(); 216 | } 217 | 218 | 219 | // Tool strip menu handlers 220 | 221 | private void menuQuit_Click(object sender, EventArgs e) 222 | { 223 | Application.Exit(); 224 | } 225 | 226 | private void menuRemoveAccountFromManifest_Click(object sender, EventArgs e) 227 | { 228 | if (manifest.Encrypted) 229 | { 230 | MessageBox.Show("You cannot remove accounts from the manifest file while it is encrypted.", "Remove from manifest", MessageBoxButtons.OK, MessageBoxIcon.Error); 231 | } 232 | else 233 | { 234 | DialogResult res = MessageBox.Show("This will remove the selected account from the manifest file.\nUse this to move a maFile to another computer.\nThis will NOT delete your maFile.", "Remove from manifest", MessageBoxButtons.OKCancel); 235 | if (res == DialogResult.OK) 236 | { 237 | manifest.RemoveAccount(currentAccount, false); 238 | MessageBox.Show("Account removed from manifest.\nYou can now move its maFile to another computer and import it using the File menu.", "Remove from manifest"); 239 | loadAccountsList(); 240 | } 241 | } 242 | } 243 | 244 | private void menuLoginAgain_Click(object sender, EventArgs e) 245 | { 246 | this.PromptRefreshLogin(currentAccount); 247 | } 248 | 249 | private void menuImportAccount_Click(object sender, EventArgs e) 250 | { 251 | ImportAccountForm currentImport_maFile_Form = new ImportAccountForm(); 252 | currentImport_maFile_Form.ShowDialog(); 253 | loadAccountsList(); 254 | } 255 | 256 | private void menuSettings_Click(object sender, EventArgs e) 257 | { 258 | new SettingsForm().ShowDialog(); 259 | manifest = Manifest.GetManifest(true); 260 | loadSettings(); 261 | } 262 | 263 | private async void menuDeactivateAuthenticator_Click(object sender, EventArgs e) 264 | { 265 | if (currentAccount == null) return; 266 | 267 | // Check for a valid refresh token first 268 | if (currentAccount.Session.IsRefreshTokenExpired()) 269 | { 270 | MessageBox.Show("Your session has expired. Use the login again button under the selected account menu.", "Deactivate Authenticator", MessageBoxButtons.OK, MessageBoxIcon.Error); 271 | return; 272 | } 273 | 274 | // Check for a valid access token, refresh it if needed 275 | if (currentAccount.Session.IsAccessTokenExpired()) 276 | { 277 | try 278 | { 279 | await currentAccount.Session.RefreshAccessToken(); 280 | } 281 | catch (Exception ex) 282 | { 283 | MessageBox.Show(ex.Message, "Deactivate Authenticator Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 284 | return; 285 | } 286 | } 287 | 288 | DialogResult res = MessageBox.Show("Would you like to remove Steam Guard completely?\nYes - Remove Steam Guard completely.\nNo - Switch back to Email authentication.", "Deactivate Authenticator: " + currentAccount.AccountName, MessageBoxButtons.YesNoCancel); 289 | int scheme = 0; 290 | if (res == DialogResult.Yes) 291 | { 292 | scheme = 2; 293 | } 294 | else if (res == DialogResult.No) 295 | { 296 | scheme = 1; 297 | } 298 | else if (res == DialogResult.Cancel) 299 | { 300 | scheme = 0; 301 | } 302 | 303 | if (scheme != 0) 304 | { 305 | string confCode = currentAccount.GenerateSteamGuardCode(); 306 | InputForm confirmationDialog = new InputForm(String.Format("Removing Steam Guard from {0}. Enter this confirmation code: {1}", currentAccount.AccountName, confCode)); 307 | confirmationDialog.ShowDialog(); 308 | 309 | if (confirmationDialog.Canceled) 310 | { 311 | return; 312 | } 313 | 314 | string enteredCode = confirmationDialog.txtBox.Text.ToUpper(); 315 | if (enteredCode != confCode) 316 | { 317 | MessageBox.Show("Confirmation codes do not match. Steam Guard not removed."); 318 | return; 319 | } 320 | 321 | bool success = await currentAccount.DeactivateAuthenticator(scheme); 322 | if (success) 323 | { 324 | MessageBox.Show(String.Format("Steam Guard {0}. maFile will be deleted after hitting okay. If you need to make a backup, now's the time.", (scheme == 2 ? "removed completely" : "switched to emails"))); 325 | this.manifest.RemoveAccount(currentAccount); 326 | this.loadAccountsList(); 327 | } 328 | else 329 | { 330 | MessageBox.Show("Steam Guard failed to deactivate."); 331 | } 332 | } 333 | else 334 | { 335 | MessageBox.Show("Steam Guard was not removed. No action was taken."); 336 | } 337 | } 338 | 339 | // Tray menu handlers 340 | private void trayIcon_MouseDoubleClick(object sender, MouseEventArgs e) 341 | { 342 | trayRestore_Click(sender, EventArgs.Empty); 343 | } 344 | 345 | private void trayRestore_Click(object sender, EventArgs e) 346 | { 347 | this.Show(); 348 | this.WindowState = FormWindowState.Normal; 349 | } 350 | 351 | private void trayQuit_Click(object sender, EventArgs e) 352 | { 353 | Application.Exit(); 354 | } 355 | 356 | private void trayTradeConfirmations_Click(object sender, EventArgs e) 357 | { 358 | btnTradeConfirmations_Click(sender, e); 359 | } 360 | 361 | private void trayCopySteamGuard_Click(object sender, EventArgs e) 362 | { 363 | if (txtLoginToken.Text != "") 364 | { 365 | Clipboard.SetText(txtLoginToken.Text); 366 | } 367 | } 368 | 369 | private void trayAccountList_SelectedIndexChanged(object sender, EventArgs e) 370 | { 371 | listAccounts.SelectedIndex = trayAccountList.SelectedIndex; 372 | } 373 | 374 | 375 | // Misc UI handlers 376 | private void listAccounts_SelectedValueChanged(object sender, EventArgs e) 377 | { 378 | for (int i = 0; i < allAccounts.Length; i++) 379 | { 380 | // Check if index is out of bounds first 381 | if (i < 0 || listAccounts.SelectedIndex < 0) 382 | continue; 383 | 384 | SteamGuardAccount account = allAccounts[i]; 385 | if (account.AccountName == (string)listAccounts.Items[listAccounts.SelectedIndex]) 386 | { 387 | trayAccountList.Text = account.AccountName; 388 | currentAccount = account; 389 | loadAccountInfo(); 390 | break; 391 | } 392 | } 393 | } 394 | 395 | private void txtAccSearch_TextChanged(object sender, EventArgs e) 396 | { 397 | List names = new List(getAllNames()); 398 | names = names.FindAll(new Predicate(IsFilter)); 399 | 400 | listAccounts.Items.Clear(); 401 | listAccounts.Items.AddRange(names.ToArray()); 402 | 403 | trayAccountList.Items.Clear(); 404 | trayAccountList.Items.AddRange(names.ToArray()); 405 | } 406 | 407 | 408 | // Timers 409 | 410 | private async void timerSteamGuard_Tick(object sender, EventArgs e) 411 | { 412 | lblStatus.Text = "Aligning time with Steam..."; 413 | steamTime = await TimeAligner.GetSteamTimeAsync(); 414 | lblStatus.Text = ""; 415 | 416 | currentSteamChunk = steamTime / 30L; 417 | int secondsUntilChange = (int)(steamTime - (currentSteamChunk * 30L)); 418 | 419 | loadAccountInfo(); 420 | if (currentAccount != null) 421 | { 422 | pbTimeout.Value = 30 - secondsUntilChange; 423 | } 424 | } 425 | 426 | private async void timerTradesPopup_Tick(object sender, EventArgs e) 427 | { 428 | if (currentAccount == null || popupFrm.Visible) return; 429 | if (!confirmationsSemaphore.Wait(0)) 430 | { 431 | return; //Only one thread may access this critical section at once. Mutex is a bad choice here because it'll cause a pileup of threads. 432 | } 433 | 434 | List confs = new List(); 435 | Dictionary> autoAcceptConfirmations = new Dictionary>(); 436 | 437 | SteamGuardAccount[] accs = 438 | manifest.CheckAllAccounts ? allAccounts : new SteamGuardAccount[] { currentAccount }; 439 | 440 | try 441 | { 442 | lblStatus.Text = "Checking confirmations..."; 443 | 444 | foreach (var acc in accs) 445 | { 446 | // Check for a valid refresh token first 447 | if (acc.Session.IsRefreshTokenExpired()) 448 | { 449 | MessageBox.Show("Your session for account " + acc.AccountName + " has expired. You will be prompted to login again.", "Trade Confirmations", MessageBoxButtons.OK, MessageBoxIcon.Error); 450 | PromptRefreshLogin(acc); 451 | break; 452 | } 453 | 454 | // Check for a valid access token, refresh it if needed 455 | if (acc.Session.IsAccessTokenExpired()) 456 | { 457 | try 458 | { 459 | lblStatus.Text = "Refreshing session..."; 460 | await acc.Session.RefreshAccessToken(); 461 | lblStatus.Text = "Checking confirmations..."; 462 | } 463 | catch (Exception ex) 464 | { 465 | MessageBox.Show(ex.Message, "Steam Login Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 466 | break; 467 | } 468 | } 469 | 470 | try 471 | { 472 | Confirmation[] tmp = await acc.FetchConfirmationsAsync(); 473 | foreach (var conf in tmp) 474 | { 475 | if ((conf.ConfType == Confirmation.EMobileConfirmationType.MarketListing && manifest.AutoConfirmMarketTransactions) || 476 | (conf.ConfType == Confirmation.EMobileConfirmationType.Trade && manifest.AutoConfirmTrades)) 477 | { 478 | if (!autoAcceptConfirmations.ContainsKey(acc)) 479 | autoAcceptConfirmations[acc] = new List(); 480 | autoAcceptConfirmations[acc].Add(conf); 481 | } 482 | else 483 | confs.Add(conf); 484 | } 485 | } 486 | catch (Exception) 487 | { 488 | 489 | } 490 | } 491 | 492 | lblStatus.Text = ""; 493 | 494 | if (confs.Count > 0) 495 | { 496 | popupFrm.Confirmations = confs.ToArray(); 497 | popupFrm.Popup(); 498 | } 499 | if (autoAcceptConfirmations.Count > 0) 500 | { 501 | foreach (var acc in autoAcceptConfirmations.Keys) 502 | { 503 | var confirmations = autoAcceptConfirmations[acc].ToArray(); 504 | await acc.AcceptMultipleConfirmations(confirmations); 505 | } 506 | } 507 | } 508 | catch (SteamGuardAccount.WGTokenInvalidException) 509 | { 510 | lblStatus.Text = ""; 511 | } 512 | 513 | confirmationsSemaphore.Release(); 514 | } 515 | 516 | // Other methods 517 | 518 | private void CopyLoginToken() 519 | { 520 | string text = txtLoginToken.Text; 521 | if (String.IsNullOrEmpty(text)) 522 | return; 523 | Clipboard.SetText(text); 524 | } 525 | 526 | /// 527 | /// Display a login form to the user to refresh their OAuth Token 528 | /// 529 | /// The account to refresh 530 | private void PromptRefreshLogin(SteamGuardAccount account) 531 | { 532 | var loginForm = new LoginForm(LoginForm.LoginType.Refresh, account); 533 | loginForm.ShowDialog(); 534 | } 535 | 536 | /// 537 | /// Load UI with the current account info, this is run every second 538 | /// 539 | private void loadAccountInfo() 540 | { 541 | if (currentAccount != null && steamTime != 0) 542 | { 543 | popupFrm.Account = currentAccount; 544 | txtLoginToken.Text = currentAccount.GenerateSteamGuardCodeForTime(steamTime); 545 | groupAccount.Text = "Account: " + currentAccount.AccountName; 546 | } 547 | } 548 | 549 | /// 550 | /// Decrypts files and populates list UI with accounts 551 | /// 552 | private void loadAccountsList() 553 | { 554 | currentAccount = null; 555 | 556 | listAccounts.Items.Clear(); 557 | listAccounts.SelectedIndex = -1; 558 | 559 | trayAccountList.Items.Clear(); 560 | trayAccountList.SelectedIndex = -1; 561 | 562 | allAccounts = manifest.GetAllAccounts(passKey); 563 | 564 | if (allAccounts.Length > 0) 565 | { 566 | for (int i = 0; i < allAccounts.Length; i++) 567 | { 568 | SteamGuardAccount account = allAccounts[i]; 569 | listAccounts.Items.Add(account.AccountName); 570 | trayAccountList.Items.Add(account.AccountName); 571 | } 572 | 573 | listAccounts.SelectedIndex = 0; 574 | trayAccountList.SelectedIndex = 0; 575 | 576 | listAccounts.Sorted = true; 577 | trayAccountList.Sorted = true; 578 | } 579 | menuDeactivateAuthenticator.Enabled = btnTradeConfirmations.Enabled = allAccounts.Length > 0; 580 | } 581 | 582 | private void listAccounts_KeyDown(object sender, KeyEventArgs e) 583 | { 584 | if (e.Control) 585 | { 586 | if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down) 587 | { 588 | int to = listAccounts.SelectedIndex - (e.KeyCode == Keys.Up ? 1 : -1); 589 | manifest.MoveEntry(listAccounts.SelectedIndex, to); 590 | loadAccountsList(); 591 | } 592 | return; 593 | } 594 | 595 | if (!IsKeyAChar(e.KeyCode) && !IsKeyADigit(e.KeyCode)) 596 | { 597 | return; 598 | } 599 | 600 | txtAccSearch.Focus(); 601 | txtAccSearch.Text = e.KeyCode.ToString(); 602 | txtAccSearch.SelectionStart = 1; 603 | } 604 | 605 | private static bool IsKeyAChar(Keys key) 606 | { 607 | return key >= Keys.A && key <= Keys.Z; 608 | } 609 | 610 | private static bool IsKeyADigit(Keys key) 611 | { 612 | return (key >= Keys.D0 && key <= Keys.D9) || (key >= Keys.NumPad0 && key <= Keys.NumPad9); 613 | } 614 | 615 | private bool IsFilter(string f) 616 | { 617 | if (txtAccSearch.Text.StartsWith("~")) 618 | { 619 | try 620 | { 621 | return Regex.IsMatch(f, txtAccSearch.Text); 622 | } 623 | catch (Exception) 624 | { 625 | return true; 626 | } 627 | 628 | } 629 | else 630 | { 631 | return f.Contains(txtAccSearch.Text); 632 | } 633 | } 634 | 635 | private string[] getAllNames() 636 | { 637 | string[] itemArray = new string[allAccounts.Length]; 638 | for (int i = 0; i < itemArray.Length; i++) 639 | { 640 | itemArray[i] = allAccounts[i].AccountName; 641 | } 642 | return itemArray; 643 | } 644 | 645 | private void loadSettings() 646 | { 647 | timerTradesPopup.Enabled = manifest.PeriodicChecking; 648 | timerTradesPopup.Interval = manifest.PeriodicCheckingInterval * 1000; 649 | } 650 | 651 | // Logic for version checking 652 | private Version newVersion = null; 653 | private Version currentVersion = null; 654 | private WebClient updateClient = null; 655 | private string updateUrl = null; 656 | private bool startupUpdateCheck = true; 657 | 658 | private void checkForUpdates() 659 | { 660 | if (updateClient != null) return; 661 | updateClient = new WebClient(); 662 | updateClient.DownloadStringCompleted += UpdateClient_DownloadStringCompleted; 663 | updateClient.Headers.Add("Content-Type", "application/json"); 664 | updateClient.Headers.Add("User-Agent", "Steam Desktop Authenticator"); 665 | updateClient.DownloadStringAsync(new Uri("https://api.github.com/repos/Jessecar96/SteamDesktopAuthenticator/releases/latest")); 666 | } 667 | 668 | private void compareVersions() 669 | { 670 | if (newVersion > currentVersion) 671 | { 672 | labelUpdate.Text = "Download new version"; // Show the user a new version is available if they press no 673 | DialogResult updateDialog = MessageBox.Show(String.Format("A new version is available! Would you like to download it now?\nYou will update from version {0} to {1}", Application.ProductVersion, newVersion.ToString()), "New Version", MessageBoxButtons.YesNo); 674 | if (updateDialog == DialogResult.Yes) 675 | { 676 | Process.Start(updateUrl); 677 | } 678 | } 679 | else 680 | { 681 | if (!startupUpdateCheck) 682 | { 683 | MessageBox.Show(String.Format("You are using the latest version: {0}", Application.ProductVersion)); 684 | } 685 | } 686 | 687 | newVersion = null; // Check the api again next time they check for updates 688 | updateClient = null; // Set to null to indicate it's done checking 689 | startupUpdateCheck = false; // Set when it's done checking on startup 690 | } 691 | 692 | private void UpdateClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) 693 | { 694 | try 695 | { 696 | dynamic resultObject = JsonConvert.DeserializeObject(e.Result); 697 | newVersion = new Version(resultObject.tag_name.Value); 698 | currentVersion = new Version(Application.ProductVersion); 699 | updateUrl = resultObject.assets.First.browser_download_url.Value; 700 | compareVersions(); 701 | } 702 | catch (Exception) 703 | { 704 | MessageBox.Show("Failed to check for updates."); 705 | } 706 | } 707 | 708 | private void MainForm_KeyDown(object sender, KeyEventArgs e) 709 | { 710 | if (e.KeyCode == Keys.C && e.Modifiers == Keys.Control) 711 | { 712 | CopyLoginToken(); 713 | } 714 | } 715 | 716 | private void panelButtons_SizeChanged(object sender, EventArgs e) 717 | { 718 | int totButtons = panelButtons.Controls.OfType