Fix 1.7+ server list ping by properly parsing Json

Separate Json and ChatParser classes
Use Json parser for retrieving Json fields
Will avoid wrong "name" field from being used
This commit is contained in:
ORelio 2015-06-19 19:29:23 +02:00
parent a7f0897f09
commit 67affc6270
4 changed files with 202 additions and 159 deletions

View file

@ -123,6 +123,7 @@
<Compile Include="Protocol\Handlers\Compression\ZlibBaseStream.cs" /> <Compile Include="Protocol\Handlers\Compression\ZlibBaseStream.cs" />
<Compile Include="Protocol\Handlers\Compression\ZlibCodec.cs" /> <Compile Include="Protocol\Handlers\Compression\ZlibCodec.cs" />
<Compile Include="Protocol\Handlers\Compression\ZlibConstants.cs" /> <Compile Include="Protocol\Handlers\Compression\ZlibConstants.cs" />
<Compile Include="Protocol\Handlers\Json.cs" />
<Compile Include="Protocol\Handlers\ZlibUtils.cs" /> <Compile Include="Protocol\Handlers\ZlibUtils.cs" />
<Compile Include="Protocol\Handlers\ChatParser.cs" /> <Compile Include="Protocol\Handlers\ChatParser.cs" />
<Compile Include="Crypto\IAesStream.cs" /> <Compile Include="Crypto\IAesStream.cs" />

View file

