2016-08-21 20:17:04 +02:00
using Newtonsoft.Json ;
using SteamAuth ;
2016-08-21 17:29:27 +02:00
using System ;
2016-08-21 20:17:04 +02:00
using System.Collections.Generic ;
2016-08-21 19:03:43 +02:00
using System.IO ;
2016-08-21 20:17:04 +02:00
using System.Linq ;
using System.Text ;
using System.Threading.Tasks ;
2016-08-21 17:29:27 +02:00
2016-08-21 17:38:03 +02:00
public static class Program
2016-08-21 17:29:27 +02:00
{
2016-08-21 19:03:43 +02:00
const string defaultSteamGuardPath = "~/maFiles" ;
public static string SteamGuardPath { get ; set ; } = defaultSteamGuardPath ;
2016-08-21 23:02:17 +02:00
public static Manifest Manifest { get ; set ; }
public static SteamGuardAccount [ ] SteamGuardAccounts { get ; set ; }
2016-08-22 01:24:23 +02:00
public static bool Verbose { get ; set ; } = false ;
2016-08-21 19:03:43 +02:00
2016-08-21 17:38:03 +02:00
/// <summary>
/// The main entry point for the application
/// </summary>
[STAThread]
public static void Main ( string [ ] args )
2016-08-21 17:34:48 +02:00
{
2016-08-22 01:24:23 +02:00
string user = "" ;
// Parse cli arguments
2016-08-21 17:38:03 +02:00
if ( args . Contains ( "--help" ) | | args . Contains ( "-h" ) )
{
Console . WriteLine ( "steamguard-cli - v0.0" ) ;
Console . WriteLine ( ) ;
Console . WriteLine ( "--help, -h Display this help message." ) ;
2016-08-21 19:03:43 +02:00
return ;
}
2016-08-22 01:24:23 +02:00
Verbose = args . Contains ( "-v" ) | | args . Contains ( "--verbose" ) ;
if ( args . Contains ( "--user" ) | | args . Contains ( "-u" ) )
{
int u = Array . IndexOf ( args , "--user" ) ;
if ( u = = - 1 )
{
u = Array . IndexOf ( args , "-u" ) ;
}
try
{
user = args [ u + 1 ] ;
}
catch ( IndexOutOfRangeException )
{
Console . WriteLine ( "error: Account name must be supplied after --user or -u." ) ;
return ;
}
if ( Verbose ) Console . WriteLine ( "Generating Steam Gaurd code for account \"{0}\"" , user ) ;
}
2016-08-21 19:03:43 +02:00
2016-08-22 01:24:23 +02:00
// Do some configure
2016-08-21 19:03:43 +02:00
SteamGuardPath = SteamGuardPath . Replace ( "~" , Environment . GetEnvironmentVariable ( "HOME" ) ) ;
if ( ! Directory . Exists ( SteamGuardPath ) )
{
if ( SteamGuardPath = = defaultSteamGuardPath . Replace ( "~" , Environment . GetEnvironmentVariable ( "HOME" ) ) )
{
2016-08-22 01:24:23 +02:00
if ( Verbose ) Console . WriteLine ( "warn: {0} does not exist, creating..." , SteamGuardPath ) ;
2016-08-21 19:03:43 +02:00
Directory . CreateDirectory ( SteamGuardPath ) ;
}
else
{
Console . WriteLine ( "error: {0} does not exist." , SteamGuardPath ) ;
return ;
}
2016-08-21 17:38:03 +02:00
}
2016-08-22 01:24:23 +02:00
if ( Verbose ) Console . WriteLine ( "maFiles path: {0}" , SteamGuardPath ) ;
// Generate the code
if ( Verbose ) Console . WriteLine ( "Aligning time..." ) ;
TimeAligner . AlignTime ( ) ;
if ( Verbose ) Console . WriteLine ( "Opening manifest..." ) ;
Manifest = Manifest . GetManifest ( true ) ;
if ( Verbose ) Console . WriteLine ( "Reading accounts from manifest..." ) ;
2016-08-21 23:02:17 +02:00
SteamGuardAccounts = Manifest . GetAllAccounts ( ) ;
2016-08-22 01:24:23 +02:00
if ( SteamGuardAccounts . Length = = 0 )
{
Console . WriteLine ( "error: No accounts read." ) ;
return ;
}
if ( Verbose ) Console . WriteLine ( "Selecting account..." ) ;
string code = "" ;
for ( int i = 0 ; i < SteamGuardAccounts . Length ; i + + )
{
SteamGuardAccount account = SteamGuardAccounts [ i ] ;
if ( user ! = "" )
{
if ( account . AccountName . ToLower ( ) = = user . ToLower ( ) )
{
if ( Verbose ) Console . WriteLine ( "Generating Code..." ) ;
code = account . GenerateSteamGuardCode ( ) ;
break ;
}
}
else
{
if ( Verbose ) Console . WriteLine ( "Generating Code for {0}..." , account . AccountName ) ;
code = account . GenerateSteamGuardCode ( ) ;
break ;
}
}
if ( code ! = "" )
Console . WriteLine ( code ) ;
else
Console . WriteLine ( "error: No Steam accounts found in {0}" , SteamGuardAccounts ) ;
2016-08-21 17:34:48 +02:00
}
2016-08-21 17:29:27 +02:00
}
2016-08-21 20:17:04 +02:00
public class Manifest
{
[JsonProperty("encrypted")]
public bool Encrypted { get ; set ; }
[JsonProperty("first_run")]
public bool FirstRun { get ; set ; } = true ;
[JsonProperty("entries")]
public List < ManifestEntry > Entries { get ; set ; }
[JsonProperty("periodic_checking")]
public bool PeriodicChecking { get ; set ; } = false ;
[JsonProperty("periodic_checking_interval")]
public int PeriodicCheckingInterval { get ; set ; } = 5 ;
[JsonProperty("periodic_checking_checkall")]
public bool CheckAllAccounts { get ; set ; } = false ;
[JsonProperty("auto_confirm_market_transactions")]
public bool AutoConfirmMarketTransactions { get ; set ; } = false ;
[JsonProperty("auto_confirm_trades")]
public bool AutoConfirmTrades { get ; set ; } = false ;
private static Manifest _manifest { get ; set ; }
public static string GetExecutableDir ( )
{
return Path . GetDirectoryName ( System . Reflection . Assembly . GetEntryAssembly ( ) . Location ) ;
}
public static Manifest GetManifest ( bool forceLoad = false )
{
// Check if already staticly loaded
if ( _manifest ! = null & & ! forceLoad )
{
return _manifest ;
}
// Find config dir and manifest file
2016-08-22 01:24:23 +02:00
string maFile = Program . SteamGuardPath + "/manifest.json" ;
2016-08-21 20:17:04 +02:00
// If there's no config dir, create it
2016-08-22 01:24:23 +02:00
if ( ! Directory . Exists ( Program . SteamGuardPath ) )
2016-08-21 20:17:04 +02:00
{
_manifest = _generateNewManifest ( ) ;
return _manifest ;
}
// If there's no manifest, create it
if ( ! File . Exists ( maFile ) )
{
2016-08-22 01:24:23 +02:00
if ( Program . Verbose ) Console . WriteLine ( "warn: No manifest file found at {0}" , maFile ) ;
2016-08-21 20:17:04 +02:00
_manifest = _generateNewManifest ( true ) ;
return _manifest ;
}
try
{
string manifestContents = File . ReadAllText ( maFile ) ;
_manifest = JsonConvert . DeserializeObject < Manifest > ( manifestContents ) ;
if ( _manifest . Encrypted & & _manifest . Entries . Count = = 0 )
{
_manifest . Encrypted = false ;
_manifest . Save ( ) ;
}
2016-08-22 01:24:23 +02:00
if ( _manifest . Encrypted )
{
throw new NotSupportedException ( "Encrypted maFiles are not supported at this time." ) ;
}
2016-08-21 20:17:04 +02:00
_manifest . RecomputeExistingEntries ( ) ;
return _manifest ;
}
2016-08-22 01:24:23 +02:00
catch ( Exception ex )
2016-08-21 20:17:04 +02:00
{
2016-08-22 01:24:23 +02:00
Console . WriteLine ( "error: Could not open manifest file: {0}" , ex . ToString ( ) ) ;
2016-08-21 20:17:04 +02:00
return null ;
}
}
private static Manifest _generateNewManifest ( bool scanDir = false )
{
2016-08-22 01:24:23 +02:00
if ( Program . Verbose ) Console . WriteLine ( "Generating new manifest..." ) ;
2016-08-21 20:17:04 +02:00
// No directory means no manifest file anyways.
Manifest newManifest = new Manifest ( ) ;
newManifest . Encrypted = false ;
newManifest . PeriodicCheckingInterval = 5 ;
newManifest . PeriodicChecking = false ;
newManifest . AutoConfirmMarketTransactions = false ;
newManifest . AutoConfirmTrades = false ;
newManifest . Entries = new List < ManifestEntry > ( ) ;
newManifest . FirstRun = true ;
// Take a pre-manifest version and generate a manifest for it.
if ( scanDir )
{
2016-08-22 01:24:23 +02:00
if ( Directory . Exists ( Program . SteamGuardPath ) )
2016-08-21 20:17:04 +02:00
{
2016-08-22 01:24:23 +02:00
DirectoryInfo dir = new DirectoryInfo ( Program . SteamGuardPath ) ;
2016-08-21 20:17:04 +02:00
var files = dir . GetFiles ( ) ;
foreach ( var file in files )
{
if ( file . Extension ! = ".maFile" ) continue ;
string contents = File . ReadAllText ( file . FullName ) ;
try
{
SteamGuardAccount account = JsonConvert . DeserializeObject < SteamGuardAccount > ( contents ) ;
ManifestEntry newEntry = new ManifestEntry ( )
{
Filename = file . Name ,
SteamID = account . Session . SteamID
} ;
newManifest . Entries . Add ( newEntry ) ;
}
catch ( Exception )
{
}
}
if ( newManifest . Entries . Count > 0 )
{
newManifest . Save ( ) ;
newManifest . PromptSetupPassKey ( "This version of SDA has encryption. Please enter a passkey below, or hit cancel to remain unencrypted" ) ;
}
}
}
if ( newManifest . Save ( ) )
{
return newManifest ;
}
return null ;
}
public class IncorrectPassKeyException : Exception { }
public class ManifestNotEncryptedException : Exception { }
public string PromptForPassKey ( )
{
if ( ! this . Encrypted )
{
throw new ManifestNotEncryptedException ( ) ;
}
bool passKeyValid = false ;
2016-08-21 23:02:17 +02:00
string passKey = "" ;
2016-08-21 20:17:04 +02:00
while ( ! passKeyValid )
{
2016-08-21 23:02:17 +02:00
Console . WriteLine ( "Please enter encryption password: " ) ;
passKey = Console . ReadLine ( ) ;
if ( passKey = = "" )
continue ;
passKeyValid = this . VerifyPasskey ( passKey ) ;
if ( ! passKeyValid )
2016-08-21 20:17:04 +02:00
{
2016-08-21 23:02:17 +02:00
Console . WriteLine ( "Incorrect." ) ;
2016-08-21 20:17:04 +02:00
}
}
return passKey ;
}
public string PromptSetupPassKey ( string initialPrompt = "Enter passkey, or hit cancel to remain unencrypted." )
{
2016-08-21 23:02:17 +02:00
Console . Write ( "Would you like to use encryption? [Y/n] " ) ;
string doEncryptAnswer = Console . ReadLine ( ) ;
if ( doEncryptAnswer = = "n" | | doEncryptAnswer = = "N" )
2016-08-21 20:17:04 +02:00
{
2016-08-21 23:02:17 +02:00
Console . WriteLine ( "WARNING: You chose to not encrypt your files. Doing so imposes a security risk for yourself. If an attacker were to gain access to your computer, they could completely lock you out of your account and steal all your items." ) ;
2016-08-21 20:17:04 +02:00
return null ;
}
2016-08-21 23:02:17 +02:00
string newPassKey = "" ;
string confirmPassKey = "" ;
do
2016-08-21 20:17:04 +02:00
{
2016-08-21 23:02:17 +02:00
Console . Write ( "Enter passkey: " ) ;
newPassKey = Console . ReadLine ( ) ;
Console . Write ( "Confirm passkey: " ) ;
confirmPassKey = Console . ReadLine ( ) ;
2016-08-21 20:17:04 +02:00
2016-08-21 23:02:17 +02:00
if ( newPassKey ! = confirmPassKey )
{
Console . WriteLine ( "Passkeys do not match." ) ;
}
} while ( newPassKey ! = confirmPassKey ) ;
2016-08-21 20:17:04 +02:00
if ( ! this . ChangeEncryptionKey ( null , newPassKey ) )
{
2016-08-21 23:02:17 +02:00
Console . WriteLine ( "Unable to set passkey." ) ;
2016-08-21 20:17:04 +02:00
return null ;
}
else
{
2016-08-21 23:02:17 +02:00
Console . WriteLine ( "Passkey successfully set." ) ;
2016-08-21 20:17:04 +02:00
}
return newPassKey ;
}
public SteamAuth . SteamGuardAccount [ ] GetAllAccounts ( string passKey = null , int limit = - 1 )
{
if ( passKey = = null & & this . Encrypted ) return new SteamGuardAccount [ 0 ] ;
List < SteamAuth . SteamGuardAccount > accounts = new List < SteamAuth . SteamGuardAccount > ( ) ;
foreach ( var entry in this . Entries )
{
2016-08-22 01:24:23 +02:00
string fileText = File . ReadAllText ( Path . Combine ( Program . SteamGuardPath , entry . Filename ) ) ;
2016-08-21 20:17:04 +02:00
if ( this . Encrypted )
{
2016-08-21 23:02:17 +02:00
throw new NotSupportedException ( "Encrypted maFiles are not supported at this time." ) ;
//string decryptedText = FileEncryptor.DecryptData(passKey, entry.Salt, entry.IV, fileText);
//if (decryptedText == null) return new SteamGuardAccount[0];
//fileText = decryptedText;
2016-08-21 20:17:04 +02:00
}
var account = JsonConvert . DeserializeObject < SteamAuth . SteamGuardAccount > ( fileText ) ;
if ( account = = null ) continue ;
accounts . Add ( account ) ;
if ( limit ! = - 1 & & limit > = accounts . Count )
break ;
}
return accounts . ToArray ( ) ;
}
public bool ChangeEncryptionKey ( string oldKey , string newKey )
{
2016-08-21 23:02:17 +02:00
throw new NotSupportedException ( "Encrypted maFiles are not supported at this time." ) ;
2016-08-21 20:17:04 +02:00
}
public bool VerifyPasskey ( string passkey )
{
if ( ! this . Encrypted | | this . Entries . Count = = 0 ) return true ;
var accounts = this . GetAllAccounts ( passkey , 1 ) ;
return accounts ! = null & & accounts . Length = = 1 ;
}
public bool RemoveAccount ( SteamGuardAccount account , bool deleteMaFile = true )
{
ManifestEntry entry = ( from e in this . Entries where e . SteamID = = account . Session . SteamID select e ) . FirstOrDefault ( ) ;
if ( entry = = null ) return true ; // If something never existed, did you do what they asked?
2016-08-22 01:24:23 +02:00
string filename = Path . Combine ( Program . SteamGuardPath , entry . Filename ) ;
2016-08-21 20:17:04 +02:00
this . Entries . Remove ( entry ) ;
if ( this . Entries . Count = = 0 )
{
this . Encrypted = false ;
}
if ( this . Save ( ) & & deleteMaFile )
{
try
{
File . Delete ( filename ) ;
return true ;
}
catch ( Exception )
{
return false ;
}
}
return false ;
}
public bool SaveAccount ( SteamGuardAccount account , bool encrypt , string passKey = null )
{
if ( encrypt & & String . IsNullOrEmpty ( passKey ) ) return false ;
if ( ! encrypt & & this . Encrypted ) return false ;
string salt = null ;
string iV = null ;
string jsonAccount = JsonConvert . SerializeObject ( account ) ;
if ( encrypt )
{
2016-08-21 23:02:17 +02:00
throw new NotSupportedException ( "Encrypted maFiles are not supported at this time." ) ;
2016-08-21 20:17:04 +02:00
}
2016-08-22 01:24:23 +02:00
2016-08-21 20:17:04 +02:00
string filename = account . Session . SteamID . ToString ( ) + ".maFile" ;
ManifestEntry newEntry = new ManifestEntry ( )
{
SteamID = account . Session . SteamID ,
IV = iV ,
Salt = salt ,
Filename = filename
} ;
bool foundExistingEntry = false ;
for ( int i = 0 ; i < this . Entries . Count ; i + + )
{
if ( this . Entries [ i ] . SteamID = = account . Session . SteamID )
{
this . Entries [ i ] = newEntry ;
foundExistingEntry = true ;
break ;
}
}
if ( ! foundExistingEntry )
{
this . Entries . Add ( newEntry ) ;
}
bool wasEncrypted = this . Encrypted ;
this . Encrypted = encrypt | | this . Encrypted ;
if ( ! this . Save ( ) )
{
this . Encrypted = wasEncrypted ;
return false ;
}
try
{
2016-08-22 01:24:23 +02:00
File . WriteAllText ( Program . SteamGuardPath + filename , jsonAccount ) ;
2016-08-21 20:17:04 +02:00
return true ;
}
catch ( Exception )
{
return false ;
}
}
public bool Save ( )
{
2016-08-22 01:24:23 +02:00
string filename = Program . SteamGuardPath + "manifest.json" ;
if ( ! Directory . Exists ( Program . SteamGuardPath ) )
2016-08-21 20:17:04 +02:00
{
try
{
2016-08-22 01:24:23 +02:00
Directory . CreateDirectory ( Program . SteamGuardPath ) ;
2016-08-21 20:17:04 +02:00
}
catch ( Exception )
{
return false ;
}
}
try
{
string contents = JsonConvert . SerializeObject ( this ) ;
File . WriteAllText ( filename , contents ) ;
return true ;
}
catch ( Exception )
{
return false ;
}
}
private void RecomputeExistingEntries ( )
{
List < ManifestEntry > newEntries = new List < ManifestEntry > ( ) ;
foreach ( var entry in this . Entries )
{
2016-08-22 01:24:23 +02:00
string filename = Path . Combine ( Program . SteamGuardPath , entry . Filename ) ;
2016-08-21 20:17:04 +02:00
if ( File . Exists ( filename ) )
{
newEntries . Add ( entry ) ;
}
}
this . Entries = newEntries ;
if ( this . Entries . Count = = 0 )
{
this . Encrypted = false ;
}
}
public void MoveEntry ( int from , int to )
{
if ( from < 0 | | to < 0 | | from > Entries . Count | | to > Entries . Count - 1 ) return ;
ManifestEntry sel = Entries [ from ] ;
Entries . RemoveAt ( from ) ;
Entries . Insert ( to , sel ) ;
Save ( ) ;
}
public class ManifestEntry
{
[JsonProperty("encryption_iv")]
public string IV { get ; set ; }
[JsonProperty("encryption_salt")]
public string Salt { get ; set ; }
[JsonProperty("filename")]
public string Filename { get ; set ; }
[JsonProperty("steamid")]
public ulong SteamID { get ; set ; }
}
}