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 @@ - - - -