diff --git a/MinecraftClient/ChatParser.cs b/MinecraftClient/ChatParser.cs
index 5f9daf6b..7a656792 100644
--- a/MinecraftClient/ChatParser.cs
+++ b/MinecraftClient/ChatParser.cs
@@ -95,29 +95,25 @@ namespace MinecraftClient
TranslationRules["commands.message.display.incoming"] = "§7%s whispers to you: %s";
TranslationRules["commands.message.display.outgoing"] = "§7You whisper to %s: %s";
- //Use translations from Minecraft assets if translation file is not found but a copy of the game is installed?
- if (!System.IO.File.Exists(Settings.TranslationsFile) //Try en_GB.lang
- && System.IO.File.Exists(Settings.TranslationsFile_FromMCDir))
- {
- Settings.TranslationsFile = Settings.TranslationsFile_FromMCDir;
- Console.ForegroundColor = ConsoleColor.DarkGray;
- ConsoleIO.WriteLine("Using en_GB.lang from your Minecraft directory.");
- Console.ForegroundColor = ConsoleColor.Gray;
- }
+ //Language file in a subfolder, depending on the language setting
+ if (!System.IO.Directory.Exists("lang"))
+ System.IO.Directory.CreateDirectory("lang");
- //Still not found? try downloading en_GB from Mojang's servers?
- if (!System.IO.File.Exists(Settings.TranslationsFile))
+ string Language_File = "lang\\" + Settings.Language + ".lang";
+
+ //File not found? Try downloading language file from Mojang's servers?
+ if (!System.IO.File.Exists(Language_File))
{
Console.ForegroundColor = ConsoleColor.DarkGray;
- ConsoleIO.WriteLine("Downloading en_GB.lang from Mojang's servers...");
+ ConsoleIO.WriteLine("Downloading '" + Settings.Language + ".lang' from Mojang servers...");
try
{
string assets_index = downloadString(Settings.TranslationsFile_Website_Index);
- string[] tmp = assets_index.Split(new string[] { "lang/en_GB.lang" }, StringSplitOptions.None);
+ string[] tmp = assets_index.Split(new string[] { "lang/" + Settings.Language + ".lang" }, StringSplitOptions.None);
tmp = tmp[1].Split(new string[] { "hash\": \"" }, StringSplitOptions.None);
string hash = tmp[1].Split('"')[0]; //Translations file identifier on Mojang's servers
- System.IO.File.WriteAllText(Settings.TranslationsFile, downloadString(Settings.TranslationsFile_Website_Download + '/' + hash.Substring(0, 2) + '/' + hash));
- ConsoleIO.WriteLine("Done. File saved as \"" + Settings.TranslationsFile + '"');
+ System.IO.File.WriteAllText(Language_File, downloadString(Settings.TranslationsFile_Website_Download + '/' + hash.Substring(0, 2) + '/' + hash));
+ ConsoleIO.WriteLine("Done. File saved as '" + Language_File + '\'');
}
catch
{
@@ -126,10 +122,20 @@ namespace MinecraftClient
Console.ForegroundColor = ConsoleColor.Gray;
}
- //Load the external dictionnary of translation rules or display an error message
- if (System.IO.File.Exists(Settings.TranslationsFile))
+ //Download Failed? Defaulting to en_GB.lang if the game is installed
+ if (!System.IO.File.Exists(Language_File) //Try en_GB.lang
+ && System.IO.File.Exists(Settings.TranslationsFile_FromMCDir))
{
- string[] translations = System.IO.File.ReadAllLines(Settings.TranslationsFile);
+ Language_File = Settings.TranslationsFile_FromMCDir;
+ Console.ForegroundColor = ConsoleColor.DarkGray;
+ ConsoleIO.WriteLine("Defaulting to en_GB.lang from your Minecraft directory.");
+ Console.ForegroundColor = ConsoleColor.Gray;
+ }
+
+ //Load the external dictionnary of translation rules or display an error message
+ if (System.IO.File.Exists(Language_File))
+ {
+ string[] translations = System.IO.File.ReadAllLines(Language_File);
foreach (string line in translations)
{
if (line.Length > 0)
@@ -149,8 +155,7 @@ namespace MinecraftClient
else //No external dictionnary found.
{
Console.ForegroundColor = ConsoleColor.DarkGray;
- ConsoleIO.WriteLine("Translations file not found: \"" + Settings.TranslationsFile + "\""
- + "\nYou can pick a translation file from .minecraft\\assets\\lang\\"
+ ConsoleIO.WriteLine("Translations file not found: \"" + Language_File + "\""
+ "\nSome messages won't be properly printed without this file.");
Console.ForegroundColor = ConsoleColor.Gray;
}
diff --git a/MinecraftClient/Crypto.cs b/MinecraftClient/Crypto.cs
index d30d6cf1..80501a98 100644
--- a/MinecraftClient/Crypto.cs
+++ b/MinecraftClient/Crypto.cs
@@ -3,110 +3,211 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
-using java.security;
-using java.security.spec;
-using javax.crypto;
-using javax.crypto.spec;
namespace MinecraftClient
{
///
- /// Cryptographic functions ported from Minecraft Source Code (Java). Decompiled with MCP. Copy, paste, little adjustements.
+ /// Methods for handling all the crypto stuff: RSA (Encryption Key Request), AES (Encrypted Stream), SHA-1 (Server Hash).
///
public class Crypto
{
- public static PublicKey GenerateRSAPublicKey(byte[] key)
- {
- X509EncodedKeySpec localX509EncodedKeySpec = new X509EncodedKeySpec(key);
- KeyFactory localKeyFactory = KeyFactory.getInstance("RSA");
- return localKeyFactory.generatePublic(localX509EncodedKeySpec);
- }
+ ///
+ /// Get a cryptographic service for encrypting data using the server's RSA public key
+ ///
+ /// Byte array containing the encoded key
+ /// Returns the corresponding RSA Crypto Service
- public static SecretKey GenerateAESPrivateKey()
+ public static RSACryptoServiceProvider DecodeRSAPublicKey(byte[] x509key)
{
- AesManaged aes = new AesManaged();
- aes.KeySize = 128; aes.GenerateKey();
- return new SecretKeySpec(aes.Key, "AES");
- }
+ /* Code from StackOverflow no. 18091460 */
- public static byte[] getServerHash(String toencode, PublicKey par1PublicKey, SecretKey par2SecretKey)
- {
- return digest("SHA-1", new byte[][] { Encoding.GetEncoding("iso-8859-1").GetBytes(toencode), par2SecretKey.getEncoded(), par1PublicKey.getEncoded() });
- }
+ byte[] SeqOID = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 };
- public static byte[] Encrypt(Key par0Key, byte[] par1ArrayOfByte)
- {
- return func_75885_a(1, par0Key, par1ArrayOfByte);
- }
+ System.IO.MemoryStream ms = new System.IO.MemoryStream(x509key);
+ System.IO.BinaryReader reader = new System.IO.BinaryReader(ms);
- private static byte[] digest(String par0Str, byte[][] par1ArrayOfByte)
- {
- MessageDigest var2 = MessageDigest.getInstance(par0Str);
- byte[][] var3 = par1ArrayOfByte;
- int var4 = par1ArrayOfByte.Length;
+ if (reader.ReadByte() == 0x30)
+ ReadASNLength(reader); //skip the size
+ else
+ return null;
- for (int var5 = 0; var5 < var4; ++var5)
+ int identifierSize = 0; //total length of Object Identifier section
+ if (reader.ReadByte() == 0x30)
+ identifierSize = ReadASNLength(reader);
+ else
+ return null;
+
+ if (reader.ReadByte() == 0x06) //is the next element an object identifier?
{
- byte[] var6 = var3[var5];
- var2.update(var6);
+ int oidLength = ReadASNLength(reader);
+ byte[] oidBytes = new byte[oidLength];
+ reader.Read(oidBytes, 0, oidBytes.Length);
+ if (oidBytes.SequenceEqual(SeqOID) == false) //is the object identifier rsaEncryption PKCS#1?
+ return null;
+
+ int remainingBytes = identifierSize - 2 - oidBytes.Length;
+ reader.ReadBytes(remainingBytes);
}
- return var2.digest();
- }
- private static byte[] func_75885_a(int par0, Key par1Key, byte[] par2ArrayOfByte)
- {
- try
+ if (reader.ReadByte() == 0x03) //is the next element a bit string?
{
- return cypherencrypt(par0, par1Key.getAlgorithm(), par1Key).doFinal(par2ArrayOfByte);
- }
- catch (IllegalBlockSizeException var4)
- {
- var4.printStackTrace();
- }
- catch (BadPaddingException var5)
- {
- var5.printStackTrace();
- }
+ ReadASNLength(reader); //skip the size
+ reader.ReadByte(); //skip unused bits indicator
+ if (reader.ReadByte() == 0x30)
+ {
+ ReadASNLength(reader); //skip the size
+ if (reader.ReadByte() == 0x02) //is it an integer?
+ {
+ int modulusSize = ReadASNLength(reader);
+ byte[] modulus = new byte[modulusSize];
+ reader.Read(modulus, 0, modulus.Length);
+ if (modulus[0] == 0x00) //strip off the first byte if it's 0
+ {
+ byte[] tempModulus = new byte[modulus.Length - 1];
+ Array.Copy(modulus, 1, tempModulus, 0, modulus.Length - 1);
+ modulus = tempModulus;
+ }
- Console.Error.WriteLine("Cipher data failed!");
+ if (reader.ReadByte() == 0x02) //is it an integer?
+ {
+ int exponentSize = ReadASNLength(reader);
+ byte[] exponent = new byte[exponentSize];
+ reader.Read(exponent, 0, exponent.Length);
+
+ RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
+ RSAParameters RSAKeyInfo = new RSAParameters();
+ RSAKeyInfo.Modulus = modulus;
+ RSAKeyInfo.Exponent = exponent;
+ RSA.ImportParameters(RSAKeyInfo);
+ return RSA;
+ }
+ }
+ }
+ }
return null;
}
- private static Cipher cypherencrypt(int par0, String par1Str, Key par2Key)
- {
- try
- {
- Cipher var3 = Cipher.getInstance(par1Str);
- var3.init(par0, par2Key);
- return var3;
- }
- catch (InvalidKeyException var4)
- {
- var4.printStackTrace();
- }
- catch (NoSuchAlgorithmException var5)
- {
- var5.printStackTrace();
- }
- catch (NoSuchPaddingException var6)
- {
- var6.printStackTrace();
- }
-
- Console.Error.WriteLine("Cipher creation failed!");
- return null;
- }
-
- public static AesStream SwitchToAesMode(System.IO.Stream stream, Key key)
- {
- return new AesStream(stream, key.getEncoded());
- }
///
- /// An encrypted stream using AES
+ /// Subfunction for decrypting ASN.1 (x509) RSA certificate data fields lengths
+ ///
+ /// StreamReader containing the stream to decode
+ /// Return the read length
+
+ private static int ReadASNLength(System.IO.BinaryReader reader)
+ {
+ //Note: this method only reads lengths up to 4 bytes long as
+ //this is satisfactory for the majority of situations.
+ int length = reader.ReadByte();
+ if ((length & 0x00000080) == 0x00000080) //is the length greater than 1 byte
+ {
+ int count = length & 0x0000000f;
+ byte[] lengthBytes = new byte[4];
+ reader.Read(lengthBytes, 4 - count, count);
+ Array.Reverse(lengthBytes); //
+ length = BitConverter.ToInt32(lengthBytes, 0);
+ }
+ return length;
+ }
+
+ ///
+ /// Generate a new random AES key for symmetric encryption
+ ///
+ /// Returns a byte array containing the key
+
+ public static byte[] GenerateAESPrivateKey()
+ {
+ AesManaged AES = new AesManaged();
+ AES.KeySize = 128; AES.GenerateKey();
+ return AES.Key;
+ }
+
+ ///
+ /// Get a SHA-1 hash for online-mode session checking
+ ///
+ /// Server ID hash
+ /// Server's RSA key
+ /// Secret key chosen by the client
+ /// Returns the corresponding SHA-1 hex hash
+
+ public static string getServerHash(string serverID, byte[] PublicKey, byte[] SecretKey)
+ {
+ byte[] hash = digest(new byte[][] { Encoding.GetEncoding("iso-8859-1").GetBytes(serverID), SecretKey, PublicKey });
+ bool negative = (hash[0] & 0x80) == 0x80;
+ if (negative) { hash = TwosComplementLittleEndian(hash); }
+ string result = GetHexString(hash).TrimStart('0');
+ if (negative) { result = "-" + result; }
+ return result;
+ }
+
+ ///
+ /// Generate a SHA-1 hash using several byte arrays
+ ///
+ /// array of byte arrays to hash
+ /// Returns the hashed data
+
+ private static byte[] digest(byte[][] tohash)
+ {
+ SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
+ for (int i = 0; i < tohash.Length; i++)
+ sha1.TransformBlock(tohash[i], 0, tohash[i].Length, tohash[i], 0);
+ sha1.TransformFinalBlock(new byte[] { }, 0, 0);
+ return sha1.Hash;
+ }
+
+ ///
+ /// Converts a byte array to its hex string representation
+ ///
+ /// Byte array to convert
+ /// Returns the string representation
+
+ private static string GetHexString(byte[] p)
+ {
+ string result = string.Empty;
+ for (int i = 0; i < p.Length; i++)
+ result += p[i].ToString("x2");
+ return result;
+ }
+
+ ///
+ /// Compute the two's complement of a little endian byte array
+ ///
+ /// Byte array to compute
+ /// Returns the corresponding two's complement
+
+ private static byte[] TwosComplementLittleEndian(byte[] p)
+ {
+ int i;
+ bool carry = true;
+ for (i = p.Length - 1; i >= 0; i--)
+ {
+ p[i] = (byte)~p[i];
+ if (carry)
+ {
+ carry = p[i] == 0xFF;
+ p[i]++;
+ }
+ }
+ return p;
+ }
+
+ ///
+ /// Interface for AES stream
+ /// Allows to use any object which has a Read() and Write() method.
///
- public class AesStream : System.IO.Stream
+ public interface IAesStream
+ {
+ int Read(byte[] buffer, int offset, int count);
+ void Write(byte[] buffer, int offset, int count);
+ }
+
+ ///
+ /// An encrypted stream using AES, used for encrypting network data on the fly using AES.
+ /// This is the regular AesStream class used with the regular .NET framework from Microsoft.
+ ///
+
+ public class AesStream : System.IO.Stream, IAesStream
{
CryptoStream enc;
CryptoStream dec;
@@ -197,5 +298,143 @@ namespace MinecraftClient
return cipher;
}
}
+
+ ///
+ /// An encrypted stream using AES, used for encrypting network data on the fly using AES.
+ /// This is a mono-compatible adaptation which only sends and receive 16 bytes at a time, and manually transforms blocks.
+ /// Data is cached before reaching the 128bits block size necessary for mono which is not CFB-8 compatible.
+ ///
+
+ public class MonoAesStream : System.IO.Stream, IAesStream
+ {
+ ICryptoTransform enc;
+ ICryptoTransform dec;
+ List dec_cache = new List();
+ List tosend_cache = new List();
+ public MonoAesStream(System.IO.Stream stream, byte[] key)
+ {
+ BaseStream = stream;
+ RijndaelManaged aes = GenerateAES(key);
+ enc = aes.CreateEncryptor();
+ dec = aes.CreateDecryptor();
+ }
+ public System.IO.Stream BaseStream { get; set; }
+
+ public override bool CanRead
+ {
+ get { return true; }
+ }
+
+ public override bool CanSeek
+ {
+ get { return false; }
+ }
+
+ public override bool CanWrite
+ {
+ get { return true; }
+ }
+
+ public override void Flush()
+ {
+ BaseStream.Flush();
+ }
+
+ public override long Length
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ public override long Position
+ {
+ get
+ {
+ throw new NotSupportedException();
+ }
+ set
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ public override int ReadByte()
+ {
+ byte[] temp = new byte[1];
+ Read(temp, 0, 1);
+ return temp[0];
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ while (dec_cache.Count < count)
+ {
+ byte[] temp_in = new byte[16];
+ byte[] temp_out = new byte[16];
+ int read = 0;
+ while (read < 16)
+ read += BaseStream.Read(temp_in, read, 16 - read);
+ dec.TransformBlock(temp_in, 0, 16, temp_out, 0);
+ foreach (byte b in temp_out)
+ dec_cache.Add(b);
+ }
+
+ for (int i = offset; i - offset < count; i++)
+ {
+ buffer[i] = dec_cache[0];
+ dec_cache.RemoveAt(0);
+ }
+
+ return count;
+ }
+
+ public override long Seek(long offset, System.IO.SeekOrigin origin)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void WriteByte(byte b)
+ {
+ Write(new byte[] { b }, 0, 1);
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ for (int i = offset; i - offset < count; i++)
+ tosend_cache.Add(buffer[i]);
+
+ if (tosend_cache.Count < 16)
+ tosend_cache.AddRange(MinecraftCom.getPaddingPacket());
+
+ while (tosend_cache.Count > 16)
+ {
+ byte[] temp_in = new byte[16];
+ byte[] temp_out = new byte[16];
+ for (int i = 0; i < 16; i++)
+ {
+ temp_in[i] = tosend_cache[0];
+ tosend_cache.RemoveAt(0);
+ }
+ enc.TransformBlock(temp_in, 0, 16, temp_out, 0);
+ BaseStream.Write(temp_out, 0, 16);
+ }
+ }
+
+ private RijndaelManaged GenerateAES(byte[] key)
+ {
+ RijndaelManaged cipher = new RijndaelManaged();
+ cipher.Mode = CipherMode.CFB;
+ cipher.Padding = PaddingMode.None;
+ cipher.KeySize = 128;
+ cipher.FeedbackSize = 8;
+ cipher.Key = key;
+ cipher.IV = key;
+ return cipher;
+ }
+ }
}
}
diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj
index 48af6f73..384513bd 100644
--- a/MinecraftClient/MinecraftClient.csproj
+++ b/MinecraftClient/MinecraftClient.csproj
@@ -60,18 +60,6 @@
-
- False
- .\IKVM.OpenJDK.Core.dll
-
-
- False
- .\IKVM.OpenJDK.Security.dll
-
-
- False
- .\IKVM.OpenJDK.Util.dll
-
@@ -116,10 +104,6 @@
-
-
-
-