├── .gitattributes ├── .gitignore ├── README.md ├── SteamAccountSwitcher2.sln ├── SteamAccountSwitcher2 ├── AccountLoader.cs ├── AccountType.cs ├── AccountWindow.xaml ├── AccountWindow.xaml.cs ├── App.config ├── App.xaml ├── App.xaml.cs ├── CachedAccountManager.cs ├── Encryption │ └── EncryptionHelper.cs ├── EncryptionType.cs ├── ImageHelper.cs ├── LogWindow.xaml ├── LogWindow.xaml.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── PasswordWindow.xaml ├── PasswordWindow.xaml.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ ├── Settings.settings │ └── app.manifest ├── SasManager.cs ├── Settings │ ├── SettingsWindow.xaml │ ├── SettingsWindow.xaml.cs │ └── UserSettings.cs ├── Steam.cs ├── SteamAccount.cs ├── SteamAccountSwitcher.csproj ├── SteamStatus.cs ├── UserInteraction.cs ├── edit.png ├── images │ ├── acc-bg-banned.jpg │ ├── acc-bg-friend.jpg │ ├── acc-bg-main.jpg │ ├── acc-bg-smurf.jpg │ ├── cross-button.png │ ├── gear.png │ ├── icons │ │ ├── download.png │ │ ├── edit.png │ │ ├── export.png │ │ ├── external.png │ │ ├── full_trash.png │ │ ├── import.png │ │ ├── internal.png │ │ ├── key.png │ │ ├── lock.png │ │ ├── minus.png │ │ ├── multiple_inputs.png │ │ ├── plus.png │ │ ├── search.png │ │ ├── settings.png │ │ ├── small │ │ │ ├── download.png │ │ │ ├── edit.png │ │ │ ├── export.png │ │ │ ├── external.png │ │ │ ├── full_trash.png │ │ │ ├── import.png │ │ │ ├── internal.png │ │ │ ├── key.png │ │ │ ├── lock.png │ │ │ ├── minus.png │ │ │ ├── multiple_inputs.png │ │ │ ├── plus.png │ │ │ ├── search.png │ │ │ ├── settings.png │ │ │ ├── synchronize.png │ │ │ ├── unlock.png │ │ │ └── upload.png │ │ ├── synchronize.png │ │ ├── unlock.png │ │ └── upload.png │ ├── lock.png │ ├── minus-button.png │ ├── pencil-button.png │ ├── pencil.png │ ├── plus-button.png │ ├── plus.png │ └── plus_old.png └── packages.config └── SteamAccountSwitcherSetup ├── SteamAccountSwitcherSetup-SetupFiles └── SteamAccountSwitcherSetup.msi ├── SteamAccountSwitcherSetup-cache ├── cacheIndex.txt └── part2 │ ├── disk1.cab │ └── output-info.ini ├── SteamAccountSwitcherSetup.aip └── SteamAccountSwitcherSetup.aiproj /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | # DNX 42 | project.lock.json 43 | artifacts/ 44 | 45 | *_i.c 46 | *_p.c 47 | *_i.h 48 | *.ilk 49 | *.meta 50 | *.obj 51 | *.pch 52 | *.pdb 53 | *.pgc 54 | *.pgd 55 | *.rsp 56 | *.sbr 57 | *.tlb 58 | *.tli 59 | *.tlh 60 | *.tmp 61 | *.tmp_proj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | 70 | # Chutzpah Test files 71 | _Chutzpah* 72 | 73 | # Visual C++ cache files 74 | ipch/ 75 | *.aps 76 | *.ncb 77 | *.opensdf 78 | *.sdf 79 | *.cachefile 80 | 81 | # Visual Studio profiler 82 | *.psess 83 | *.vsp 84 | *.vspx 85 | 86 | # TFS 2012 Local Workspace 87 | $tf/ 88 | 89 | # Guidance Automation Toolkit 90 | *.gpState 91 | 92 | # ReSharper is a .NET coding add-in 93 | _ReSharper*/ 94 | *.[Rr]e[Ss]harper 95 | *.DotSettings.user 96 | 97 | # JustCode is a .NET coding add-in 98 | .JustCode 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | _NCrunch_* 108 | .*crunch*.local.xml 109 | 110 | # MightyMoose 111 | *.mm.* 112 | AutoTest.Net/ 113 | 114 | # Web workbench (sass) 115 | .sass-cache/ 116 | 117 | # Installshield output folder 118 | [Ee]xpress/ 119 | 120 | # DocProject is a documentation generator add-in 121 | DocProject/buildhelp/ 122 | DocProject/Help/*.HxT 123 | DocProject/Help/*.HxC 124 | DocProject/Help/*.hhc 125 | DocProject/Help/*.hhk 126 | DocProject/Help/*.hhp 127 | DocProject/Help/Html2 128 | DocProject/Help/html 129 | 130 | # Click-Once directory 131 | publish/ 132 | 133 | # Publish Web Output 134 | *.[Pp]ublish.xml 135 | *.azurePubxml 136 | ## TODO: Comment the next line if you want to checkin your 137 | ## web deploy settings but do note that will include unencrypted 138 | ## passwords 139 | #*.pubxml 140 | 141 | *.publishproj 142 | 143 | # NuGet Packages 144 | *.nupkg 145 | # The packages folder can be ignored because of Package Restore 146 | **/packages/* 147 | # except build/, which is used as an MSBuild target. 148 | !**/packages/build/ 149 | # Uncomment if necessary however generally it will be regenerated when needed 150 | #!**/packages/repositories.config 151 | 152 | # Windows Azure Build Output 153 | csx/ 154 | *.build.csdef 155 | 156 | # Windows Store app package directory 157 | AppPackages/ 158 | 159 | # Visual Studio cache files 160 | # files ending in .cache can be ignored 161 | *.[Cc]ache 162 | # but keep track of directories ending in .cache 163 | !*.[Cc]ache/ 164 | 165 | # Others 166 | ClientBin/ 167 | [Ss]tyle[Cc]op.* 168 | ~$* 169 | *~ 170 | *.dbmdl 171 | *.dbproj.schemaview 172 | *.pfx 173 | *.publishsettings 174 | node_modules/ 175 | orleans.codegen.cs 176 | 177 | # RIA/Silverlight projects 178 | Generated_Code/ 179 | 180 | # Backup & report files from converting an old project file 181 | # to a newer Visual Studio version. Backup files are not needed, 182 | # because we have git ;-) 183 | _UpgradeReport_Files/ 184 | Backup*/ 185 | UpgradeLog*.XML 186 | UpgradeLog*.htm 187 | 188 | # SQL Server files 189 | *.mdf 190 | *.ldf 191 | 192 | # Business Intelligence projects 193 | *.rdl.data 194 | *.bim.layout 195 | *.bim_*.settings 196 | 197 | # Microsoft Fakes 198 | FakesAssemblies/ 199 | 200 | # Node.js Tools for Visual Studio 201 | .ntvs_analysis.dat 202 | 203 | # Visual Studio 6 build log 204 | *.plg 205 | 206 | # Visual Studio 6 workspace options file 207 | *.opt 208 | 209 | # LightSwitch generated files 210 | GeneratedArtifacts/ 211 | _Pvt_Extensions/ 212 | ModelManifest.xml 213 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SteamAccountSwitcher2 2 | Completely rewritten account switcher for Steam. 3 | 4 | This is work in progress! 5 | There are major bugs and missing features in this current version! 6 | 7 | ### Download 8 | *There is no download yet, there are a lot of things to do before you should install this as your new Account Switcher!* 9 | If you clone this and use it, expect things to break. 10 | 11 | ### Current Features 12 | - new UI 13 | - launching Steam Account 14 | - SafeMode to protect password (could be seen in task manager) 15 | - Saves and encrypts accounts locally (now AES-256 bit encryption) 16 | - Reorder accounts by drag and drop 17 | 18 | ### More info 19 | If you are interested, you can check out my blog, will share some stuff there, because i don't want to spam the old github repo with info about a unfinished second version ;) 20 | [Blog](https://wedenig.org/blog/steamaccountswitcher-v2) 21 | -------------------------------------------------------------------------------- /SteamAccountSwitcher2.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29613.14 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamAccountSwitcher", "SteamAccountSwitcher2\SteamAccountSwitcher.csproj", "{CD84E7B0-56E4-43F0-8532-55972070F563}" 7 | EndProject 8 | Project("{840C416C-B8F3-42BC-B0DD-F6BB14C9F8CB}") = "SteamAccountSwitcherSetup", "SteamAccountSwitcherSetup\SteamAccountSwitcherSetup.aiproj", "{DF67B771-6F39-474C-9833-5FC8AE064EEA}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | All|Any CPU = All|Any CPU 13 | CD_ROM|Any CPU = CD_ROM|Any CPU 14 | Debug|Any CPU = Debug|Any CPU 15 | Deploy|Any CPU = Deploy|Any CPU 16 | DVD-5|Any CPU = DVD-5|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | SingleImage|Any CPU = SingleImage|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {CD84E7B0-56E4-43F0-8532-55972070F563}.All|Any CPU.ActiveCfg = Release|Any CPU 22 | {CD84E7B0-56E4-43F0-8532-55972070F563}.All|Any CPU.Build.0 = Release|Any CPU 23 | {CD84E7B0-56E4-43F0-8532-55972070F563}.CD_ROM|Any CPU.ActiveCfg = Deploy|Any CPU 24 | {CD84E7B0-56E4-43F0-8532-55972070F563}.CD_ROM|Any CPU.Build.0 = Deploy|Any CPU 25 | {CD84E7B0-56E4-43F0-8532-55972070F563}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {CD84E7B0-56E4-43F0-8532-55972070F563}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {CD84E7B0-56E4-43F0-8532-55972070F563}.Deploy|Any CPU.ActiveCfg = Deploy|Any CPU 28 | {CD84E7B0-56E4-43F0-8532-55972070F563}.Deploy|Any CPU.Build.0 = Deploy|Any CPU 29 | {CD84E7B0-56E4-43F0-8532-55972070F563}.Deploy|Any CPU.Deploy.0 = Deploy|Any CPU 30 | {CD84E7B0-56E4-43F0-8532-55972070F563}.DVD-5|Any CPU.ActiveCfg = Deploy|Any CPU 31 | {CD84E7B0-56E4-43F0-8532-55972070F563}.DVD-5|Any CPU.Build.0 = Deploy|Any CPU 32 | {CD84E7B0-56E4-43F0-8532-55972070F563}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {CD84E7B0-56E4-43F0-8532-55972070F563}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {CD84E7B0-56E4-43F0-8532-55972070F563}.SingleImage|Any CPU.ActiveCfg = Deploy|Any CPU 35 | {CD84E7B0-56E4-43F0-8532-55972070F563}.SingleImage|Any CPU.Build.0 = Deploy|Any CPU 36 | {DF67B771-6F39-474C-9833-5FC8AE064EEA}.All|Any CPU.ActiveCfg = DefaultBuild 37 | {DF67B771-6F39-474C-9833-5FC8AE064EEA}.All|Any CPU.Build.0 = DefaultBuild 38 | {DF67B771-6F39-474C-9833-5FC8AE064EEA}.CD_ROM|Any CPU.ActiveCfg = DefaultBuild 39 | {DF67B771-6F39-474C-9833-5FC8AE064EEA}.CD_ROM|Any CPU.Build.0 = DefaultBuild 40 | {DF67B771-6F39-474C-9833-5FC8AE064EEA}.Debug|Any CPU.ActiveCfg = DefaultBuild 41 | {DF67B771-6F39-474C-9833-5FC8AE064EEA}.Debug|Any CPU.Build.0 = DefaultBuild 42 | {DF67B771-6F39-474C-9833-5FC8AE064EEA}.Deploy|Any CPU.ActiveCfg = DefaultBuild 43 | {DF67B771-6F39-474C-9833-5FC8AE064EEA}.Deploy|Any CPU.Build.0 = DefaultBuild 44 | {DF67B771-6F39-474C-9833-5FC8AE064EEA}.DVD-5|Any CPU.ActiveCfg = DefaultBuild 45 | {DF67B771-6F39-474C-9833-5FC8AE064EEA}.DVD-5|Any CPU.Build.0 = DefaultBuild 46 | {DF67B771-6F39-474C-9833-5FC8AE064EEA}.Release|Any CPU.ActiveCfg = DefaultBuild 47 | {DF67B771-6F39-474C-9833-5FC8AE064EEA}.Release|Any CPU.Build.0 = DefaultBuild 48 | {DF67B771-6F39-474C-9833-5FC8AE064EEA}.SingleImage|Any CPU.ActiveCfg = DefaultBuild 49 | {DF67B771-6F39-474C-9833-5FC8AE064EEA}.SingleImage|Any CPU.Build.0 = DefaultBuild 50 | EndGlobalSection 51 | GlobalSection(SolutionProperties) = preSolution 52 | HideSolutionNode = FALSE 53 | EndGlobalSection 54 | GlobalSection(ExtensibilityGlobals) = postSolution 55 | SolutionGuid = {3CC09722-36B7-4CBB-B9F9-73DBF8664D57} 56 | EndGlobalSection 57 | EndGlobal 58 | -------------------------------------------------------------------------------- /SteamAccountSwitcher2/AccountLoader.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Security.Cryptography; 6 | using System.Windows; 7 | using SteamAccountSwitcher2.Encryption; 8 | 9 | namespace SteamAccountSwitcher2 10 | { 11 | class AccountLoader 12 | { 13 | EncryptionType _encryptionType; 14 | string _directory; 15 | private string _password; 16 | 17 | const string BasicKey = "OQPTu9Rf4u4vkywWy+GCBptmXeqC0e456SR3N31vutU="; 18 | 19 | public AccountLoader(EncryptionType e) 20 | { 21 | _encryptionType = e; 22 | this._directory = AppDomain.CurrentDomain.BaseDirectory; 23 | } 24 | 25 | public AccountLoader(EncryptionType e, string directory) 26 | { 27 | _encryptionType = e; 28 | this._directory = directory; 29 | } 30 | 31 | public EncryptionType EncryptionType 32 | { 33 | get => _encryptionType; 34 | set => _encryptionType = value; 35 | } 36 | 37 | /// 38 | /// Password that is used only when Encryption Type is set to Password! 39 | /// 40 | public string Password 41 | { 42 | get => _password; 43 | set => _password = value; 44 | } 45 | 46 | public string AccountsFilePath => Path.Combine(_directory, "accounts.ini"); 47 | 48 | public List LoadAccounts() 49 | { 50 | bool retry = true; 51 | while (retry) 52 | { 53 | string encryptionKey; 54 | switch (_encryptionType) 55 | { 56 | case EncryptionType.Basic: 57 | encryptionKey = BasicKey; 58 | break; 59 | case EncryptionType.Password: 60 | if (!string.IsNullOrEmpty(_password)) 61 | { 62 | encryptionKey = _password; 63 | } 64 | else 65 | { 66 | _password = AskForPassword(); 67 | encryptionKey = _password; 68 | } 69 | 70 | break; 71 | default: 72 | throw new ArgumentException("Unsupported EncryptionType type!"); 73 | } 74 | 75 | try 76 | { 77 | string encrypted = File.ReadAllText(this._directory + "accounts.ini"); 78 | string decrypted = EncryptionHelper.Decrypt(encrypted, encryptionKey); 79 | List accountList = JsonConvert.DeserializeObject>(decrypted); 80 | return accountList; 81 | } 82 | catch (CryptographicException e) 83 | { 84 | MessageBox.Show("Try entering the password again.", "Could not decrypt"); 85 | _password = null; 86 | } 87 | catch (JsonException e) 88 | { 89 | MessageBox.Show(e.Message, "Fatal Error when reading accounts file!"); 90 | retry = false; 91 | } 92 | catch (Exception e) 93 | { 94 | retry = false; 95 | } 96 | } 97 | 98 | return null; 99 | } 100 | 101 | private string AskForPassword() 102 | { 103 | PasswordWindow passwordWindow = new PasswordWindow(false); 104 | passwordWindow.ShowDialog(); 105 | if (passwordWindow.Password == null) 106 | { 107 | System.Environment.Exit(1); 108 | } 109 | 110 | return passwordWindow.Password; 111 | } 112 | 113 | public void SaveAccounts(List list) 114 | { 115 | string encryptionKey; 116 | switch (_encryptionType) 117 | { 118 | case EncryptionType.Basic: 119 | encryptionKey = BasicKey; 120 | break; 121 | case EncryptionType.Password: 122 | encryptionKey = _password; 123 | break; 124 | default: 125 | throw new ArgumentException("Unsupported EncryptionType type!"); 126 | } 127 | 128 | 129 | string output = JsonConvert.SerializeObject(list, Formatting.None); 130 | string encrypted = EncryptionHelper.Encrypt(output, encryptionKey); 131 | 132 | File.WriteAllText(AccountsFilePath, encrypted); 133 | } 134 | 135 | public bool AccountFileExists() 136 | { 137 | return File.Exists(AccountsFilePath); 138 | } 139 | 140 | private static byte[] GetBytes(string str) 141 | { 142 | byte[] bytes = new byte[str.Length * sizeof(char)]; 143 | System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length); 144 | return bytes; 145 | } 146 | 147 | private static string GetString(byte[] bytes) 148 | { 149 | char[] chars = new char[bytes.Length / sizeof(char)]; 150 | System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length); 151 | return new string(chars); 152 | } 153 | } 154 | } -------------------------------------------------------------------------------- /SteamAccountSwitcher2/AccountType.cs: -------------------------------------------------------------------------------- 1 | namespace SteamAccountSwitcher2 2 | { 3 | public enum AccountType 4 | { 5 | Main, Smurf, Friend, Banned 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /SteamAccountSwitcher2/AccountWindow.xaml: -------------------------------------------------------------------------------- 1 |  8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /SteamAccountSwitcher2/AccountWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | 4 | namespace SteamAccountSwitcher2 5 | { 6 | /// 7 | /// Interaction logic for AccountWindow.xaml 8 | /// 9 | public partial class AccountWindow : Window 10 | { 11 | private SteamAccount newAcc; 12 | 13 | /// 14 | /// Creates a new instance of the AccountWindow class. Allows the user to create new accounts. 15 | /// 16 | public AccountWindow() 17 | { 18 | InitializeComponent(); 19 | comboBoxType.ItemsSource = Enum.GetValues(typeof(AccountType)); 20 | comboBoxType.SelectedIndex = 0; 21 | labelIsCached.Visibility = Visibility.Collapsed; 22 | } 23 | 24 | /// 25 | /// Creates a new instance of the AccountWindow class. Allows the user to edit new accounts 26 | /// 27 | /// The account to edit. 28 | public AccountWindow(SteamAccount accToEdit) 29 | { 30 | if (accToEdit == null) 31 | throw new ArgumentNullException(); 32 | 33 | InitializeComponent(); 34 | this.Title = "Edit Account"; 35 | newAcc = accToEdit; 36 | 37 | comboBoxType.ItemsSource = Enum.GetValues(typeof(AccountType)); 38 | comboBoxType.SelectedItem = accToEdit.Type; 39 | 40 | textBoxName.Text = accToEdit.Name; 41 | textBoxUsername.Text = accToEdit.AccountName; 42 | textBoxPassword.Password = accToEdit.Password; 43 | 44 | textBoxUsername.IsEnabled = accToEdit.CachedAccount; 45 | labelIsCached.Visibility = accToEdit.CachedAccount ? Visibility.Visible : Visibility.Collapsed; 46 | } 47 | 48 | /// 49 | /// Accessor to the Account associated with the window. 50 | /// 51 | public SteamAccount Account => newAcc; 52 | 53 | private void buttonSave_Click(object sender, RoutedEventArgs e) 54 | { 55 | if (ValidateInput()) 56 | { 57 | if (newAcc == null) 58 | { 59 | newAcc = new SteamAccount(textBoxUsername.Text, textBoxPassword.Password); 60 | newAcc.Type = (AccountType)comboBoxType.SelectedValue; 61 | newAcc.Name = textBoxName.Text; 62 | } 63 | else 64 | { 65 | newAcc.AccountName = textBoxUsername.Text; 66 | newAcc.Password = textBoxPassword.Password; 67 | newAcc.Name = textBoxName.Text; 68 | newAcc.Type = (AccountType)comboBoxType.SelectedValue; 69 | } 70 | 71 | 72 | Close(); 73 | } 74 | } 75 | 76 | private bool ValidateInput() 77 | { 78 | bool success = true; 79 | string errorstring = ""; 80 | if (String.IsNullOrEmpty(textBoxName.Text)) 81 | { 82 | success = false; 83 | errorstring += "Profile name cannot be empty!\n"; 84 | } 85 | 86 | if (String.IsNullOrEmpty(textBoxUsername.Text)) 87 | { 88 | success = false; 89 | errorstring += "Username cannot be empty!\n"; 90 | } 91 | 92 | if (String.IsNullOrEmpty(textBoxPassword.Password) && labelIsCached.Visibility != Visibility.Visible) 93 | { 94 | success = false; 95 | errorstring += "Password cannot be empty!\n"; 96 | } 97 | 98 | if (success) 99 | { 100 | return true; 101 | } 102 | else 103 | { 104 | MessageBox.Show(errorstring, "Validation problem", MessageBoxButton.OK, MessageBoxImage.Information); 105 | return false; 106 | } 107 | } 108 | 109 | private void buttonCancel_Click(object sender, RoutedEventArgs e) 110 | { 111 | newAcc = null; 112 | Close(); 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /SteamAccountSwitcher2/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 23 | 24 | 0 25 | 26 | 27 | 400 28 | 29 | 30 | 300 31 | 32 | 33 | False 34 | 35 | 36 | Basic 37 | 38 | 39 | False 40 | 41 | 42 | False 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /SteamAccountSwitcher2/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /SteamAccountSwitcher2/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace SteamAccountSwitcher2 4 | { 5 | /// 6 | /// Interaction logic for App.xaml 7 | /// 8 | public partial class App : Application 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SteamAccountSwitcher2/CachedAccountManager.cs: -------------------------------------------------------------------------------- 1 | using Gameloop.Vdf; 2 | using Microsoft.Win32; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Windows; 7 | 8 | namespace SteamAccountSwitcher2 9 | { 10 | /// 11 | /// Manages everything related to cached steam accounts 12 | /// 13 | class CachedAccountManager 14 | { 15 | private Steam _steamInstallation; 16 | List _cachedAccounts = new List(); 17 | private string loginUsersVDFPath; 18 | 19 | public CachedAccountManager(Steam installation) 20 | { 21 | _steamInstallation = installation; 22 | loginUsersVDFPath = Path.Combine(installation.InstallDir, "config/loginusers.vdf"); 23 | } 24 | 25 | public IEnumerable CachedAccounts => _cachedAccounts; 26 | 27 | public void scanForAccounts() 28 | { 29 | dynamic loginUsersVdf = VdfConvert.Deserialize(File.ReadAllText(loginUsersVDFPath)); 30 | // 'volvo' is a VProperty, analogous to Json.NET's JProperty 31 | _cachedAccounts.Clear(); 32 | foreach (var account in loginUsersVdf.Value) 33 | { 34 | _cachedAccounts.Add(new SteamAccount( 35 | (string)account.Key.ToString(), 36 | (string)account.Value.AccountName.Value.ToString(), 37 | (string)account.Value.PersonaName.Value.ToString(), 38 | account.Value.RememberPassword.Value.ToString() == "1", 39 | account.Value.MostRecent?.Value.ToString() == "1", 40 | long.Parse(account.Value.Timestamp.Value.ToString()) 41 | )); 42 | } 43 | } 44 | 45 | /// 46 | /// Uses the technique from TcNo Account Switcher to change the loginUsers.vdf and 47 | /// some registry values so on next start steam is starting with the defined user 48 | /// only works if the user already logged in once with this account and chose "remember password"! 49 | /// 50 | /// the account to start with 51 | public void startCachedAccount(SteamAccount selectedAccount) 52 | { 53 | _steamInstallation.KillSteam(); 54 | dynamic loginUsersVdf = VdfConvert.Deserialize(File.ReadAllText(loginUsersVDFPath)); 55 | try 56 | { 57 | foreach (var account in loginUsersVdf.Value) 58 | { 59 | if (account.Key.ToString() == selectedAccount.SteamId) 60 | { 61 | account.Value.MostRecent.Value = "1"; 62 | if (account.Value.RememberPassword.Value == "0") 63 | { 64 | // Steam does not remember this accounts password! 65 | if (!string.IsNullOrEmpty(selectedAccount.Password)) 66 | { 67 | // If the user has a password, we use that to log in the old way 68 | resetActiveAccount(); 69 | _steamInstallation.StartSteamAccount(selectedAccount); 70 | } 71 | else 72 | { 73 | // Else we notify the user and let him log in 74 | MessageBox.Show( 75 | "This account does not have a password associated with it and can only be started if it has already logged in once and 'Remember Password' has been checked. Log in and select 'Remember Password' now or add a password in SteamAccountSwitcher.", 76 | "Cannot start with selected account", MessageBoxButton.OK, MessageBoxImage.Exclamation); 77 | break; 78 | } 79 | 80 | } 81 | } 82 | else 83 | { 84 | account.Value.MostRecent.Value = "0"; 85 | } 86 | } 87 | 88 | File.WriteAllText(loginUsersVDFPath, loginUsersVdf.ToString()); 89 | 90 | using (RegistryKey key = Registry.CurrentUser.CreateSubKey(@"Software\Valve\Steam")) 91 | { 92 | key.SetValue("AutoLoginUser", selectedAccount.AccountName); 93 | key.SetValue("RememberPassword", 1); 94 | } 95 | _steamInstallation.Start(); 96 | } 97 | catch (Exception e) 98 | { 99 | MessageBox.Show(e.Message); 100 | } 101 | } 102 | 103 | public void resetActiveAccount() 104 | { 105 | using (RegistryKey key = Registry.CurrentUser.CreateSubKey(@"Software\Valve\Steam")) 106 | { 107 | key.DeleteValue("AutoLoginUser"); 108 | key.SetValue("RememberPassword", 1); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /SteamAccountSwitcher2/Encryption/EncryptionHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Security.Cryptography; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SteamAccountSwitcher2.Encryption 10 | { 11 | class EncryptionHelper 12 | { 13 | // This constant is used to determine the keysize of the encryption algorithm in bits. 14 | // We divide this by 8 within the code below to get the equivalent number of bytes. 15 | private const int Keysize = 256; 16 | 17 | // This constant determines the number of iterations for the password bytes generation function. 18 | private const int DerivationIterations = 1000; 19 | 20 | public static string Encrypt(string plainText, string passPhrase) 21 | { 22 | // Salt and IV is randomly generated each time, but is preprended to encrypted cipher text 23 | // so that the same Salt and IV values can be used when decrypting. 24 | var saltStringBytes = Generate256BitsOfRandomEntropy(); 25 | var ivStringBytes = Generate256BitsOfRandomEntropy(); 26 | var plainTextBytes = Encoding.UTF8.GetBytes(plainText); 27 | using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) 28 | { 29 | var keyBytes = password.GetBytes(Keysize / 8); 30 | using (var symmetricKey = new RijndaelManaged()) 31 | { 32 | symmetricKey.BlockSize = 256; 33 | symmetricKey.Mode = CipherMode.CBC; 34 | symmetricKey.Padding = PaddingMode.PKCS7; 35 | using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes)) 36 | { 37 | using (var memoryStream = new MemoryStream()) 38 | { 39 | using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) 40 | { 41 | cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length); 42 | cryptoStream.FlushFinalBlock(); 43 | // Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes. 44 | var cipherTextBytes = saltStringBytes; 45 | cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray(); 46 | cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray(); 47 | memoryStream.Close(); 48 | cryptoStream.Close(); 49 | return Convert.ToBase64String(cipherTextBytes); 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | 57 | public static string Decrypt(string cipherText, string passPhrase) 58 | { 59 | // Get the complete stream of bytes that represent: 60 | // [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText] 61 | var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText); 62 | // Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes. 63 | var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray(); 64 | // Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes. 65 | var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray(); 66 | // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string. 67 | var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray(); 68 | 69 | using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) 70 | { 71 | var keyBytes = password.GetBytes(Keysize / 8); 72 | using (var symmetricKey = new RijndaelManaged()) 73 | { 74 | symmetricKey.BlockSize = 256; 75 | symmetricKey.Mode = CipherMode.CBC; 76 | symmetricKey.Padding = PaddingMode.PKCS7; 77 | using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes)) 78 | { 79 | using (var memoryStream = new MemoryStream(cipherTextBytes)) 80 | { 81 | using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) 82 | { 83 | var plainTextBytes = new byte[cipherTextBytes.Length]; 84 | var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length); 85 | memoryStream.Close(); 86 | cryptoStream.Close(); 87 | return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount); 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | 95 | private static byte[] Generate256BitsOfRandomEntropy() 96 | { 97 | var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits. 98 | using (var rngCsp = new RNGCryptoServiceProvider()) 99 | { 100 | // Fill the array with cryptographically secure random bytes. 101 | rngCsp.GetBytes(randomBytes); 102 | } 103 | return randomBytes; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /SteamAccountSwitcher2/EncryptionType.cs: -------------------------------------------------------------------------------- 1 | namespace SteamAccountSwitcher2 2 | { 3 | public enum EncryptionType 4 | { 5 | Basic, Password 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /SteamAccountSwitcher2/ImageHelper.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.Windows.Media; 7 | using System.Windows.Media.Imaging; 8 | 9 | namespace SteamAccountSwitcher2 10 | { 11 | public class ImageHelper 12 | { 13 | public static ImageSource GetIconImageSource(string name) 14 | { 15 | return new BitmapImage(new Uri(@"images/icons/" + name + ".png", UriKind.Relative)); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /SteamAccountSwitcher2/LogWindow.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /SteamAccountSwitcher2/LogWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace SteamAccountSwitcher2 4 | { 5 | /// 6 | /// Interaction logic for LogWindow.xaml 7 | /// 8 | public partial class LogWindow : Window 9 | { 10 | public LogWindow() 11 | { 12 | InitializeComponent(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /SteamAccountSwitcher2/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 12 | 13 | 14 | 20 | 26 | 32 | 38 | 39 | 47 | 48 | 54 | 55 | 56 | 57 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 108 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /SteamAccountSwitcher2/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using AutoUpdaterDotNET; 2 | using System; 3 | using System.Diagnostics; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | using System.Windows.Input; 7 | 8 | namespace SteamAccountSwitcher2 9 | { 10 | /// 11 | /// Interaction logic for MainWindow.xaml 12 | /// 13 | /// 14 | public partial class MainWindow : Window 15 | { 16 | //ObservableCollection accountList = new ObservableCollection(); 17 | 18 | public MainWindow() 19 | { 20 | AutoUpdater.Start("https://wedenig.org/SteamAccountSwitcher/version.xml"); 21 | 22 | InitializeComponent(); 23 | 24 | //Restore size 25 | this.Top = Properties.Settings.Default.Top; 26 | this.Left = Properties.Settings.Default.Left; 27 | this.Height = Properties.Settings.Default.Height; 28 | this.Width = Properties.Settings.Default.Width; 29 | 30 | if (Properties.Settings.Default.Maximized) 31 | { 32 | WindowState = WindowState.Maximized; 33 | } 34 | 35 | fixOutOfBoundsWindow(); 36 | 37 | askUserForSteamLocation(); 38 | 39 | showSteamStatus(); 40 | 41 | 42 | try 43 | { 44 | SasManager.Instance.InitializeAccountsFromFile(); 45 | } 46 | catch 47 | { 48 | MessageBox.Show( 49 | "Account file is currupted or wrong encryption method is set. Check Settings and try again. Save on close has been disabled so that nothing can be overwritten! Make sure to restart the applications after switching EncryptionType method!", 50 | "Error parsing file", MessageBoxButton.OK, MessageBoxImage.Exclamation); 51 | } 52 | 53 | 54 | SteamAccount sa = new SteamAccount("username", "testpw"); 55 | sa.Name = "profile name"; 56 | //accountList.Add(sa); 57 | 58 | listBoxAccounts.ItemsSource = SasManager.Instance.AccountList; 59 | listBoxAccounts.Items.Refresh(); 60 | 61 | Style itemContainerStyle = new Style(typeof(ListBoxItem)); 62 | //take full width 63 | itemContainerStyle.Setters.Add(new Setter(HorizontalContentAlignmentProperty, HorizontalAlignment.Stretch)); 64 | listBoxAccounts.ItemContainerStyle = itemContainerStyle; 65 | } 66 | 67 | private void askUserForSteamLocation() 68 | { 69 | //No steam directory in Settings, let's find 'em! 70 | if (Properties.Settings.Default.steamInstallDir == String.Empty) 71 | { 72 | //Run this on first start 73 | string installDir = UserInteraction.selectSteamDirectory(@"C:\Program Files (x86)\Steam"); 74 | if (installDir == null) 75 | { 76 | MessageBox.Show( 77 | "You cannot use SteamAccountSwitcher without selecting your Steam.exe. Program will close now.", 78 | "Steam missing", MessageBoxButton.OK, MessageBoxImage.Error); 79 | Close(); 80 | } 81 | else 82 | { 83 | SasManager.Instance.SetSteamInstallDir(installDir); 84 | } 85 | } 86 | } 87 | 88 | private void showSteamStatus() 89 | { 90 | statusBarLabel.Content = SasManager.Instance.SteamStatus.steamStatusMessage(); 91 | statusbar.Background = SasManager.Instance.SteamStatus.getStatusColor(); 92 | } 93 | 94 | private void settingsButton_Click(object sender, RoutedEventArgs e) 95 | { 96 | SettingsWindow settingsWindow = new SettingsWindow(); 97 | settingsWindow.Owner = this; 98 | settingsWindow.ShowDialog(); 99 | } 100 | 101 | private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) 102 | { 103 | SasManager.Instance.SaveAccounts(); 104 | 105 | if (WindowState == WindowState.Maximized) 106 | { 107 | // Use the RestoreBounds as the current values will be 0, 0 and the size of the screen 108 | Properties.Settings.Default.Top = RestoreBounds.Top; 109 | Properties.Settings.Default.Left = RestoreBounds.Left; 110 | Properties.Settings.Default.Height = RestoreBounds.Height; 111 | Properties.Settings.Default.Width = RestoreBounds.Width; 112 | Properties.Settings.Default.Maximized = true; 113 | } 114 | else 115 | { 116 | Properties.Settings.Default.Top = this.Top; 117 | Properties.Settings.Default.Left = this.Left; 118 | Properties.Settings.Default.Height = this.Height; 119 | Properties.Settings.Default.Width = this.Width; 120 | Properties.Settings.Default.Maximized = false; 121 | } 122 | 123 | Properties.Settings.Default.Save(); 124 | } 125 | 126 | private void buttonAdd_Click(object sender, RoutedEventArgs e) 127 | { 128 | AccountWindow newAccWindow = new AccountWindow(); 129 | newAccWindow.Owner = this; 130 | newAccWindow.ShowDialog(); 131 | if (newAccWindow.Account != null) 132 | { 133 | SasManager.Instance.AccountList.Add(newAccWindow.Account); 134 | } 135 | } 136 | 137 | private void listBoxAccounts_SelectionChanged(object sender, SelectionChangedEventArgs e) 138 | { 139 | if (sender != null) 140 | { 141 | buttonEdit.IsEnabled = true; 142 | } 143 | else 144 | { 145 | buttonEdit.IsEnabled = false; 146 | } 147 | 148 | listBoxAccounts.Items.Refresh(); 149 | } 150 | 151 | private void listContextMenuRemove_Click(object sender, RoutedEventArgs e) 152 | { 153 | AskForDeletionOfAccount((SteamAccount) listBoxAccounts.SelectedItem); 154 | } 155 | 156 | private void listContextMenuEdit_Click(object sender, RoutedEventArgs e) 157 | { 158 | if (listBoxAccounts.SelectedItem != null) 159 | { 160 | AccountWindow newAccWindow = new AccountWindow((SteamAccount) listBoxAccounts.SelectedItem); 161 | newAccWindow.Owner = this; 162 | newAccWindow.ShowDialog(); 163 | listBoxAccounts.Items.Refresh(); 164 | } 165 | } 166 | 167 | private void fixOutOfBoundsWindow() 168 | { 169 | bool outOfBounds = 170 | (this.Left <= SystemParameters.VirtualScreenLeft - this.Width) || 171 | (this.Top <= SystemParameters.VirtualScreenTop - this.Height) || 172 | (SystemParameters.VirtualScreenLeft + 173 | SystemParameters.VirtualScreenWidth <= this.Left) || 174 | (SystemParameters.VirtualScreenTop + 175 | SystemParameters.VirtualScreenHeight <= this.Top); 176 | 177 | if (outOfBounds) 178 | { 179 | Debug.WriteLine("Out of bounds window was reset to default offsets"); 180 | this.Left = 0; 181 | this.Top = 0; 182 | this.Width = 450; 183 | this.Height = 400; 184 | } 185 | } 186 | 187 | private void buttonEdit_Click(object sender, RoutedEventArgs e) 188 | { 189 | listContextMenuEdit_Click(sender, e); 190 | } 191 | 192 | private void buttonScanAccounts_Click(object sender, RoutedEventArgs e) 193 | { 194 | SasManager.Instance.ScanAndAddAccounts(); 195 | listBoxAccounts.Items.Refresh(); 196 | } 197 | 198 | private void steamAccount_MouseDown(object sender, MouseButtonEventArgs e) 199 | { 200 | if (e.ClickCount >= 2) 201 | { 202 | SteamAccount selectedAcc = (SteamAccount) listBoxAccounts.SelectedItem; 203 | SasManager.Instance.StartSteamWithAccount(selectedAcc); 204 | } 205 | } 206 | 207 | /// 208 | /// Handles key downs on listBox with steam accounts 209 | /// 210 | private void OnKeyDownHandler(object sender, KeyEventArgs e) 211 | { 212 | if (e.Key == Key.Delete) 213 | { 214 | AskForDeletionOfAccount((SteamAccount) listBoxAccounts.SelectedItem); 215 | } 216 | } 217 | 218 | private void AskForDeletionOfAccount(SteamAccount selectedAccount) 219 | { 220 | var result = MessageBox.Show( 221 | "Are you sure you want to delete the account profile of " + selectedAccount.ToString() + "?", 222 | "Deletion prompt", 223 | MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No); 224 | if (result == MessageBoxResult.Yes) 225 | { 226 | SasManager.Instance.AccountList.Remove(selectedAccount); 227 | buttonEdit.IsEnabled = false; // Cannot edit deleted account 228 | listBoxAccounts.Items.Refresh(); 229 | } 230 | } 231 | } 232 | } -------------------------------------------------------------------------------- /SteamAccountSwitcher2/PasswordWindow.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 22 | 23 |