2022-10-02 18:31:08 +08:00
using System ;
2016-03-02 18:16:19 -07:00
using System.Collections.Generic ;
2022-12-20 22:41:14 +08:00
using System.Diagnostics.CodeAnalysis ;
2016-03-02 18:16:19 -07:00
using System.IO ;
2022-12-20 22:41:14 +08:00
using System.Reflection ;
2016-03-02 18:16:19 -07:00
using System.Runtime.Serialization ;
using System.Runtime.Serialization.Formatters.Binary ;
2022-12-20 22:41:14 +08:00
using System.ServiceModel.Channels ;
using System.Text ;
using System.Text.Json ;
using System.Text.Json.Serialization ;
using System.Text.RegularExpressions ;
using System.Threading.Tasks ;
2016-03-02 19:08:24 -07:00
using System.Timers ;
2022-12-20 22:41:14 +08:00
using MinecraftClient.Protocol.ProfileKey ;
using MinecraftClient.Scripting ;
2022-10-05 15:02:30 +08:00
using static MinecraftClient . Settings ;
using static MinecraftClient . Settings . MainConfigHealper . MainConfig . AdvancedConfig ;
2016-03-02 18:16:19 -07:00
2018-05-25 20:27:31 +02:00
namespace MinecraftClient.Protocol.Session
2016-03-02 18:16:19 -07:00
{
2016-03-10 13:29:05 +01:00
/// <summary>
/// Handle sessions caching and storage.
2016-03-02 18:16:19 -07:00
/// </summary>
2022-12-20 22:41:14 +08:00
public static partial class SessionCache
2016-03-02 18:16:19 -07:00
{
2022-12-20 22:41:14 +08:00
public class Cache
{
[JsonInclude]
public Dictionary < string , SessionToken > SessionTokens = new ( ) ;
2018-05-03 23:51:56 +02:00
2022-12-20 22:41:14 +08:00
[JsonInclude]
public Dictionary < string , PlayerKeyPair > ProfileKeys = new ( ) ;
2016-03-02 18:16:19 -07:00
2022-12-20 22:41:14 +08:00
[JsonInclude]
public Dictionary < string , ServerInfo > ServerKeys = new ( ) ;
2016-03-02 18:16:19 -07:00
2022-12-20 22:41:14 +08:00
public record ServerInfo
2016-03-02 18:16:19 -07:00
{
2022-12-20 22:41:14 +08:00
public ServerInfo ( string serverIDhash , byte [ ] serverPublicKey )
{
ServerIDhash = serverIDhash ;
ServerPublicKey = serverPublicKey ;
}
2016-03-02 18:16:19 -07:00
2022-12-20 22:41:14 +08:00
public string? ServerIDhash { init ; get ; }
public byte [ ] ? ServerPublicKey { init ; get ; }
2016-03-02 18:16:19 -07:00
}
}
2022-12-20 22:41:14 +08:00
private static Cache cache = new ( ) ;
2016-03-02 18:16:19 -07:00
2022-12-20 22:41:14 +08:00
private const string SessionCacheFileJson = "SessionCache.json" ;
2016-03-02 19:08:24 -07:00
2022-12-20 22:41:14 +08:00
private static readonly JsonSerializerOptions JsonOptions = new ( JsonSerializerDefaults . General )
2016-03-02 18:16:19 -07:00
{
2022-12-20 22:41:14 +08:00
WriteIndented = true ,
AllowTrailingCommas = true ,
PropertyNameCaseInsensitive = false ,
ReadCommentHandling = JsonCommentHandling . Skip ,
NumberHandling = JsonNumberHandling . AllowReadingFromString ,
} ;
2016-03-02 19:08:24 -07:00
2016-03-02 18:16:19 -07:00
2022-12-20 22:41:14 +08:00
public static async Task ReadCacheSessionAsync ( )
2016-03-02 18:16:19 -07:00
{
2022-12-20 22:41:14 +08:00
if ( File . Exists ( SessionCacheFileJson ) )
2018-05-03 23:51:56 +02:00
{
2022-10-05 15:02:30 +08:00
if ( Config . Logging . DebugMessages )
2022-12-20 22:41:14 +08:00
ConsoleIO . WriteLineFormatted ( string . Format ( Translations . cache_loading_session , SessionCacheFileJson ) ) ;
2016-10-08 19:52:36 +02:00
2022-12-20 22:41:14 +08:00
FileStream fileStream = File . OpenRead ( SessionCacheFileJson ) ;
2018-05-03 23:51:56 +02:00
2016-03-02 18:16:19 -07:00
try
{
2022-12-20 22:41:14 +08:00
Cache ? diskCache = ( Cache ? ) await JsonSerializer . DeserializeAsync ( fileStream , typeof ( Cache ) , JsonOptions ) ;
if ( diskCache ! = null )
2016-03-02 18:16:19 -07:00
{
2022-12-20 22:41:14 +08:00
cache = diskCache ;
2022-10-05 15:02:30 +08:00
if ( Config . Logging . DebugMessages )
2022-12-20 22:41:14 +08:00
ConsoleIO . WriteLineFormatted ( string . Format ( Translations . cache_loaded , cache . SessionTokens . Count , cache . ProfileKeys . Count ) ) ;
2016-03-02 18:16:19 -07:00
}
}
2022-12-20 22:41:14 +08:00
catch ( IOException e )
2016-03-02 18:16:19 -07:00
{
2022-12-20 22:41:14 +08:00
ConsoleIO . WriteLineFormatted ( "§8" + string . Format ( Translations . cache_read_fail_plain , e . Message ) ) ;
2016-03-02 18:16:19 -07:00
}
2022-12-20 22:41:14 +08:00
catch ( JsonException e )
2016-03-02 18:16:19 -07:00
{
2022-12-20 22:41:14 +08:00
ConsoleIO . WriteLineFormatted ( "§8" + string . Format ( Translations . cache_read_fail_plain , e . Message ) ) ;
2016-03-02 18:16:19 -07:00
}
2022-12-20 22:41:14 +08:00
await fileStream . DisposeAsync ( ) ;
2016-03-02 18:16:19 -07:00
}
2022-12-20 22:41:14 +08:00
}
2018-05-03 23:51:56 +02:00
2022-12-20 22:41:14 +08:00
/// <summary>
/// Retrieve whether SessionCache contains a session for the given login.
/// </summary>
/// <param name="login">User login used with Minecraft.net</param>
/// <returns>TRUE if session is available</returns>
public static bool ContainsSession ( string login )
{
return cache . SessionTokens . ContainsKey ( login ) ;
}
2018-05-03 23:51:56 +02:00
2022-12-20 22:41:14 +08:00
/// <summary>
/// Retrieve a session token for the given login.
/// </summary>
/// <param name="login">User login used with Minecraft.net</param>
/// <returns>SessionToken for given login</returns>
public static Tuple < SessionToken ? , PlayerKeyPair ? > GetSession ( string login )
{
cache . SessionTokens . TryGetValue ( login , out SessionToken ? sessionToken ) ;
cache . ProfileKeys . TryGetValue ( login , out PlayerKeyPair ? playerKeyPair ) ;
return new ( sessionToken , playerKeyPair ) ;
}
public static Cache . ServerInfo ? GetServerInfo ( string server )
{
if ( cache . ServerKeys . TryGetValue ( server , out Cache . ServerInfo ? info ) )
return info ;
else
return null ;
}
/// <summary>
/// Store a session and save it to disk if required.
/// </summary>
/// <param name="login">User login used with Minecraft.net</param>
/// <param name="newSession">User session token used with Minecraft.net</param>
public static async Task StoreSessionAsync ( string login , SessionToken ? sessionToken , PlayerKeyPair ? profileKey )
{
if ( sessionToken ! = null )
cache . SessionTokens [ login ] = sessionToken ;
if ( profileKey ! = null )
cache . ProfileKeys [ login ] = profileKey ;
2018-05-03 23:51:56 +02:00
2022-12-20 22:41:14 +08:00
if ( Config . Main . Advanced . SessionCache = = CacheType . disk )
await SaveToDisk ( ) ;
}
public static void StoreServerInfo ( string server , string ServerIDhash , byte [ ] ServerPublicKey )
{
cache . ServerKeys [ server ] = new ( ServerIDhash , ServerPublicKey ) ;
2016-03-02 18:16:19 -07:00
}
2016-03-10 13:29:05 +01:00
/// <summary>
/// Saves SessionToken's from SessionCache into cache file.
2016-03-02 18:16:19 -07:00
/// </summary>
2022-12-20 22:41:14 +08:00
private static async Task SaveToDisk ( )
2016-03-02 18:16:19 -07:00
{
2022-10-05 15:02:30 +08:00
if ( Config . Logging . DebugMessages )
2022-11-30 16:08:55 +08:00
ConsoleIO . WriteLineFormatted ( "§8" + Translations . cache_saving , acceptnewlines : true ) ;
2016-10-08 19:52:36 +02:00
2022-12-20 22:41:14 +08:00
foreach ( ( string login , SessionToken session ) in cache . SessionTokens )
2022-10-02 18:31:08 +08:00
{
2022-12-20 22:41:14 +08:00
if ( ! GetJwtRegex ( ) . IsMatch ( session . ID ) )
cache . SessionTokens . Remove ( login ) ;
else if ( ! ChatBot . IsValidName ( session . PlayerName ) )
cache . SessionTokens . Remove ( login ) ;
else if ( ! Guid . TryParseExact ( session . PlayerID , "N" , out _ ) )
cache . SessionTokens . Remove ( login ) ;
else if ( ! Guid . TryParseExact ( session . ClientID , "N" , out _ ) )
cache . SessionTokens . Remove ( login ) ;
// No validation on refresh token because it is custom format token (not Jwt)
}
foreach ( ( string login , PlayerKeyPair profileKey ) in cache . ProfileKeys )
{
if ( profileKey . NeedRefresh ( ) )
cache . ProfileKeys . Remove ( login ) ;
}
2016-03-02 18:16:19 -07:00
2020-08-07 11:58:44 +02:00
try
2016-03-02 18:16:19 -07:00
{
2022-12-20 22:41:14 +08:00
FileStream fileStream = File . Open ( SessionCacheFileJson , FileMode . Create ) ;
await fileStream . WriteAsync ( Encoding . UTF8 . GetBytes ( $"/* Generated by MCC v{Program.Version} - Keep it secret & Edit at own risk! */{Environment.NewLine}" ) ) ;
await JsonSerializer . SerializeAsync ( fileStream , cache , typeof ( Cache ) , JsonOptions ) ;
await fileStream . FlushAsync ( ) ;
fileStream . Close ( ) ;
await fileStream . DisposeAsync ( ) ;
2020-08-07 11:58:44 +02:00
}
catch ( IOException e )
{
2022-11-30 16:08:55 +08:00
ConsoleIO . WriteLineFormatted ( "§8" + string . Format ( Translations . cache_save_fail , e . Message ) ) ;
2016-03-02 18:16:19 -07:00
}
2022-12-20 22:41:14 +08:00
catch ( JsonException e )
{
ConsoleIO . WriteLineFormatted ( "§8" + string . Format ( Translations . cache_save_fail , e . Message ) ) ;
}
2016-03-02 18:16:19 -07:00
}
2022-12-20 22:41:14 +08:00
[GeneratedRegex("^[A-Za-z0-9-_] + \ \ . [ A - Za - z0 - 9 - _ ] + \ \ . [ A - Za - z0 - 9 - _ ] + $ ", RegexOptions.Compiled)]
private static partial Regex GetJwtRegex ( ) ;
2016-03-02 18:16:19 -07:00
}
}