- 论坛徽章:
- 0
|
CS 1.6 服务器信息读取
最近在做一个CS约战平台的东西,实现类似 HLSW 读取CS服务器信息的工具。研究了一下CS1.6服务器udp协议的东西。分享一下。
对UDP协议不熟悉,可能有错误的地方请大家指点。
这个是协议的的全文
http://developer.valvesoftware.com/wiki/Server_Queries
协议提供了 5 个请求:
Java代码- 1. * The server responds to 5 queries:
- 2. * A2A_PING
- 3. * Ping the server.
- 4.// ping 服务器
- 5. * A2S_SERVERQUERY_GETCHALLENGE
- 6. * Returns a challenge number for use in the player and rules query.
- 7.//challenge number 应该是用于表示一个用户请求。
- 8. * A2S_INFO
- 9. * Basic information about the server.
- 10.//服务器信息
- 11. * A2S_PLAYER
- 12. * Details about each player on the server.
- 13.//列出服务器所有用户
- 14. * A2S_RULES
- 15. * The rules the server is using.
- 16.//服务器的规则
- 17. * Queries should be sent in UDP packets to the listen port of the server, which is typically port 27015.
- 18.//UDP 的数据包,服务器默认端口是 27015
- * The server responds to 5 queries:
- * A2A_PING
- * Ping the server.
- // ping 服务器
- * A2S_SERVERQUERY_GETCHALLENGE
- * Returns a challenge number for use in the player and rules query.
- //challenge number 应该是用于表示一个用户请求。
- * A2S_INFO
- * Basic information about the server.
- //服务器信息
- * A2S_PLAYER
- * Details about each player on the server.
- //列出服务器所有用户
- * A2S_RULES
- * The rules the server is using.
- //服务器的规则
- * Queries should be sent in UDP packets to the listen port of the server, which is typically port 27015.
- //UDP 的数据包,服务器默认端口是 27015
复制代码 五个请求的内容是:
Java代码- 1.private static final byte[] A2S_INFO_BYTE =
- 2. new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
- 3. 0x54,//T
- 4. 'S', 'o', 'u', 'r', 'c', 'e', ' ', 'E', 'n', 'g', 'i', 'n', 'e', ' ', 'Q', 'u', 'e', 'r', 'y',
- 5. 0x00};
- 6.
- 7.private static final byte[] A2S_PLAYER_BYTE =
- 8. new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
- 9. 0x55,//U
- 10. (byte) 0xFF,(byte) 0xFF,(byte) 0xFF,(byte) 0xFF,//四位 0xFF 可能被替换为challenge number
- 11. 0x00};
- 12.
- 13.private static final byte[] A2S_RULES_BYTE =
- 14. new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
- 15. 0x56,//V
- 16. (byte) 0xFF,(byte) 0xFF,(byte) 0xFF,(byte) 0xFF,//四位 0xFF 可能被替换为challenge number
- 17. 0x00};
- 18.
- 19.private static final byte[] A2S_SERVERQUERY_GETCHALLENGE_BYTE =
- 20. new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
- 21. 0x57,//W
- 22. 0x00};
- 23.
- 24.private static final byte[] A2A_PING_BYTE =
- 25. new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
- 26. 0x69,//i
- 27. 0x00};
- private static final byte[] A2S_INFO_BYTE =
- new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
- 0x54,//T
- 'S', 'o', 'u', 'r', 'c', 'e', ' ', 'E', 'n', 'g', 'i', 'n', 'e', ' ', 'Q', 'u', 'e', 'r', 'y',
- 0x00};
- private static final byte[] A2S_PLAYER_BYTE =
- new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
- 0x55,//U
- (byte) 0xFF,(byte) 0xFF,(byte) 0xFF,(byte) 0xFF,//四位 0xFF 可能被替换为challenge number
- 0x00};
- private static final byte[] A2S_RULES_BYTE =
- new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
- 0x56,//V
- (byte) 0xFF,(byte) 0xFF,(byte) 0xFF,(byte) 0xFF,//四位 0xFF 可能被替换为challenge number
- 0x00};
- private static final byte[] A2S_SERVERQUERY_GETCHALLENGE_BYTE =
- new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
- 0x57,//W
- 0x00};
-
- private static final byte[] A2A_PING_BYTE =
- new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
- 0x69,//i
- 0x00};
复制代码 服务器返回的数据包格式:
数据包除去IP/UDP 头部,最大长度为 1400 bytes。
如果单个数据包传输数据则开始的四个字节是 -1 (0xFF,0xFF,0xFF,0xFF)后面是数据。
如果多个数据包传输数据则开始的四个字节是 -2 (0xFE,0xFF,0xFF,0xFF),后面是四个字节的Request ID(对一次请求返回的多个数据包是相同的且唯一),之后是一个字节的数据包大小和编号,低位的4bit是表示请求返回的包的总数,高位的4bit是表示当前包的编号(从零开始)(协议中说 Source Engine 引擎,会用两个字节来标识,但未发现这种服务器,而且后面有两个字节的 Split Length 也没有发现)。第0个数据包开头有四个字节 (0xFF,0xFF,0xFF,0xFF),其他数据包没有。
Java代码- 1.socket.receive(datapack);
- 2.Frame f = new Frame(HelpUtil.getSubBytes(datapack.getData(), 0, datapack.getLength()));
- 3.if(f.isHasNext()){
- 4. log.debug("has next!");
- 5. //if(type == SOURCE_OR_SHIP_SERVERS) throw new UnsupportedOperationException(" do not have implient for source agent! ");
- 6. byte[][] bts = new byte[f.getDataLength()][];
- 7. bts[f.getIndex()] = f.getData();
- 8. for(int i=1; i < bts.length; i++){
- 9. socket.receive(datapack);
- 10. Frame next = new Frame(HelpUtil.
- 11. getSubBytes(datapack.getData(), 0, datapack.getLength()));
- 12. if(!next.isHasNext() || next.getId() != f.getId()){
- 13. i--;
- 14. log.warn("one package frame is discard!");
- 15. //不应该丢弃包,应该根据第一个字节判断返回的类型。
- 16. continue;
- 17. }else{
- 18. bts[next.getIndex()] = next.getData();
- 19. if(i >= bts.length ) break;
- 20. }
- 21. }
- 22. ByteArrayOutputStream array = new ByteArrayOutputStream();
- 23. for(int i=0; i<bts.length; i++){
- 24. array.write(bts[i]);
- 25. }
- socket.receive(datapack);
- Frame f = new Frame(HelpUtil.getSubBytes(datapack.getData(), 0, datapack.getLength()));
- if(f.isHasNext()){
- log.debug("has next!");
- //if(type == SOURCE_OR_SHIP_SERVERS) throw new UnsupportedOperationException(" do not have implient for source agent! ");
- byte[][] bts = new byte[f.getDataLength()][];
- bts[f.getIndex()] = f.getData();
- for(int i=1; i < bts.length; i++){
- socket.receive(datapack);
- Frame next = new Frame(HelpUtil.
- getSubBytes(datapack.getData(), 0, datapack.getLength()));
- if(!next.isHasNext() || next.getId() != f.getId()){
- i--;
- log.warn("one package frame is discard!");
- //不应该丢弃包,应该根据第一个字节判断返回的类型。
- continue;
- }else{
- bts[next.getIndex()] = next.getData();
- if(i >= bts.length ) break;
- }
- }
- ByteArrayOutputStream array = new ByteArrayOutputStream();
- for(int i=0; i<bts.length; i++){
- array.write(bts[i]);
- }
复制代码 服务器返回的数据起始的一个字节都是标识位(除去开头的 0xFF,0xFF,0xFF,0xFF),这个是后来才发现的,所以现在的设计上有很大问题。如:ping 第一个字节是 'j',A2S_PLAYER 是 'D' ......
如果服务器返回的数据中是字符串 则是以 UTF-8 编码, 并且以 0x00 做为字符串结尾标识。
•A2A_PING 请求:
用户发现服务器是不是可以连接(或检查服务器类型)。
第一个字节 是 'j'(0x6A)
Goldsource servers 引擎的服务器返回
Java代码- 1.0x6A,0x00
- 0x6A,0x00
- Source servers 引擎的服务器返回(这种服务器暂时未发现)
- Java代码
- [code]1.0x6A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
- 0x6A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
复制代码 •A2S_SERVERQUERY_GETCHALLENGE [/code]请求:
第一个字节 是 'A' (0x41)
接下来是 4 个字节的 challenge number 用于 A2S_PLAYER 和 A2S_RULES 这个请求的时候替换请求的后四个字节。
很多服务器不支持该请求。
•A2S_INFO 请求:
区分两种服务器
第一个字节分别是 'I','m'(貌似区分是正版服务器,还是盗版服务器,这个不确定)
m 类型:
Java代码- 1. int i = 0, t;
- 2. info.setType(bts[i]); //m / I
- 3. t = i + 1;
- 4.if(info.getType() == 'm'){
- 5. i = HelpUtil.find(bts, t, (byte) 0x00);//ip:port
- 6. t = i + 1;
- 7. i = HelpUtil.find(bts, t, (byte) 0x00);//server name
- 8. if(i >= 0){
- 9. info.setName(new String(HelpUtil.getSubBytes(bts, t, i-t), DEFAULT_CHARSET));
- 10. }
- 11. t = i + 1;
- 12. i = HelpUtil.find(bts, t, (byte) 0x00);//map
- 13. if(i >= 0){
- 14. info.setMap(new String(HelpUtil.getSubBytes(bts, t, i-t), DEFAULT_CHARSET));
- 15. }
- 16. t = i + 1;
- 17. i = HelpUtil.find(bts, t, (byte) 0x00);//Game Directory
- 18. t = i + 1;
- 19. i = HelpUtil.find(bts, t, (byte) 0x00);//Game Description
- 20. if(i >= 0){
- 21. info.setDescription(new String(HelpUtil.getSubBytes(bts, t, i-t), DEFAULT_CHARSET));
- 22. }
- 23. t = i + 1;
- 24. info.setCurrentPlayers(bts[t]);
- 25. t += 1;
- 26. info.setMaxPlayers(bts[t]);
- 27. t += 1; //version
- 28. t += 1; //Dedicated
- 29. t += 1; //OS
- 30. info.setNeedPassword(bts[t++] != 0);//Password
- 31. t += 1; //IsMod
- 32. i = t;
- 33. if(bts[t] == 0x01){
- 34. t = i + 1;
- 35. i = HelpUtil.find(bts, t, (byte) 0x00); //URLInfo
- 36. t = i + 1;
- 37. i = HelpUtil.find(bts, t, (byte) 0x00); //URLDL
- 38. t = i + 1; //Nul
- 39. t += 4; //ModVersion
- 40. t += 4; //ModSize
- 41. t += 1; //SvOnly
- 42. t += 1; //ClDLL
- 43. }
- 44. t += 1; //Secure
- 45. t += 1;//Number of bots
- 46. byte end = bts[t];//不应该溢出
- 47.}
- int i = 0, t;
- info.setType(bts[i]); //m / I
- t = i + 1;
- if(info.getType() == 'm'){
- i = HelpUtil.find(bts, t, (byte) 0x00);//ip:port
- t = i + 1;
- i = HelpUtil.find(bts, t, (byte) 0x00);//server name
- if(i >= 0){
- info.setName(new String(HelpUtil.getSubBytes(bts, t, i-t), DEFAULT_CHARSET));
- }
- t = i + 1;
- i = HelpUtil.find(bts, t, (byte) 0x00);//map
- if(i >= 0){
- info.setMap(new String(HelpUtil.getSubBytes(bts, t, i-t), DEFAULT_CHARSET));
- }
- t = i + 1;
- i = HelpUtil.find(bts, t, (byte) 0x00);//Game Directory
- t = i + 1;
- i = HelpUtil.find(bts, t, (byte) 0x00);//Game Description
- if(i >= 0){
- info.setDescription(new String(HelpUtil.getSubBytes(bts, t, i-t), DEFAULT_CHARSET));
- }
- t = i + 1;
- info.setCurrentPlayers(bts[t]);
- t += 1;
- info.setMaxPlayers(bts[t]);
- t += 1; //version
- t += 1; //Dedicated
- t += 1; //OS
- info.setNeedPassword(bts[t++] != 0);//Password
- t += 1; //IsMod
- i = t;
- if(bts[t] == 0x01){
- t = i + 1;
- i = HelpUtil.find(bts, t, (byte) 0x00); //URLInfo
- t = i + 1;
- i = HelpUtil.find(bts, t, (byte) 0x00); //URLDL
- t = i + 1; //Nul
- t += 4; //ModVersion
- t += 4; //ModSize
- t += 1; //SvOnly
- t += 1; //ClDLL
- }
- t += 1; //Secure
- t += 1;//Number of bots
- byte end = bts[t];//不应该溢出
- }
复制代码 对于'I'类型
Java代码- 1.if(info.type == 'I'){
- 2. int version = bts[t++];//version
- 3. i = HelpUtil.find(bts, t, (byte) 0x00);//server name
- 4. if(i >= 0){
- 5. info.setName(new String(HelpUtil.getSubBytes(bts, t, i-t), DEFAULT_CHARSET));
- 6. }
- 7. t = i + 1;
- 8. i = HelpUtil.find(bts, t, (byte) 0x00);//Map
- 9. if(i >= 0){
- 10. info.setMap(new String(HelpUtil.getSubBytes(bts, t, i-t), DEFAULT_CHARSET));
- 11. }
- 12. t = i + 1;
- 13. i = HelpUtil.find(bts, t, (byte) 0x00);//Map
- 14. if(i >= 0){//Game Directory
- 15. //info.setMap(new String(HelpUtil.getSubBytes(bts, t, i-t), DEFAULT_CHARSET));
- 16. }
- 17. t = i + 1;
- 18. i = HelpUtil.find(bts, t, (byte) 0x00);//Map
- 19. if(i >= 0){//Game Description
- 20. info.setDescription(new String(HelpUtil.getSubBytes(bts, t, i-t), DEFAULT_CHARSET));
- 21. }
- 22. t = i + 1;
- 23. HelpUtil.toShort(bts[t++],bts[t++]); //AppID
- 24. info.setCurrentPlayers(bts[t++]);
- 25. info.setMaxPlayers(bts[t++]);
- 26. t++; //Number of bots
- 27. t++; //Dedicated byte 'l' for listen, 'd' for dedicated, 'p' for SourceTV
- 28. t++; //Host operating system. 'l' for Linux, 'w' for Windows
- 29. t++; //Password
- 30. t++; //Secure
- 31.
- 32. //后面的数据不清楚做什么用的 所以忽略掉了。
- 33.}else{
- 34. throw new RuntimeException("donot support type [" + info.type + "]");
- 35.}
- if(info.type == 'I'){
- int version = bts[t++];//version
- i = HelpUtil.find(bts, t, (byte) 0x00);//server name
- if(i >= 0){
- info.setName(new String(HelpUtil.getSubBytes(bts, t, i-t), DEFAULT_CHARSET));
- }
- t = i + 1;
- i = HelpUtil.find(bts, t, (byte) 0x00);//Map
- if(i >= 0){
- info.setMap(new String(HelpUtil.getSubBytes(bts, t, i-t), DEFAULT_CHARSET));
- }
- t = i + 1;
- i = HelpUtil.find(bts, t, (byte) 0x00);//Map
- if(i >= 0){//Game Directory
- //info.setMap(new String(HelpUtil.getSubBytes(bts, t, i-t), DEFAULT_CHARSET));
- }
- t = i + 1;
- i = HelpUtil.find(bts, t, (byte) 0x00);//Map
- if(i >= 0){//Game Description
- info.setDescription(new String(HelpUtil.getSubBytes(bts, t, i-t), DEFAULT_CHARSET));
- }
- t = i + 1;
- HelpUtil.toShort(bts[t++],bts[t++]); //AppID
- info.setCurrentPlayers(bts[t++]);
- info.setMaxPlayers(bts[t++]);
- t++; //Number of bots
- t++; //Dedicated byte 'l' for listen, 'd' for dedicated, 'p' for SourceTV
- t++; //Host operating system. 'l' for Linux, 'w' for Windows
- t++; //Password
- t++; //Secure
-
- //后面的数据不清楚做什么用的 所以忽略掉了。
- }else{
- throw new RuntimeException("donot support type [" + info.type + "]");
- }
复制代码 •A2S_PLAYER 请求:
玩家的列表,名字,得分,在线时间
Java代码- 1.int t = 0;
- 2.byte type = bts[t++];//type Should be equal to 'D' (0x44)
- 3.int len = bts[t++];
- 4.for(int i=0; i<len; i++){
- 5. Player p = new Player();
- 6. p.setIndex(bts[t++]);//序号
- 7. int u = HelpUtil.find(bts, t, (byte) 0x00);
- 8. p.setName(new String(HelpUtil.getSubBytes(bts, t, u - t), DEFAULT_CHARSET));//name
- 9. t = u + 1;
- 10. p.setKill(Integer.reverseBytes(HelpUtil.toInt(bts[t++],bts[t++],bts[t++],bts[t++])));
- 11. //Number of kills this player has
- 12. p.setConnectedTime(
- 13. Float.intBitsToFloat(//浮点类型 秒数,不清楚为什么用浮点数
- 14. Integer.reverseBytes(//比较奇怪为什么是反向的字节
- 15. HelpUtil.toInt(bts[t++],bts[t++],bts[t++],bts[t++])))); //(x)
- 16. //The time in seconds this player has been connected
- 17. list.add(p);
- 18.}
- int t = 0;
- byte type = bts[t++];//type Should be equal to 'D' (0x44)
- int len = bts[t++];
- for(int i=0; i<len; i++){
- Player p = new Player();
- p.setIndex(bts[t++]);//序号
- int u = HelpUtil.find(bts, t, (byte) 0x00);
- p.setName(new String(HelpUtil.getSubBytes(bts, t, u - t), DEFAULT_CHARSET));//name
- t = u + 1;
- p.setKill(Integer.reverseBytes(HelpUtil.toInt(bts[t++],bts[t++],bts[t++],bts[t++])));
- //Number of kills this player has
- p.setConnectedTime(
- Float.intBitsToFloat(//浮点类型 秒数,不清楚为什么用浮点数
- Integer.reverseBytes(//比较奇怪为什么是反向的字节
- HelpUtil.toInt(bts[t++],bts[t++],bts[t++],bts[t++])))); //(x)
- //The time in seconds this player has been connected
- list.add(p);
- }
复制代码 •A2S_RULES 请求:
这个结构比较简单 字符串的键值对
Java代码- 1.int t = 0;
- 2.int type = bts[t++];//Should be equal to 'E' (0x45)
- 3.short len = HelpUtil.toShort(bts[t++], bts[t++]);//The number of rules reported in this response
- 4.// top is error!
- 5.for(short i=0; ; i++){
- 6. int u = HelpUtil.find(bts, t, (byte) 0x00);
- 7. if(u == -1) break;
- 8. String key = new String(HelpUtil.getSubBytes(bts, t, u-t));
- 9. t = u + 1;
- 10. u = HelpUtil.find(bts, t, (byte) 0x00);
- 11. String value = new String(HelpUtil.getSubBytes(bts, t, u-t));
- 12. t = u + 1;
- 13. map.put(key, value);
- 14.}
- int t = 0;
- int type = bts[t++];//Should be equal to 'E' (0x45)
- short len = HelpUtil.toShort(bts[t++], bts[t++]);//The number of rules reported in this response
- // top is error!
- for(short i=0; ; i++){
- int u = HelpUtil.find(bts, t, (byte) 0x00);
- if(u == -1) break;
- String key = new String(HelpUtil.getSubBytes(bts, t, u-t));
- t = u + 1;
- u = HelpUtil.find(bts, t, (byte) 0x00);
- String value = new String(HelpUtil.getSubBytes(bts, t, u-t));
- t = u + 1;
- map.put(key, value);
- }
复制代码 源码SVN地址:https://lineblog.googlecode.com/svn/trunk/
目录:httpAnalysis/cs/ 下
转载请保留原文地址: [url=转载请保留原文地址: http://lchshu001.iteye.com/blog/1207956 , 谢谢 ]转载请保留原文地址: http://lchshu001.iteye.com/blog/1207956 , [/url], 谢谢 |
|