@ -19,31 +19,7 @@ namespace MinecraftClient.Protocol.Handlers
public static string ParseText(string json) public static string ParseText(string json)
{ {
int cursorpos = 0; return JSONData2String(Json.ParseJson(json), "");
JSONData jsonData = String2Data(json, ref cursorpos);
return JSONData2String(jsonData, "");
}
/// <summary>
/// An internal class to store unserialized JSON data
/// The data can be an object, an array or a string
/// </summary>
private class JSONData
{
public enum DataType { Object, Array, String };
private DataType type;
public DataType Type { get { return type; } }
public Dictionary<string, JSONData> Properties;
public List<JSONData> DataArray;
public string StringValue;
public JSONData(DataType datatype)
{
type = datatype;
Properties = new Dictionary<string, JSONData>();
DataArray = new List<JSONData>();
StringValue = String.Empty;
}
} }
/// <summary> /// <summary>
@ -210,135 +186,27 @@ namespace MinecraftClient.Protocol.Handlers
else return "[" + rulename + "] " + String.Join(" ", using_data); else return "[" + rulename + "] " + String.Join(" ", using_data);
} }
/// <summary>
/// Parse a JSON string to build a JSON object
/// </summary>
/// <param name="toparse">String to parse</param>
/// <param name="cursorpos">Cursor start (set to 0 for function init)</param>
/// <returns></returns>
private static JSONData String2Data(string toparse, ref int cursorpos)
{
try
{
JSONData data;
switch (toparse[cursorpos])
{
//Object
case '{':
data = new JSONData(JSONData.DataType.Object);
cursorpos++;
while (toparse[cursorpos] != '}')
{
if (toparse[cursorpos] == '"')
{
JSONData propertyname = String2Data(toparse, ref cursorpos);
if (toparse[cursorpos] == ':') { cursorpos++; } else { /* parse error ? */ }
JSONData propertyData = String2Data(toparse, ref cursorpos);
data.Properties[propertyname.StringValue] = propertyData;
}
else cursorpos++;
}
cursorpos++;
break;
//Array
case '[':
data = new JSONData(JSONData.DataType.Array);
cursorpos++;
while (toparse[cursorpos] != ']')
{
if (toparse[cursorpos] == ',') { cursorpos++; }
JSONData arrayItem = String2Data(toparse, ref cursorpos);
data.DataArray.Add(arrayItem);
}
cursorpos++;
break;
//String
case '"':
data = new JSONData(JSONData.DataType.String);
cursorpos++;
while (toparse[cursorpos] != '"')
{
if (toparse[cursorpos] == '\\')
{
try //Unicode character \u0123
{
if (toparse[cursorpos + 1] == 'u'
&& isHex(toparse[cursorpos + 2])
&& isHex(toparse[cursorpos + 3])
&& isHex(toparse[cursorpos + 4])
&& isHex(toparse[cursorpos + 5]))
{
//"abc\u0123abc" => "0123" => 0123 => Unicode char n°0123 => Add char to string
data.StringValue += char.ConvertFromUtf32(int.Parse(toparse.Substring(cursorpos + 2, 4), System.Globalization.NumberStyles.HexNumber));
cursorpos += 6; continue;
}
else cursorpos++; //Normal character escapement \"
}
catch (IndexOutOfRangeException) { cursorpos++; } // \u01<end of string>
catch (ArgumentOutOfRangeException) { cursorpos++; } // Unicode index 0123 was invalid
}
data.StringValue += toparse[cursorpos];
cursorpos++;
}
cursorpos++;
break;
//Boolean : true
case 't':
data = new JSONData(JSONData.DataType.String);
cursorpos++;
if (toparse[cursorpos] == 'r') { cursorpos++; }
if (toparse[cursorpos] == 'u') { cursorpos++; }
if (toparse[cursorpos] == 'e') { cursorpos++; data.StringValue = "true"; }
break;
//Boolean : false
case 'f':
data = new JSONData(JSONData.DataType.String);
cursorpos++;
if (toparse[cursorpos] == 'a') { cursorpos++; }
if (toparse[cursorpos] == 'l') { cursorpos++; }
if (toparse[cursorpos] == 's') { cursorpos++; }
if (toparse[cursorpos] == 'e') { cursorpos++; data.StringValue = "false"; }
break;
//Unknown data
default:
cursorpos++;
return String2Data(toparse, ref cursorpos);
}
return data;
}
catch (IndexOutOfRangeException)
{
return new JSONData(JSONData.DataType.String);
}
}
/// <summary> /// <summary>
/// Use a JSON Object to build the corresponding string /// Use a JSON Object to build the corresponding string
/// </summary> /// </summary>
/// <param name="data">JSON object to convert</param> /// <param name="data">JSON object to convert</param>
/// <param name="colorcode">Allow parent color code to affect child elements (set to "" for function init)</param> /// <param name="colorcode">Allow parent color code to affect child elements (set to "" for function init)</param>
/// <returns>returns the Minecraft-formatted string</returns> /// <returns>returns the Minecraft-formatted string</returns>
private static string JSONData2String(JSONData data, string colorcode) private static string JSONData2String(Json.JSONData data, string colorcode)
{ {
string extra_result = ""; string extra_result = "";
switch (data.Type) switch (data.Type)
{ {
case JSONData.DataType.Object: case Json.JSONData.DataType.Object:
if (data.Properties.ContainsKey("color")) if (data.Properties.ContainsKey("color"))
{ {
colorcode = color2tag(JSONData2String(data.Properties["color"], "")); colorcode = color2tag(JSONData2String(data.Properties["color"], ""));
} }
if (data.Properties.ContainsKey("extra")) if (data.Properties.ContainsKey("extra"))
{ {
JSONData[] extras = data.Properties["extra"].DataArray.ToArray(); Json.JSONData[] extras = data.Properties["extra"].DataArray.ToArray();
foreach (JSONData item in extras) foreach (Json.JSONData item in extras)
extra_result = extra_result + JSONData2String(item, colorcode) + "§r"; extra_result = extra_result + JSONData2String(item, colorcode) + "§r";
} }
if (data.Properties.ContainsKey("text")) if (data.Properties.ContainsKey("text"))
@ -352,7 +220,7 @@ namespace MinecraftClient.Protocol.Handlers
data.Properties["with"] = data.Properties["using"]; data.Properties["with"] = data.Properties["using"];
if (data.Properties.ContainsKey("with")) if (data.Properties.ContainsKey("with"))
{ {
JSONData[] array = data.Properties["with"].DataArray.ToArray(); Json.JSONData[] array = data.Properties["with"].DataArray.ToArray();
for (int i = 0; i < array.Length; i++) for (int i = 0; i < array.Length; i++)
{ {
using_data.Add(JSONData2String(array[i], colorcode)); using_data.Add(JSONData2String(array[i], colorcode));
@ -362,29 +230,21 @@ namespace MinecraftClient.Protocol.Handlers
} }
else return extra_result; else return extra_result;
case JSONData.DataType.Array: case Json.JSONData.DataType.Array:
string result = ""; string result = "";
foreach (JSONData item in data.DataArray) foreach (Json.JSONData item in data.DataArray)
{ {
result += JSONData2String(item, colorcode); result += JSONData2String(item, colorcode);
} }
return result; return result;
case JSONData.DataType.String: case Json.JSONData.DataType.String:
return colorcode + data.StringValue; return colorcode + data.StringValue;
} }
return ""; return "";
} }
/// <summary>
/// Small function for checking if a char is an hexadecimal char (0-9 A-F a-f)
/// </summary>
/// <param name="c">Char to test</param>
/// <returns>True if hexadecimal</returns>
private static bool isHex(char c) { return ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')); }
/// <summary> /// <summary>
/// Do a HTTP request to get a webpage or text data from a server file /// Do a HTTP request to get a webpage or text data from a server file
/// </summary> /// </summary>

View file

@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Protocol.Handlers
{
/// <summary>
/// This class parses JSON data and returns an object describing that data.
/// Really lightweight JSON handling by ORelio - (c) 2013 - 2014
/// </summary>
static class Json
{
/// <summary>
/// Parse some JSON and return the corresponding JSON object
/// </summary>
public static JSONData ParseJson(string json)
{
int cursorpos = 0;
return String2Data(json, ref cursorpos);
}
/// <summary>
/// The class storing unserialized JSON data
/// The data can be an object, an array or a string
/// </summary>
public class JSONData
{
public enum DataType { Object, Array, String };
private DataType type;
public DataType Type { get { return type; } }
public Dictionary<string, JSONData> Properties;
public List<JSONData> DataArray;
public string StringValue;
public JSONData(DataType datatype)
{
type = datatype;
Properties = new Dictionary<string, JSONData>();
DataArray = new List<JSONData>();
StringValue = String.Empty;
}
}
/// <summary>
/// Parse a JSON string to build a JSON object
/// </summary>
/// <param name="toparse">String to parse</param>
/// <param name="cursorpos">Cursor start (set to 0 for function init)</param>
private static JSONData String2Data(string toparse, ref int cursorpos)
{
try
{
JSONData data;
switch (toparse[cursorpos])
{
//Object
case '{':
data = new JSONData(JSONData.DataType.Object);
cursorpos++;
while (toparse[cursorpos] != '}')
{
if (toparse[cursorpos] == '"')
{
JSONData propertyname = String2Data(toparse, ref cursorpos);
if (toparse[cursorpos] == ':') { cursorpos++; } else { /* parse error ? */ }
JSONData propertyData = String2Data(toparse, ref cursorpos);
data.Properties[propertyname.StringValue] = propertyData;
}
else cursorpos++;
}
cursorpos++;
break;
//Array
case '[':
data = new JSONData(JSONData.DataType.Array);
cursorpos++;
while (toparse[cursorpos] != ']')
{
if (toparse[cursorpos] == ',') { cursorpos++; }
JSONData arrayItem = String2Data(toparse, ref cursorpos);
data.DataArray.Add(arrayItem);
}
cursorpos++;
break;
//String
case '"':
data = new JSONData(JSONData.DataType.String);
cursorpos++;
while (toparse[cursorpos] != '"')
{
if (toparse[cursorpos] == '\\')
{
try //Unicode character \u0123
{
if (toparse[cursorpos + 1] == 'u'
&& isHex(toparse[cursorpos + 2])
&& isHex(toparse[cursorpos + 3])
&& isHex(toparse[cursorpos + 4])
&& isHex(toparse[cursorpos + 5]))
{
//"abc\u0123abc" => "0123" => 0123 => Unicode char n°0123 => Add char to string
data.StringValue += char.ConvertFromUtf32(int.Parse(toparse.Substring(cursorpos + 2, 4), System.Globalization.NumberStyles.HexNumber));
cursorpos += 6; continue;
}
else cursorpos++; //Normal character escapement \"
}
catch (IndexOutOfRangeException) { cursorpos++; } // \u01<end of string>
catch (ArgumentOutOfRangeException) { cursorpos++; } // Unicode index 0123 was invalid
}
data.StringValue += toparse[cursorpos];
cursorpos++;
}
cursorpos++;
break;
//Number
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.':
data = new JSONData(JSONData.DataType.String);
StringBuilder sb = new StringBuilder();
while ((toparse[cursorpos] >= '0' && toparse[cursorpos] <= '9') || toparse[cursorpos] == '.')
{
sb.Append(toparse[cursorpos]);
cursorpos++;
}
data.StringValue = sb.ToString();
break;
//Boolean : true
case 't':
data = new JSONData(JSONData.DataType.String);
cursorpos++;
if (toparse[cursorpos] == 'r') { cursorpos++; }
if (toparse[cursorpos] == 'u') { cursorpos++; }
if (toparse[cursorpos] == 'e') { cursorpos++; data.StringValue = "true"; }
break;
//Boolean : false
case 'f':
data = new JSONData(JSONData.DataType.String);
cursorpos++;
if (toparse[cursorpos] == 'a') { cursorpos++; }
if (toparse[cursorpos] == 'l') { cursorpos++; }
if (toparse[cursorpos] == 's') { cursorpos++; }
if (toparse[cursorpos] == 'e') { cursorpos++; data.StringValue = "false"; }
break;
//Unknown data
default:
cursorpos++;
return String2Data(toparse, ref cursorpos);
}
while (cursorpos < toparse.Length
&& (char.IsWhiteSpace(toparse[cursorpos])
|| toparse[cursorpos] == '\r'
|| toparse[cursorpos] == '\n'))
cursorpos++;
return data;
}
catch (IndexOutOfRangeException)
{
return new JSONData(JSONData.DataType.String);
}
}
/// <summary>
/// Small function for checking if a char is an hexadecimal char (0-9 A-F a-f)
/// </summary>
/// <param name="c">Char to test</param>
/// <returns>True if hexadecimal</returns>
private static bool isHex(char c) { return ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')); }
}
}

View file

@ -580,18 +580,21 @@ namespace MinecraftClient.Protocol.Handlers
if (ComTmp.readNextVarInt() == 0x00) //Read Packet ID if (ComTmp.readNextVarInt() == 0x00) //Read Packet ID
{ {
string result = ComTmp.readNextString(); //Get the Json data string result = ComTmp.readNextString(); //Get the Json data
if (result[0] == '{' && result.Contains("protocol\":") && result.Contains("name\":\"")) if (!String.IsNullOrEmpty(result) && result.StartsWith("{") && result.EndsWith("}"))
{ {
string[] tmp_ver = result.Split(new string[] { "protocol\":" }, StringSplitOptions.None); Json.JSONData jsonData = Json.ParseJson(result);
string[] tmp_name = result.Split(new string[] { "name\":\"" }, StringSplitOptions.None); if (jsonData.Type == Json.JSONData.DataType.Object && jsonData.Properties.ContainsKey("version"))
if (tmp_ver.Length >= 2 && tmp_name.Length >= 2)
{ {
protocolversion = atoi(tmp_ver[1]); jsonData = jsonData.Properties["version"];
//Retrieve display name of the Minecraft version
if (jsonData.Properties.ContainsKey("name"))
version = jsonData.Properties["name"].StringValue;
//Retrieve protocol version number for handling this server
if (jsonData.Properties.ContainsKey("protocol"))
protocolversion = atoi(jsonData.Properties["protocol"].StringValue);
//Handle if "name" exists twice, eg when connecting to a server with another user logged in.
version = (tmp_name.Length == 2) ? tmp_name[1].Split('"')[0] : tmp_name[2].Split('"')[0];
//Automatic fix for BungeeCord 1.8 not properly reporting protocol version //Automatic fix for BungeeCord 1.8 not properly reporting protocol version
if (protocolversion < 47 && version.Split(' ').Contains("1.8")) if (protocolversion < 47 && version.Split(' ').Contains("1.8"))
protocolversion = ProtocolHandler.MCVer2ProtocolVersion("1.8.0"); protocolversion = ProtocolHandler.MCVer2ProtocolVersion("1.8.0");