// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 using System; using System.Drawing; using System.Globalization; using ClassicalSharp.Model; using OpenTK; using OpenTK.Input; #if ANDROID using Android.Graphics; using AndroidColor = Android.Graphics.Color; #endif namespace ClassicalSharp { // NOTE: These delegates should be removed when using versions later than NET 2.0. // ################################################################ public delegate void Action(); public delegate void Action(T1 arg1, T2 arg2); public delegate void Action(T1 arg1, T2 arg2, T3 arg3); public delegate void Action(T1 arg1, T2 arg2, T3 arg3, T4 arg4); public delegate TResult Func(); public delegate TResult Func(T1 arg1); public delegate TResult Func(T1 arg1, T2 arg2); public delegate TResult Func(T1 arg1, T2 arg2, T3 arg3); // ################################################################ public static partial class Utils { public const int StringLength = 64; /// Returns a string with all the colour codes stripped from it. public static string StripColours(string value) { if (value.IndexOf('&') == -1) return value; char[] output = new char[value.Length]; int usedChars = 0; for (int i = 0; i < value.Length; i++) { char token = value[i]; if (token == '&') { i++; // Skip over the following colour code. } else { output[usedChars++] = token; } } return new String(output, 0, usedChars); } /// Returns a string with a + removed if it is the last character in the string. public static string RemoveEndPlus(string value) { // Workaround for MCDzienny (and others) use a '+' at the end to distinguish classicube.net accounts // from minecraft.net accounts. Unfortunately they also send this ending + to the client. if (String.IsNullOrEmpty(value)) return value; return value[value.Length - 1] == '+' ? value.Substring(0, value.Length - 1) : value; } const StringComparison comp = StringComparison.OrdinalIgnoreCase; /// Returns whether a equals b, ignoring any case differences. public static bool CaselessEquals(string a, string b) { return a.Equals(b, comp); } /// Returns whether a starts with b, ignoring any case differences. public static bool CaselessStarts(string a, string b) { return a.StartsWith(b, comp); } /// Returns whether a ends with b, ignoring any case differences. public static bool CaselessEnds(string a, string b) { return a.EndsWith(b, comp); } /// Converts the given byte array of length N to a hex string of length 2N. public static string ToHexString(byte[] array) { int len = array.Length; char[] hex = new char[len * 2]; for (int i = 0; i < array.Length; i++) { int value = array[i]; int hi = value >> 4, lo = value & 0x0F; // 48 = index of 0, 55 = index of (A - 10). hex[i * 2 + 0] = hi < 10 ? (char)(hi + 48) : (char)(hi + 55); hex[i * 2 + 1] = lo < 10 ? (char)(lo + 48) : (char)(lo + 55); } return new String(hex); } /// Returns the hex code represented by the given character. /// Throws FormatException if the input character isn't a hex code. public static int ParseHex(char value) { int hex; if (!TryParseHex(value, out hex)) throw new FormatException("Invalid hex code given: " + value); return hex; } /// Attempts to return the hex code represented by the given character. public static bool TryParseHex(char value, out int hex) { hex = 0; if (value >= '0' && value <= '9') { hex = (int)(value - '0'); } else if (value >= 'a' && value <= 'f') { hex = (int)(value - 'a') + 10; } else if (value >= 'A' && value <= 'F') { hex = (int)(value - 'A') + 10; } else { return false; } return true; } /// Attempts to caselessly parse the given string as a Key enum member, /// returning defValue if there was an error parsing. public static bool TryParseEnum(string value, T defValue, out T result) { T mapping; try { mapping = (T)Enum.Parse(typeof(T), value, true); } catch (ArgumentException) { result = defValue; return false; } result = mapping; return true; } public static void LogDebug(string text) { Console.WriteLine(text); } public static void LogDebug(string text, params object[] args) { Console.WriteLine(String.Format(text, args)); } public static int AdjViewDist(float value) { return (int)(1.4142135 * value); } /// Returns the number of vertices needed to subdivide a quad. public static int CountVertices(int axis1Len, int axis2Len, int axisSize) { return CeilDiv(axis1Len, axisSize) * CeilDiv(axis2Len, axisSize) * 4; } public static byte FastByte(string s) { int sum = 0; switch (s.Length) { case 1: sum = (s[0] - '0'); break; case 2: sum = (s[0] - '0') * 10 + (s[1] - '0'); break; case 3: sum = (s[0] - '0') * 100 + (s[1] - '0') * 10 + (s[2] - '0'); break; } return (byte)sum; } /// Determines the skin type of the specified bitmap. public static SkinType GetSkinType(Bitmap bmp) { if (bmp.Width == bmp.Height * 2) { return SkinType.Type64x32; } else if (bmp.Width == bmp.Height) { // Minecraft alex skins have this particular pixel with alpha of 0. int scale = bmp.Width / 64; #if !ANDROID int alpha = bmp.GetPixel(54 * scale, 20 * scale).A; #else int alpha = AndroidColor.GetAlphaComponent(bmp.GetPixel(54 * scale, 20 * scale)); #endif return alpha >= 127 ? SkinType.Type64x64 : SkinType.Type64x64Slim; } return SkinType.Invalid; } /// Returns whether the specified string starts with http:// or https:// public static bool IsUrlPrefix(string value, int index) { int http = value.IndexOf("http://", index); int https = value.IndexOf("https://", index); return http == index || https == index; } /// Conversion for code page 437 characters from index 0 to 31 to unicode. public const string ControlCharReplacements = "\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼"; /// Conversion for code page 437 characters from index 127 to 255 to unicode. public const string ExtendedCharReplacements = "⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»" + "░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌" + "█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\u00a0"; public static bool IsValidInputChar(char c, Game game) { if (c >= ' ' && c <= '~') return true; // ascii bool isCP437 = Utils.ControlCharReplacements.IndexOf(c) >= 0 || Utils.ExtendedCharReplacements.IndexOf(c) >= 0; bool supportsCP437 = game.Server.SupportsFullCP437; return supportsCP437 && isCP437; } public unsafe static string ToLower(string value) { fixed(char* ptr = value) { for (int i = 0; i < value.Length; i++) { char c = ptr[i]; if (c < 'A' || c > 'Z') continue; c += ' '; ptr[i] = c; } } return value; } // Not all languages use . as their decimal point separator public static bool TryParseDecimal(string s, out float result) { if (s.IndexOf(',') >= 0) s = s.Replace(',', '.'); float temp; result = 0; if (!Single.TryParse(s, style, NumberFormatInfo.InvariantInfo, out temp)) return false; if (Single.IsInfinity(temp) || Single.IsNaN(temp)) return false; result = temp; return true; } public static float ParseDecimal(string s) { if (s.IndexOf(',') >= 0) s = s.Replace(',', '.'); return Single.Parse(s, style, NumberFormatInfo.InvariantInfo); } const NumberStyles style = NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite | NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint; } }