2016-03-02 18:16:19 -07:00
|
|
|
|
using MinecraftClient.Protocol;
|
|
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Runtime.Serialization;
|
|
|
|
|
|
using System.Runtime.Serialization.Formatters.Binary;
|
2016-03-02 19:08:24 -07:00
|
|
|
|
using System.Timers;
|
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>
|
|
|
|
|
|
public static class SessionCache
|
|
|
|
|
|
{
|
2018-05-03 23:51:56 +02:00
|
|
|
|
private const string SessionCacheFilePlaintext = "SessionCache.ini";
|
|
|
|
|
|
private const string SessionCacheFileSerialized = "SessionCache.db";
|
|
|
|
|
|
private static readonly string SessionCacheFileMinecraft = String.Concat(
|
|
|
|
|
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
|
|
|
|
|
Path.DirectorySeparatorChar,
|
|
|
|
|
|
".minecraft",
|
|
|
|
|
|
Path.DirectorySeparatorChar,
|
|
|
|
|
|
"launcher_profiles.json"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2019-04-17 05:18:19 +02:00
|
|
|
|
private static SessionFileMonitor cachemonitor;
|
2016-03-02 18:16:19 -07:00
|
|
|
|
private static Dictionary<string, SessionToken> sessions = new Dictionary<string, SessionToken>();
|
2016-03-02 19:08:24 -07:00
|
|
|
|
private static Timer updatetimer = new Timer(100);
|
|
|
|
|
|
private static List<KeyValuePair<string, SessionToken>> pendingadds = new List<KeyValuePair<string, SessionToken>>();
|
2016-03-02 18:16:19 -07:00
|
|
|
|
private static BinaryFormatter formatter = new BinaryFormatter();
|
|
|
|
|
|
|
2016-03-10 13:29:05 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Retrieve whether SessionCache contains a session for the given login.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="login">User login used with Minecraft.net</param>
|
2016-03-02 18:16:19 -07:00
|
|
|
|
/// <returns>TRUE if session is available</returns>
|
|
|
|
|
|
public static bool Contains(string login)
|
|
|
|
|
|
{
|
|
|
|
|
|
return sessions.ContainsKey(login);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-10 13:29:05 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Store a session and save it to disk if required.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="login">User login used with Minecraft.net</param>
|
2016-03-02 18:16:19 -07:00
|
|
|
|
/// <param name="session">User session token used with Minecraft.net</param>
|
|
|
|
|
|
public static void Store(string login, SessionToken session)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Contains(login))
|
|
|
|
|
|
{
|
|
|
|
|
|
sessions[login] = session;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
sessions.Add(login, session);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-10 13:29:05 +01:00
|
|
|
|
if (Settings.SessionCaching == CacheType.Disk && updatetimer.Enabled == true)
|
|
|
|
|
|
{
|
2016-03-02 19:08:24 -07:00
|
|
|
|
pendingadds.Add(new KeyValuePair<string, SessionToken>(login, session));
|
2016-03-10 13:29:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.SessionCaching == CacheType.Disk)
|
2016-03-02 18:16:19 -07:00
|
|
|
|
{
|
|
|
|
|
|
SaveToDisk();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-10 13:29:05 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Retrieve a session token for the given login.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="login">User login used with Minecraft.net</param>
|
2016-03-02 18:16:19 -07:00
|
|
|
|
/// <returns>SessionToken for given login</returns>
|
|
|
|
|
|
public static SessionToken Get(string login)
|
|
|
|
|
|
{
|
|
|
|
|
|
return sessions[login];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-10 13:29:05 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Initialize cache monitoring to keep cache updated with external changes.
|
|
|
|
|
|
/// </summary>
|
2016-03-02 18:16:19 -07:00
|
|
|
|
/// <returns>TRUE if session tokens are seeded from file</returns>
|
|
|
|
|
|
public static bool InitializeDiskCache()
|
|
|
|
|
|
{
|
2019-04-17 05:18:19 +02:00
|
|
|
|
cachemonitor = new SessionFileMonitor(AppDomain.CurrentDomain.BaseDirectory, SessionCacheFilePlaintext, new FileSystemEventHandler(OnChanged));
|
2016-03-02 19:08:24 -07:00
|
|
|
|
updatetimer.Elapsed += HandlePending;
|
2016-03-02 18:16:19 -07:00
|
|
|
|
return LoadFromDisk();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-10 13:29:05 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Reloads cache on external cache file change.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="sender">Sender</param>
|
2016-03-02 18:16:19 -07:00
|
|
|
|
/// <param name="e">Event data</param>
|
2016-03-02 19:08:24 -07:00
|
|
|
|
private static void OnChanged(object sender, FileSystemEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
updatetimer.Stop();
|
|
|
|
|
|
updatetimer.Start();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-10 13:29:05 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Called after timer elapsed. Reads disk cache and adds new/modified sessions back.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="sender">Sender</param>
|
2016-03-02 19:08:24 -07:00
|
|
|
|
/// <param name="e">Event data</param>
|
|
|
|
|
|
private static void HandlePending(object sender, ElapsedEventArgs e)
|
2016-03-02 18:16:19 -07:00
|
|
|
|
{
|
2016-10-08 19:52:36 +02:00
|
|
|
|
updatetimer.Stop();
|
2016-03-02 18:16:19 -07:00
|
|
|
|
LoadFromDisk();
|
2016-03-02 19:08:24 -07:00
|
|
|
|
|
|
|
|
|
|
foreach(KeyValuePair<string, SessionToken> pending in pendingadds.ToArray())
|
|
|
|
|
|
{
|
|
|
|
|
|
Store(pending.Key, pending.Value);
|
|
|
|
|
|
pendingadds.Remove(pending);
|
|
|
|
|
|
}
|
2016-03-02 18:16:19 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-10 13:29:05 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Reads cache file and loads SessionTokens into SessionCache.
|
|
|
|
|
|
/// </summary>
|
2016-03-02 18:16:19 -07:00
|
|
|
|
/// <returns>True if data is successfully loaded</returns>
|
|
|
|
|
|
private static bool LoadFromDisk()
|
|
|
|
|
|
{
|
2018-05-03 23:51:56 +02:00
|
|
|
|
//Grab sessions in the Minecraft directory
|
|
|
|
|
|
if (File.Exists(SessionCacheFileMinecraft))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Settings.DebugMessages)
|
|
|
|
|
|
ConsoleIO.WriteLineFormatted("§8Loading Minecraft profiles: " + Path.GetFileName(SessionCacheFileMinecraft));
|
|
|
|
|
|
Json.JSONData mcSession = new Json.JSONData(Json.JSONData.DataType.String);
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
mcSession = Json.ParseJson(File.ReadAllText(SessionCacheFileMinecraft));
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (IOException) { /* Failed to read file from disk -- ignoring */ }
|
|
|
|
|
|
if (mcSession.Type == Json.JSONData.DataType.Object
|
|
|
|
|
|
&& mcSession.Properties.ContainsKey("clientToken")
|
|
|
|
|
|
&& mcSession.Properties.ContainsKey("authenticationDatabase"))
|
|
|
|
|
|
{
|
|
|
|
|
|
Guid temp;
|
|
|
|
|
|
string clientID = mcSession.Properties["clientToken"].StringValue.Replace("-", "");
|
|
|
|
|
|
Dictionary<string, Json.JSONData> sessionItems = mcSession.Properties["authenticationDatabase"].Properties;
|
|
|
|
|
|
foreach (string key in sessionItems.Keys)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Guid.TryParseExact(key, "N", out temp))
|
|
|
|
|
|
{
|
|
|
|
|
|
Dictionary<string, Json.JSONData> sessionItem = sessionItems[key].Properties;
|
|
|
|
|
|
if (sessionItem.ContainsKey("displayName")
|
|
|
|
|
|
&& sessionItem.ContainsKey("accessToken")
|
|
|
|
|
|
&& sessionItem.ContainsKey("username")
|
|
|
|
|
|
&& sessionItem.ContainsKey("uuid"))
|
|
|
|
|
|
{
|
|
|
|
|
|
string login = sessionItem["username"].StringValue.ToLower();
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
SessionToken session = SessionToken.FromString(String.Join(",",
|
|
|
|
|
|
sessionItem["accessToken"].StringValue,
|
|
|
|
|
|
sessionItem["displayName"].StringValue,
|
|
|
|
|
|
sessionItem["uuid"].StringValue.Replace("-", ""),
|
|
|
|
|
|
clientID
|
|
|
|
|
|
));
|
|
|
|
|
|
if (Settings.DebugMessages)
|
|
|
|
|
|
ConsoleIO.WriteLineFormatted("§8Loaded session: " + login + ':' + session.ID);
|
|
|
|
|
|
sessions[login] = session;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (InvalidDataException) { /* Not a valid session */ }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-10-08 19:52:36 +02:00
|
|
|
|
|
2018-05-03 23:51:56 +02:00
|
|
|
|
//Serialized session cache file in binary format
|
|
|
|
|
|
if (File.Exists(SessionCacheFileSerialized))
|
2016-03-02 18:16:19 -07:00
|
|
|
|
{
|
2018-05-03 23:51:56 +02:00
|
|
|
|
if (Settings.DebugMessages)
|
|
|
|
|
|
ConsoleIO.WriteLineFormatted("§8Converting session cache from disk: " + SessionCacheFileSerialized);
|
|
|
|
|
|
|
2016-03-02 18:16:19 -07:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2018-05-03 23:51:56 +02:00
|
|
|
|
using (FileStream fs = new FileStream(SessionCacheFileSerialized, FileMode.Open, FileAccess.Read, FileShare.Read))
|
2016-03-02 18:16:19 -07:00
|
|
|
|
{
|
2018-05-03 23:51:56 +02:00
|
|
|
|
Dictionary<string, SessionToken> sessionsTemp = (Dictionary<string, SessionToken>)formatter.Deserialize(fs);
|
|
|
|
|
|
foreach (KeyValuePair<string, SessionToken> item in sessionsTemp)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Settings.DebugMessages)
|
|
|
|
|
|
ConsoleIO.WriteLineFormatted("§8Loaded session: " + item.Key + ':' + item.Value.ID);
|
|
|
|
|
|
sessions[item.Key] = item.Value;
|
|
|
|
|
|
}
|
2016-03-02 18:16:19 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (IOException ex)
|
|
|
|
|
|
{
|
2016-10-08 19:52:36 +02:00
|
|
|
|
ConsoleIO.WriteLineFormatted("§8Failed to read session cache from disk: " + ex.Message);
|
2016-03-02 18:16:19 -07:00
|
|
|
|
}
|
2016-10-08 19:52:36 +02:00
|
|
|
|
catch (SerializationException ex2)
|
2016-03-02 18:16:19 -07:00
|
|
|
|
{
|
2016-10-08 19:52:36 +02:00
|
|
|
|
ConsoleIO.WriteLineFormatted("§8Got malformed data while reading session cache from disk: " + ex2.Message);
|
2016-03-02 18:16:19 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2018-05-03 23:51:56 +02:00
|
|
|
|
|
|
|
|
|
|
//User-editable session cache file in text format
|
|
|
|
|
|
if (File.Exists(SessionCacheFilePlaintext))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Settings.DebugMessages)
|
|
|
|
|
|
ConsoleIO.WriteLineFormatted("§8Loading session cache from disk: " + SessionCacheFilePlaintext);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (string line in File.ReadAllLines(SessionCacheFilePlaintext))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!line.Trim().StartsWith("#"))
|
|
|
|
|
|
{
|
|
|
|
|
|
string[] keyValue = line.Split('=');
|
|
|
|
|
|
if (keyValue.Length == 2)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
string login = keyValue[0].ToLower();
|
|
|
|
|
|
SessionToken session = SessionToken.FromString(keyValue[1]);
|
|
|
|
|
|
if (Settings.DebugMessages)
|
|
|
|
|
|
ConsoleIO.WriteLineFormatted("§8Loaded session: " + login + ':' + session.ID);
|
|
|
|
|
|
sessions[login] = session;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (InvalidDataException e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Settings.DebugMessages)
|
|
|
|
|
|
ConsoleIO.WriteLineFormatted("§8Ignoring session token string '" + keyValue[1] + "': " + e.Message);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Settings.DebugMessages)
|
|
|
|
|
|
{
|
|
|
|
|
|
ConsoleIO.WriteLineFormatted("§8Ignoring invalid session token line: " + line);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return sessions.Count > 0;
|
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>
|
|
|
|
|
|
private static void SaveToDisk()
|
|
|
|
|
|
{
|
2016-10-08 19:52:36 +02:00
|
|
|
|
if (Settings.DebugMessages)
|
|
|
|
|
|
ConsoleIO.WriteLineFormatted("§8Saving session cache to disk");
|
|
|
|
|
|
|
2018-05-03 23:51:56 +02:00
|
|
|
|
bool fileexists = File.Exists(SessionCacheFilePlaintext);
|
2016-03-21 10:45:50 +01:00
|
|
|
|
IOException lastEx = null;
|
|
|
|
|
|
int attempt = 1;
|
2016-03-02 18:16:19 -07:00
|
|
|
|
|
2016-03-21 10:45:50 +01:00
|
|
|
|
while (attempt < 4)
|
2016-03-02 18:16:19 -07:00
|
|
|
|
{
|
2016-03-21 10:45:50 +01:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2018-05-03 23:51:56 +02:00
|
|
|
|
List<string> sessionCacheLines = new List<string>();
|
|
|
|
|
|
sessionCacheLines.Add("# Generated by MCC v" + Program.Version + " - Edit at own risk!");
|
2018-05-08 19:27:19 +02:00
|
|
|
|
sessionCacheLines.Add("# Login=SessionID,PlayerName,UUID,ClientID");
|
2018-05-03 23:51:56 +02:00
|
|
|
|
foreach (KeyValuePair<string, SessionToken> entry in sessions)
|
|
|
|
|
|
sessionCacheLines.Add(entry.Key + '=' + entry.Value.ToString());
|
|
|
|
|
|
File.WriteAllLines(SessionCacheFilePlaintext, sessionCacheLines);
|
|
|
|
|
|
//if (File.Exists(SessionCacheFileSerialized))
|
|
|
|
|
|
// File.Delete(SessionCacheFileSerialized);
|
2016-03-21 10:45:50 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (IOException ex)
|
2016-03-02 18:16:19 -07:00
|
|
|
|
{
|
2016-03-21 10:45:50 +01:00
|
|
|
|
lastEx = ex;
|
|
|
|
|
|
attempt++;
|
|
|
|
|
|
System.Threading.Thread.Sleep(new Random().Next(150, 350) * attempt); //CSMA/CD :)
|
2016-03-02 18:16:19 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-10-08 19:52:36 +02:00
|
|
|
|
ConsoleIO.WriteLineFormatted("§8Failed to write session cache to disk" + (lastEx != null ? ": " + lastEx.Message : ""));
|
2016-03-02 18:16:19 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|