using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Timers;
using MinecraftClient.Protocol.Session;
namespace MinecraftClient.Protocol.Keys
{
///
/// Handle keys caching and storage.
///
public static class KeysCache
{
private const string KeysCacheFilePlaintext = "ProfileKeyCache.ini";
private static FileMonitor cachemonitor;
private static Dictionary keys = new Dictionary();
private static Timer updatetimer = new Timer(100);
private static List> pendingadds = new List>();
private static BinaryFormatter formatter = new BinaryFormatter();
///
/// Retrieve whether KeysCache contains a keys for the given login.
///
/// User login used with Minecraft.net
/// TRUE if keys are available
public static bool Contains(string login)
{
return keys.ContainsKey(login);
}
///
/// Store keys and save it to disk if required.
///
/// User login used with Minecraft.net
/// User keys
public static void Store(string login, PlayerKeyPair playerKeyPair)
{
if (Contains(login))
{
keys[login] = playerKeyPair;
}
else
{
keys.Add(login, playerKeyPair);
}
if (Settings.ProfileKeyCaching == CacheType.Disk && updatetimer.Enabled == true)
{
pendingadds.Add(new KeyValuePair(login, playerKeyPair));
}
else if (Settings.ProfileKeyCaching == CacheType.Disk)
{
SaveToDisk();
}
}
///
/// Retrieve keys for the given login.
///
/// User login used with Minecraft.net
/// PlayerKeyPair for given login
public static PlayerKeyPair Get(string login)
{
return keys[login];
}
///
/// Initialize cache monitoring to keep cache updated with external changes.
///
/// TRUE if keys are seeded from file
public static bool InitializeDiskCache()
{
cachemonitor = new FileMonitor(AppDomain.CurrentDomain.BaseDirectory, KeysCacheFilePlaintext, new FileSystemEventHandler(OnChanged));
updatetimer.Elapsed += HandlePending;
return LoadFromDisk();
}
///
/// Reloads cache on external cache file change.
///
/// Sender
/// Event data
private static void OnChanged(object sender, FileSystemEventArgs e)
{
updatetimer.Stop();
updatetimer.Start();
}
///
/// Called after timer elapsed. Reads disk cache and adds new/modified keys back.
///
/// Sender
/// Event data
private static void HandlePending(object sender, ElapsedEventArgs e)
{
updatetimer.Stop();
LoadFromDisk();
foreach (KeyValuePair pending in pendingadds.ToArray())
{
Store(pending.Key, pending.Value);
pendingadds.Remove(pending);
}
}
///
/// Reads cache file and loads KeysInfos into KeysCache.
///
/// True if data is successfully loaded
private static bool LoadFromDisk()
{
//User-editable keys cache file in text format
if (File.Exists(KeysCacheFilePlaintext))
{
if (Settings.DebugMessages)
ConsoleIO.WriteLineFormatted(Translations.Get("cache.loading_keys", KeysCacheFilePlaintext));
try
{
foreach (string line in FileMonitor.ReadAllLinesWithRetries(KeysCacheFilePlaintext))
{
if (!line.Trim().StartsWith("#"))
{
int separatorIdx = line.IndexOf('=');
if (separatorIdx >= 1 && line.Length > separatorIdx + 1)
{
string login = line.Substring(0, separatorIdx);
string value = line.Substring(separatorIdx + 1);
try
{
PlayerKeyPair playerKeyPair = PlayerKeyPair.FromString(value);
keys[login] = playerKeyPair;
if (Settings.DebugMessages)
ConsoleIO.WriteLineFormatted(Translations.Get("cache.loaded_keys", playerKeyPair.ExpiresAt.ToString()));
}
catch (InvalidDataException e)
{
if (Settings.DebugMessages)
ConsoleIO.WriteLineFormatted(Translations.Get("cache.ignore_string_keys", value, e.Message));
}
catch (FormatException e)
{
if (Settings.DebugMessages)
ConsoleIO.WriteLineFormatted(Translations.Get("cache.ignore_string_keys", value, e.Message));
}
catch (ArgumentNullException e)
{
if (Settings.DebugMessages)
ConsoleIO.WriteLineFormatted(Translations.Get("cache.ignore_string_keys", value, e.Message));
}
}
else if (Settings.DebugMessages)
{
ConsoleIO.WriteLineFormatted(Translations.Get("cache.ignore_line_keys", line));
}
}
}
}
catch (IOException e)
{
ConsoleIO.WriteLineFormatted(Translations.Get("cache.read_fail_plain_keys", e.Message));
}
}
return keys.Count > 0;
}
///
/// Saves player's keypair from KeysCache into cache file.
///
private static void SaveToDisk()
{
if (Settings.DebugMessages)
Translations.WriteLineFormatted("cache.saving_keys");
List KeysCacheLines = new List();
KeysCacheLines.Add("# Generated by MCC v" + Program.Version + " - Keep it secret & Edit at own risk!");
KeysCacheLines.Add("# ProfileKey=PublicKey(base64),PublicKeySignature(base64),PublicKeySignatureV2(base64),PrivateKey(base64),ExpiresAt,RefreshAfter");
foreach (KeyValuePair entry in keys)
KeysCacheLines.Add(entry.Key + '=' + entry.Value.ToString());
try
{
FileMonitor.WriteAllLinesWithRetries(KeysCacheFilePlaintext, KeysCacheLines);
}
catch (IOException e)
{
ConsoleIO.WriteLineFormatted(Translations.Get("cache.save_fail_keys", e.Message));
}
}
}
}