游戏中最重要的是什么?通信!那么本次我们就来看一下游戏里面的通信。
s2的通信分成了两部分,登陆时通信和游戏时通信,这两部分都是字节通信,即将数据打包成字节数组的形式再发送。
登陆通信和游戏通信两部分协议规则基本相同,不同的是游戏通信多了一个数据加密的过程。
数据通信包含三部分:数据头、数据体和数据加密
数据头
数据头的一个18字节长度的字节数组,包含下面五个部分
类型 | 长度 | 描述 |
---|---|---|
数据长度 | 4 | 需要发送的数据包长度,包括数据头和数据体长度 |
协议号 | 2 | 游戏定义的协议号,不同协议号代表不同的数据体结构 |
用户标识 | 4 | 用户的唯一标识,这里为用户账号 |
消息序码 | 4 | 消息序码,和用户发送数据的顺序有关 |
校验码 | 4 | 消息的校验码,和消息头、消息体有关 |
这里说一下消息标识和校验码。
消息标识和协议号、消息长度、上一个消息标识(初始为0)成多元函数关系,具体是如何对应,未知!
校验码是数据头的前14位和消息体字节值之和再模取100000所得到的32位整型int。
数据体
由于数据体是不定长度的,所以不同类型数据体需要用协议号区分。
类型 | 描述 | 长度 |
---|---|---|
字符串 | 字符串为UTF编码,可以包括字符串长度,也可以不包括,由协议号决定。 | x |
字节数组 | 在as3中,其数据结构为ByteArray。 | x |
数组 | 递归分解 | x |
其他 | 写入无符号32位整型int | 4 |
数据加密
源码加密混淆过了,我只能根据一部分数据加密前后的值,再通过数学的方式推断出加密方式。
其加密方式就是一个异或和位整合的过程,下面给出的算法是用黑盒法得到的,与源码加密过程可能不同,但二者等价,相同输入时输出也相同。
Java版本代码
private static final int[] EN_KEY = {0xe6, 0x6b, 0xed, 0xcb, 0x6f, 0x84, 0x8e, 0x2e, 0xec, 0xad, 0xad, 0xac, 0xec, 0x6b, 0xae, 0xac, 0x4c, 0x4e};
private static final int EN_KEY_LEN = EN_KEY.length;
private static final int EN_DEFAULT_NUM = 0x68;
public static byte[] MEncrypt(byte[] bytes) {
byte[] outByts = new byte[bytes.length + 1];
new ByteArray(outByts).writeUnsignedInt(outByts.length);
outByts[4]=bytes[4];
outByts[5]=bytes[5];
int before = EN_DEFAULT_NUM,now;//b'1101000
for (int i = NO_ENCRYPT_LEN; i < bytes.length; i++) {
now = bytes[i] & 0xff;
outByts[i] = (byte) (((((now << 5) + (before >>> 3)) ^ EN_KEY[i % EN_KEY_LEN]) & 0xf0) +
(((before >>> 3) ^ (EN_KEY[i % EN_KEY_LEN])) & 0xf));
before = now;
}
outByts[bytes.length] = (byte) (((before >>> 3) ^ (EN_KEY[bytes.length % EN_KEY_LEN])) & 0xf);
return outByts;
}
public static byte[] MDecrypt(byte[] bytes) {
byte[] outByts = new byte[bytes.length - 1];
new ByteArray(outByts).writeUnsignedInt(outByts.length);
outByts[4]=bytes[4];
outByts[5]=bytes[5];
for (int i = NO_ENCRYPT_LEN; i < outByts.length; i++) {
int t2_0 = ((bytes[i] ^ EN_KEY[i % EN_KEY_LEN]) & 0xe0) >>> 5; // i [YYYx xxxx] -> [xxxx xxxYYY] i
int t7 = ((bytes[i + 1] ^ EN_KEY[(i + 1) % EN_KEY_LEN]) & 0x10) << 3;//i+1 [xxxY xxxx] -> [Yxxx xxxx] i
int t6_3 = ((bytes[i + 1] ^ EN_KEY[(i + 1) % EN_KEY_LEN]) & 0xf) << 3;//i+1 [xxxx YYYY] -> [xYYY Yxxx] i
outByts[i] = (byte) (t2_0 + t7 + t6_3);
}
return outByts;
}
题外话
到这里,狮子(八月)的尾巴也快结束了,折腾日记也暂时告一段落吧!
书读百遍,百遍感觉各有不同,相信之后回顾时亦会有一番收获吧!
本文由 ukuq 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Aug 29, 2019 at 10:43 pm