最近在做一个CS约战平台的东西,实现类似 HLSW 读取CS服务器信息的工具。研究了一下CS1.6服务器udp协议的东西。分享一下。
对UDP协议不熟悉,可能有错误的地方请大家指点。
这个是协议的的全文
http://developer.valvesoftware.com/wiki/Server_Queries
协议提供了 5 个请求:
* 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
五个请求的内容是:
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),其他数据包没有。
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 引擎的服务器返回
0x6A,0x00
Source servers 引擎的服务器返回(这种服务器暂时未发现)
0x6A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
A2S_SERVERQUERY_GETCHALLENGE 请求:
第一个字节 是 'A' (0x41)
接下来是 4 个字节的 challenge number 用于 A2S_PLAYER 和 A2S_RULES 这个请求的时候替换请求的后四个字节。
很多服务器不支持该请求。
A2S_INFO 请求:
区分两种服务器
第一个字节分别是 'I','m'(貌似区分是正版服务器,还是盗版服务器,这个不确定)
m 类型:
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'类型
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 请求:
玩家的列表,名字,得分,在线时间
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 请求:
这个结构比较简单 字符串的键值对
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/ 下
[完]
转载请保留原文地址:
http://lchshu001.iteye.com/blog/1207956 , 谢谢
分享到:
相关推荐
本文将重点讨论如何通过源码实现CS 1.6服务器信息的读取,以便于开发自己的约战平台工具。 首先,我们需要了解CS 1.6服务器的信息是如何公开的。游戏服务器通常会监听特定的端口,并对外提供一种称为Source RCON...
4. **兼容性**:由于Rage是针对AMX Mod X开发的,因此它与多种版本的《反恐精英》游戏服务器兼容,包括但不限于CS 1.6、CSS和CS:GO。 5. **社区支持**:有一个活跃的开发者和用户社区围绕AMX Mod X,他们分享代码、...
在现代信息爆炸的时代,分布式数据的快速增长使得快速解析以获取有意义的结果变得至关重要。为了实现这一目标,一个可搜索的分布式数据索引系统是必不可少的。本文将通过代码实例展示如何利用Lucene和Java进行基本的...
它定义了充电站(Charge Point,CP)与充电网络管理系统(Central System,CS)之间的通信标准,用于实现远程监控、配置、诊断和更新等功能。OCPP的目标是促进不同制造商的充电设备间的互操作性,提高充电服务的可靠...
cs.ec DES加密模块 1.0.ec DIY热键框模块.ec DLL注入模块.ec DOS命令模块.ec EC.EC EdbServer1.0客户端.ec EDB、高级表格、XLS互换.ec edb到html-1.0.ec EDB数据库客户端模块 1.0.ec edb数据库转Excel...
XP皮肤1.6.ec XP 选择框1.1.ec xp风格化.ec ZCL_多线程类1.01.ec ZCL_控件类库1.01.ec ZCL_文件读写 1.01.ec ZCL_核库函数1.01.ec zip.ec Z计算器.ec [神2也教你学E] - 可执行动态载入&输出其他文件模块.ec _仿真...
XP皮肤1.6.ec XP 选择框1.1.ec xp风格化.ec ZCL_多线程类1.01.ec ZCL_控件类库1.01.ec ZCL_文件读写 1.01.ec ZCL_核库函数1.01.ec zip.ec Z计算器.ec [神2也教你学E] - 可执行动态载入&输出其他文件模块.ec _仿真...
XP皮肤1.6.ec XP完美模拟专家.EC XP选择框1.1.ec zip压缩.ec 安全关机.ec 保存图片1.0.ec 本土化易模块.ec 保证显示.ec 比较大小.ec 编辑标准格式公文2.0.ec 编辑框辅助功能.ec 编码转换大全.ec 变速...
XP皮肤1.6.ec XP完美模拟专家.EC XP选择框1.1.ec zip压缩.ec 安全关机.ec 保存图片1.0.ec 本土化易模块.ec 保证显示.ec 比较大小.ec 编辑标准格式公文2.0.ec 编辑框辅助功能.ec 编码转换大全.ec 变速...
XP皮肤1.6.ec XP完美模拟专家.EC XP选择框1.1.ec zip压缩.ec 安全关机.ec 保存图片1.0.ec 本土化易模块.ec 保证显示.ec 比较大小.ec 编辑标准格式公文2.0.ec 编辑框辅助功能.ec 编码转换大全.ec 变速...
cs.ec DES加密模块 1.0.ec DIY热键框模块.ec DLL注入模块.ec DOS命令模块.ec E.M.O_11.ec EC.EC ecom.ec EdbServer1.0客户端.ec EDB、高级表格、XLS互换.ec edb到html-1.0.ec EDB数据库客户端模块 1.0.ec edb数据库...
2004-08-11 00:34 1667 598 易语言模块大全\cs.ec 2005-10-21 15:30 4047 1417 易语言模块大全\DES加密模块 1.0.ec 2005-08-06 14:55 12387 3460 易语言模块大全\DIY热键框模块.ec 2005-10-21 15:30 10219 3288 ...
2004-08-11 00:34 1667 598 易语言模块大全\cs.ec 2005-10-21 15:30 4047 1417 易语言模块大全\DES加密模块 1.0.ec 2005-08-06 14:55 12387 3460 易语言模块大全\DIY热键框模块.ec 2005-10-21 15:30 10219 3288 ...
2004-08-11 00:34 1667 598 易语言模块大全\cs.ec 2005-10-21 15:30 4047 1417 易语言模块大全\DES加密模块 1.0.ec 2005-08-06 14:55 12387 3460 易语言模块大全\DIY热键框模块.ec 2005-10-21 15:30 10219 3288 ...
1.6节.在MXML中添加事件监听器 1.7节.设置子节点属性 1.8节.定义数组和对象 1.9节.在ActionScript中设置变量的作用域 1.10节.在ActionScript中创建组件 1.11节.使用事件冒泡机制 1.12节.使用代码隐藏模式分离MXML和...