单机手游绘制教程11-1:定义socket通信格式
赶码人
创建窗口、ndk编译C语言代码、矩阵算法、敌人数组、基址偏移,终于都讲完了,接下来就只剩下对接C语言端和java绘图端。
这期先简单谈谈,如何定义通信数据的结构。最舒服的方式肯定是java端用jni,两边都定义结构体,这样很直观,也方便解析数据,但jni也有一些学习成本,前面已经提到大小端序、字节转int和float的方法了,那绘图端还是手动解析字节吧。另外,本人C语言编程习惯不是很好(比如函数、变量命名,小驼峰和下划线随缘,etc),代码格式仅供参考。
首先是64字节的矩阵,以及4字节的敌人数量,接下来就考虑每个敌人需要占用多少字节了,本系列教程主题是绘制血量数字,所以要有8字节表示当前血量和最大血量,要绘制血量,还要有12字节的坐标(实际上坐标只需要x和z,这个游戏的y总是0,多传几个字节也无妨)。我打算顺便画个方框,那么就要有敌人的大小,因为坦克看起来似乎都是正方体,4字节就够了(这个后面再说)。还可以加一个结构体地址,这样方便将来对比数据找其他功能。
所以,每绘制一帧,需要传输64+4+(8+12+4)*n字节,接下来先通过C语言实现,定义一个结构体,如图:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
struct AITank{
int curHp;
int maxHp;
float x;
float y;
float z;
float size;
};
struct{
float matrix[16];
int count;
struct AITank obj[50];
} draw_data;
|
然后如图所示,把前面写过的calc_matrix函数改一改:
main函数,把sendto的参数改一改,如图:
test函数注释掉,新增一个readAITank,作用类似于calc_matrix,负责往draw_data里面塞数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
void readAITank(){
uintptr_t gmc = readPtr(so_addr + 0x689CC);
uintptr_t p1 = readPtr( gmc + 0x28 * readPtr(gmc + 0x120) + 0x20 );
uintptr_t p2 = readPtr( p1 + 0x20 );
uintptr_t p3 = readPtr( p2 + 0xB890 );
uintptr_t p4 = readPtr( p3 + 0x20 );
int count = readInt(p4 + 0x14);
int i = 0, index = 0;
while( i < count){
uintptr_t p5 = p4 + 0x2d4 * i ;
if( readByte(p5 + 0x48) ){
draw_data.obj[index].x = readFloat(p5 + 0x19c);
draw_data.obj[index].y = readFloat(p5 + 0x19c + 4);
draw_data.obj[index].z = readFloat(p5 + 0x19c + 8);
draw_data.obj[index].size = readFloat(p5 + 0x40 + 0x214);
draw_data.obj[index].curHp = readInt(p5 + 0x40 + 0x4);
draw_data.obj[index].maxHp = readInt(p5 + 0x40 + 0x258);
index++;
}
i++;
}
draw_data.count = index;
}
|
C代码差不多了,接下来搞接收端。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
ds.receive(dp);
int count = byteToInt(b, 64);
System.out.println(count);
for(int i = 0 ; i < count ; i++) {
int offset = 68 + i * 24;
int curHp = byteToInt(b, offset);
int maxHp = byteToInt(b, offset + 4);
float x = Float.intBitsToFloat(byteToInt(b, offset + 8));
float y = Float.intBitsToFloat(byteToInt(b, offset + 12));
float z = Float.intBitsToFloat(byteToInt(b, offset + 16));
float size = Float.intBitsToFloat(byteToInt(b, offset + 20));
System.out.printf("hp=%d/%d, pos=[%.2f, %.2f, %.2f], size=%.2f\n", curHp, maxHp, x, y, z, size);
}
|
成功接收到数据的效果图
(明天是本系列的最后一期,教程09开头的计划目录写的“番外01:从javaswing快速移动到手机端”不做了,没啥可说的,区别就是继承JComponent改成继承View,回调方法是onDraw(Canvas canvas) ,直接用canvas.drawXXX画就完了,然后就是权限申请、悬浮窗···感兴趣自行查资料吧,接下来赶紧搞最后战役连跳飞天,可以先了解一下il2cppdumper和frida,说不定花几小时研究一下,也没必要看我的修复飞天思路了,提前剧透一句话吧,连跳飞天虽然不复杂,但也不会像有些网友猜的直接改坐标那么简单…,大概比搜0搜1复杂一点点吧。