目录

11-1:定义socket通信格式

目录

单机手游绘制教程11-1:定义socket通信格式

赶码人

创建窗口、ndk编译C语言代码、矩阵算法、敌人数组、基址偏移,终于都讲完了,接下来就只剩下对接C语言端和java绘图端。

这期先简单谈谈,如何定义通信数据的结构。最舒服的方式肯定是java端用jni,两边都定义结构体,这样很直观,也方便解析数据,但jni也有一些学习成本,前面已经提到大小端序、字节转int和float的方法了,那绘图端还是手动解析字节吧。另外,本人C语言编程习惯不是很好(比如函数、变量命名,小驼峰和下划线随缘,etc),代码格式仅供参考。

https://cdn.jsdelivr.net/gh/xinqinew/pic@main/img/db75225feabec8d8b64ee7d3c7165cd639554cbc.png

首先是64字节的矩阵,以及4字节的敌人数量,接下来就考虑每个敌人需要占用多少字节了,本系列教程主题是绘制血量数字,所以要有8字节表示当前血量和最大血量,要绘制血量,还要有12字节的坐标(实际上坐标只需要x和z,这个游戏的y总是0,多传几个字节也无妨)。我打算顺便画个方框,那么就要有敌人的大小,因为坦克看起来似乎都是正方体,4字节就够了(这个后面再说)。还可以加一个结构体地址,这样方便将来对比数据找其他功能。

所以,每绘制一帧,需要传输64+4+(8+12+4)*n字节,接下来先通过C语言实现,定义一个结构体,如图:

https://cdn.jsdelivr.net/gh/xinqinew/pic@main/img/4bd698bfc2c90a5a051bcac0fc60033c99b013b2.png@942w_759h_progressive.png

 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函数改一改:

https://cdn.jsdelivr.net/gh/xinqinew/pic@main/img/ac45d71996fdd9b658e6a4020cede473f52cbee3.png@942w_479h_progressive.png

main函数,把sendto的参数改一改,如图:

https://cdn.jsdelivr.net/gh/xinqinew/pic@main/img/072cffd189e2c9447fb0e9dfd888de8c33a23e64.png@942w_552h_progressive.png

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);
}

成功接收到数据的效果图

https://cdn.jsdelivr.net/gh/xinqinew/pic@main/img/5736c1cf8d7277f4381e200a99740916ecbe1841.png@942w_531h_progressive.png

(明天是本系列的最后一期,教程09开头的计划目录写的“番外01:从javaswing快速移动到手机端”不做了,没啥可说的,区别就是继承JComponent改成继承View,回调方法是onDraw(Canvas canvas) ,直接用canvas.drawXXX画就完了,然后就是权限申请、悬浮窗···感兴趣自行查资料吧,接下来赶紧搞最后战役连跳飞天,可以先了解一下il2cppdumper和frida,说不定花几小时研究一下,也没必要看我的修复飞天思路了,提前剧透一句话吧,连跳飞天虽然不复杂,但也不会像有些网友猜的直接改坐标那么简单…,大概比搜0搜1复杂一点点吧。