本帖最后由 cfc4n 于 2016-4-20 11:33 编辑
大家好,我是新人,请多多关照。好几年前,我有过52pojie的帐号,一直都是潜水,从来不说话。也是因为自己破解水平低,而且从业的是编程业务,工作上很少接触破解,久而久之,成了几年不能录的死号。然后被清理掉了。然后,就没有然后了。。。
这次,决定发篇文章,留个档案,脱离死号的警戒线,不至于以后被清理掉,再者,最近正好做android方便的游戏研究,借此宝地,想认识一下二进制破解的前辈,学习一下。
关于这边文章的主题,去年年底时,我在我的blog上发过,但没有公布源码,这次黑米兄赐号,我总得拿点新鲜货出来,故把源码共享出来,供其他朋友学习研究,节省时间。 以下为正文:
我算个LOL玩家吧,虽然是郊区白银段位的,虽然能1打5个电脑,但瘾不小。每次想打LOL,都找不到PC电脑,看着OSX却不能玩国服,只能玩美服、韩服。而外服的网络延迟比较大,根本没法打,跳帧严重,野怪都打不过。每次瘾来了时,总是想在OSX上也能玩,次数多了,决定试试,看看能否在OSX上玩国服。
15年10月份对LOL英雄联盟录像做过解析器在mac osx上看lol国服ob录像的技术分析,文中最后提到过关于LOL客户端架构的组成,以及在OSX上打LOL国服还是有可能的。
现状分析,League of legends有mac版本,在riot拳头运营的地区中,有韩服、美服、欧服之类国外服务器。而腾讯运营国服版,却没有OSX版本。上次的录像分析中,是腾讯运营的windows版本产生的录像文件,可以在OSX版本的美服上解析、渲染、播放,也就以为着协议是相通的,也就意味着用riot运营的OSX客户端,连接腾讯运营的服务器,应该可以正常通讯,正常游戏的。
先来看下国服League of legends英雄联盟的目录结构
- Air目录为LolClient.exe所在目录,即游戏大厅目录,air的,也就是flash as的,意味着反编译比较简单…
- Config目录确定
- Cross目录为腾讯游戏必装的各种乱七八糟工具包目录
- Game目录为game进程League of legends.exe所在目录
- log目录,我也不知道这是记录啥日志的,一直是空的
- logs目录,这是launcher启动器记录通讯数据的目录
- TCLS目录是腾讯登录SSO的客户端程序目录
- TQM目录,腾讯自己的,跟游戏无关
先来对比Windows跟osx上区别,riot运营的(以下以美服代表)游戏,是先选地区,再进入登录界面,再进入游戏。而腾讯运营的是先登录,再选择大区,再进入游戏。显然,腾讯运营的选大区,可以理解为riot运营的选地区,之后再进入游戏。区别是riot先选区,再登录;腾讯(以下简称国服)的先登录,再选区。先看国服在windows上流程,快捷方式打开的是TCLS\Client.exe,内嵌登录的DLL,登录之后,选大区,直接开启LolClient.exe进入对应大区游戏。而美服的登录窗口是在LolClient.exe中完成的,不难看出区别是美服手动登录,国服自动登录。显然,是LolClient.exe启动时,CLI参数包含了一些KEY,来实现了自动登录。
也就是说,问题在登录认证部分,只要我在OSX上能实现LolClient.exe的自动登录就可以…当然要在windows上获取登录的Game Signature Key,先来看下win上的进程启动信息:
果然是CLI中参数带有signature key,参数如下:
[Bash shell] 纯文本查看 复制代码 Air\LolClient.exe -runtime .\ -nodebug META-INF\AIR\application.xml .\ -- 8393 gameSignatureLength=120
szGameSignature=000156AF4DBF0070990736102EF3C9F53FFD8DF469360BB7CD6B08C6380AE617E088291ED9FEA0395EEF92E1CCF80D6421305D3900A76AC4B6DD4E039DB9D2B84EAC0AB881FFFC045139F733D32CCED53C44DCF698E9CD636EE732F5E9006BA0C16C2B2144496E907077F5D6006824FAA52701BB0B68EFF8
cltkeyLength=16 cltkey=h4.ac}w)gsq53_6Y uId=26693841 --host=hn1-new-feapp.lol.qq.com --xmpp_server_url=hn1-new-ejabberd.lol.qq.com
--lq_uri=https://hn1-new-login.lol.qq.com:8443 --getClientIpURL=http://183.60.165.214/get_ip.php
可以看出,除了Air运行时参数外,还有8393数字,认证的KEY,当前大区服务器域名,xmpp聊天服务器地址,登录服务器地址之类。另外,在看下LolClient.exe的进程树:
如上图,游戏大厅进程LolClient.exe跟game进程League of legends.exe都是lol.launcher_tencent.exe的子进程。
这里的8393数字其实是LolClient.exe要连接的TCP端口,也就是lol.launcher_tencent.exe进程监听的端口, lol.launcher_tencent.exe进程再fork起LolClient.exe进程,进入游戏大厅。
游戏开局匹配时,都是在LolClient.exe中进行的,匹配完成之后 ,lol.launcher_tencent.exe又createprocess出Game\League of Legends.exe进入游戏。
那么,只要我在osx上实现一个launcher的功能,就能在osx上玩League of legends英雄联盟的国服了,感觉很兴奋的样子,继续往下看……
LolClient.exe进程进入游戏后,lol.launcher_tencent.exe进程是如何知道的呢?它又是如何决定启动League of Legends.exe进程的?在上篇在mac osx上看lol国服ob录像的技术分析中,提到过,game进程启动时,命令行参数有个端口8394,先查看进程监听列表
[Bash shell] 纯文本查看 复制代码
C:\Users\chenchi>tasklist|find "lol.launcher_tencent.exe"
lol.launcher_tencent.exe 5416 Console 1 10,564 K
C:\Users\chenchi>netstat -anto |find "5416"
TCP 127.0.0.1:8393 0.0.0.0:0 LISTENING 5416 InHost
TCP 127.0.0.1:8393 127.0.0.1:2552 ESTABLISHED 5416 InHost
TCP 127.0.0.1:8394 0.0.0.0:0 LISTENING 5416 InHost
TCP 127.0.0.1:8395 0.0.0.0:0 LISTENING 5416 InHost
从结果中,可以看到进程lol.launcher_tencent.exe监听了本地的8393、8394、8395端口,而且,LolClient.exe已经连接了8393端口,也就是其启动时命令行参数中的对应数值。那么另外两个端口8394、8395也应该是其他进程来连接的咯。抓本地数据包,看看他们之间有没有通讯,如看看他们通讯了什么内容:(在windows上,wireshark抓不到回环地址通讯包,可以用rawcap来抓包)
[Bash shell] 纯文本查看 复制代码
E:\crt_download\ProcessMonitor>RawCap.exe 127.0.0.1 localhost-2016-02-01.pcapng
Sniffing IP : 127.0.0.1
File : localhost-2016-02-01.pcapng
Packets : 3131
之后,wireshark打开这个文件,可以看到如下TCP数据包:
[C] 纯文本查看 复制代码
00000000 10 00 00 00 01 00 00 00 04 00 00 00 00 00 00 00 ........ ........
00000000 10 00 00 00 01 00 00 00 04 00 00 00 00 00 00 00 ........ ........
00000010 10 00 00 00 01 00 00 00 05 00 00 00 00 00 00 00 ........ ........
00000010 10 00 00 00 01 00 00 00 00 00 00 00 34 00 00 00 ........ ....4...
00000020 31 34 2e 31 37 2e 32 33 2e 39 34 20 35 31 35 36 14.17.23 .94 5156
00000030 20 51 62 77 41 34 73 6f 42 38 4a 71 37 43 4f 45 QbwA4so B8Jq7COE
00000040 51 2b 31 78 63 34 67 3d 3d 20 34 30 30 36 33 31 Q+1xc4g= = 400631
00000050 39 32 33 32 9232
从协议包结果上来看,对比发送、接收的4个包作为例子,不难看出,他们的数据包格式拆分为前8bytes10 00 00 00 01 00 00 00为固定值(暂且认为是固定值);后面4bytes04 00 00 00、05 00 00 00为一段;再后面的4bytes00 00 00 00、34 00 00 00为另一段;最后一部分的Nbytes长度31 34 2e 31 37 2e 32 33 2e 39 34 20 35 31 35 36 20 51 62 77 41 34 73 6f 42 38 4a 71 37 43 4f 45 51 2b 31 78 63 34 67 3d 3d 20 34 30 30 36 33 31 39 32 33 32为最后一段;以第4个通讯包来看,第三段的对应数值是0x34,显然是LittleEndian小端字节序,显然后面的00000020 – 00000054部分数据长度也是0x34个,也就是说,协议通讯格式的第三段表示后面消息包长度的含义,第四段就是主体消息正文。第一段、第二段都是消息头的部分。第二段多数为command指令。暂且这么假设,那么通讯协议包格式确定了,接下来就要确认下每个command对应的业务含义了。先看下LolClient.exe进程发给launcher,跟League of legends.exe发给launcher进程的TCP数据包,初步确认下我对协议包格式的判断是否正确
如何查找command的类型一共有多少种?如何确认每种command的意义?记得最初LOL目录结构里提到大厅客户端LolClient.exe是flash air的,反编译比较方便,入口文件是LolClient.swf,跟下去是mod/win/ClientWindow.dat,然后加载了&^*($#@#,as代码太乱了,而且,都分布在各个小的flash中,目录还不统一,拓展名还都是dat的,还得手动改成swf,反编译工具才能选它,as代码也不好理解,检索麻烦,索性直接反编译lol.launcher_tencent.exe进程吧。
IDA的导入lol.launcher_tencent.exe进程后,在imports中看到 Maestro_Remove、…等来自RiotLauncher这个library
再静态分析RiotLauncher.dll,在Exports里看到Maestro_MessageTypeToString函数
跟进,看到MAESTROMESSAGETYPE_GAMECLIENT_CREATE等字样
[C] 纯文本查看 复制代码
signed int __cdecl Maestro_MessageTypeToString(int a1, char *a2, rsize_t a3)
{
char *v4; // [sp-8h] [bp-8h]@2
switch ( a1 )
{
case 0:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_CREATE";
break;
case 1:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_STOPPED";
break;
case 2:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_CRASHED";
break;
case 7:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_ABANDONED";
break;
case 8:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_LAUNCHED";
break;
case 9:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_VERSION_MISMATCH";
break;
case 10:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_CONNECTED_TO_SERVER";
break;
case 3:
v4 = "MAESTROMESSAGETYPE_CLOSE";
break;
case 4:
v4 = "MAESTROMESSAGETYPE_HEARTBEAT";
break;
case 5:
v4 = "MAESTROMESSAGETYPE_REPLY";
break;
case 6:
v4 = "MAESTROMESSAGETYPE_LAUNCHERCLIENT";
break;
case 11:
v4 = "MAESTROMESSAGETYPE_CHATMESSAGE_TO_GAME";
break;
case 12:
v4 = "MAESTROMESSAGETYPE_CHATMESSAGE_FROM_GAME";
break;
case 20:
v4 = "MAESTROMESSAGETYPE_PLAY_REPLAY";
break;
case 13:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_CREATE_VERSION";
break;
case 14:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_INSTALL_VERSION";
break;
case 15:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_CANCEL_INSTALL";
break;
case 16:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_INSTALL_PROGRESS";
break;
case 17:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_INSTALL_PREVIEW";
break;
case 18:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_CANCEL_PREVIEW";
break;
case 19:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_PREVIEW_PROGRESS";
break;
case 21:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_UNINSTALL_VERSION";
break;
case 22:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_CANCEL_UNINSTALL";
break;
case 23:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_UNINSTALL_PROGRESS";
break;
case 24:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_UNINSTALL_PREVIEW";
break;
case 25:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_CANCEL_UNINSTALL_PREVIEW";
break;
case 26:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_PREVIEW_UNINSTALL_PROGRESS";
break;
case 27:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_ENUMERATE_INSTALLED_VERSIONS";
break;
case 28:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_CREATE_CLIENT_AND_PRELOAD";
break;
case 29:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_UPDATE_PRELOADED_GAME_WITH_CREDENTIALS";
break;
case 30:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_INITIAL_PRELOAD_COMPLETE";
break;
case 31:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_UPDATE_PLAYER_CONNECTION";
break;
case 32:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_PLAY_PRELOADED_GAME";
break;
case 33:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_GAME_LOADING_COMPLETE";
break;
case 34:
v4 = "MAESTROMESSAGETYPE_KILL_GAMECLIENT_PROCESS";
break;
case 35:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_ENUMERATE_UNINSTALLABLE_VERSIONS";
break;
case 36:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_ENUMERATE_LATEST_VERSIONS";
break;
case 37:
v4 = "MAESTROMESSAGETYPE_GAMECLIENT_INSTALLED_GAME_VERSION_SIZE";
break;
default:
v4 = "MAESTROMESSAGETYPE_INVALID";
break;
}
sub_10027C07(a2, a3, v4);
return 1;
}
至此,可以看到lol launcher的协议指令类型都在这里。再看看哪里调用了Maestro_MessageTypeToString函数:
[C] 纯文本查看 复制代码
signed int __cdecl MaestroMessageAgent_SendMessage(int a1, int a2, const char *a3)
{
unsigned int v4; // [sp+24h] [bp-28Ch]@2
char v5; // [sp+28h] [bp-288h]@12
char v6; // [sp+168h] [bp-148h]@12
if ( a3 )
v4 = strlen(a3);
else
v4 = 0;
if ( !*(_DWORD *)(a1 + 16) )
{
Maestro_Log(2, "Cannot send messages when the MessageAgent isn't started.\n");
return 0;
}
sub_1001FE7A(*(_DWORD *)(a1 + 12));
if ( !sub_10020C02(16) || a3 && !sub_10020C02(v4) )
{
sub_10027FBA();
return 0;
}
sub_10027FBA();
if ( *(_DWORD *)(a1 + 36) == 1 || a2 != 4 && a2 != 5 )
{
Maestro_MessageTypeToString(a2, &v5, 0x140u);
sub_10027D05(&v6, 320, "SendMessage [%s]\n", &v5);
Maestro_Log(0, &v6);
}
return 1;
}
再看看MaestroMessageAgent_SendMessage的调用处:
[C] 纯文本查看 复制代码
signed int __cdecl MaestroGameController_SendGameConnectedToServerMessage()
{
signed int result; // eax@2
if ( dword_101576E0 )
{
result = MaestroMessageAgent_SendMessage(**(_DWORD **)dword_101576E0, 10, NULL);
}
else
{
Maestro_Log(2, "MaestroGameControllerStruct is not yet initialized.\n");
result = 0;
}
return result;
}
从MaestroGameController_SendGameConnectedToServerMessage函数名的字面含义上理解,此函数为『当游戏连接到服务器时,发送消息』,继续跟进…可是,我静态分析水平比较弱,越是继续跟进,我的思路越混乱,决定另辟蹊径。
在文中最初的LOL目录结构中,有个Logs目录,这个目录下Maestro Logs目录中的maestro-server.log、maestro-game_client.log中有一些日志,是开发人员用来排查调试程序执行结果的信息,大约如下:
[C] 纯文本查看 复制代码 INFO: maestro [server] initializing...
INFO: New thread go!
INFO: Starting Process Thread Spinning
INFO: Using registered HWND: 00000000 to launch the air client.
INFO: StartProcess [MAESTROPROCESSTYPE_AIR] successful
INFO: Launcher init of maestro completed.
INFO: ReceiveMessage [MAESTROMESSAGETYPE_GAMECLIENT_CREATE]
INFO: New thread go!
INFO: Starting Process Thread Spinning
INFO: StartProcess [MAESTROPROCESSTYPE_GAME] successful
INFO: ReceiveMessage [MAESTROMESSAGETYPE_GAMECLIENT_LAUNCHED]
INFO: SendMessage [MAESTROMESSAGETYPE_GAMECLIENT_LAUNCHED]
INFO: ReceiveMessage [MAESTROMESSAGETYPE_GAMECLIENT_CONNECTED_TO_SERVER]
INFO: SendMessage [MAESTROMESSAGETYPE_GAMECLIENT_CONNECTED_TO_SERVER]
INFO: ReceiveMessage [MAESTROMESSAGETYPE_CHATMESSAGE_FROM_GAME]
INFO: SendMessage [MAESTROMESSAGETYPE_CHATMESSAGE_FROM_GAME]
INFO: ReceiveMessage [MAESTROMESSAGETYPE_CHATMESSAGE_TO_GAME]
INFO: SendMessage [MAESTROMESSAGETYPE_CHATMESSAGE_TO_GAME]
INFO: ReceiveMessage [MAESTROMESSAGETYPE_CHATMESSAGE_TO_GAME]
INFO: SendMessage [MAESTROMESSAGETYPE_CHATMESSAGE_TO_GAME]
INFO: ReceiveMessage [MAESTROMESSAGETYPE_GAMECLIENT_ABANDONED]
INFO: SendMessage [MAESTROMESSAGETYPE_GAMECLIENT_ABANDONED]
INFO: [MaestroMessageAgent_ReadFromSocketInternal] 操作成功完成。
WARNING: Received 0 bytes on Maestro socket when data was expected.
INFO: ReceiveMessage [MAESTROMESSAGETYPE_CLOSE]
INFO: SendMessage [MAESTROMESSAGETYPE_CLOSE]
INFO: maestro exiting...
这里确实可以对应着launcher协议中,某些command被接收后,会被记录到这里。如何确认每个日志记录指令对应是哪个TCP包发送后的结果呢?如何利用这些日志信息呢?如果windows上有类似linux的strace查看系统调用的工具就好了,你还别说,还真有一款功能类似的,就是ProcessMonitor,有了这工具,我就可以看到lol.launcher.tencent.exe进程在何时发送TCP数据包,发送后写了哪个文件数据,写了什么内容,先写日志还是先发TCP数据包。。。
如上图,可以看到先发TCP包,还是先写日志文件,哪个进程发,哪个进程写。但还有一点要确认的是,发的TCP包的内容如何跟写的日志内容配对问题,显然,ProcessMonitor工具里有些更有用的信息,你可能没注意到进程每次发包后,会记录发的包大小;进程每次写入文件数据时,会记录写时的offset以及写入文件长度,根据launcher进程写日志时,获取的offset位置,可以找到对应maestro-server.log文件的offset,再根据写入文件时的length数值,可以确定写入内容是什么。那么,机智的你不难看出,写入日志内容的字面含义,很容易确认了TCP数据包中对应的command代表的含义,也明白了command代表的业务逻辑。
好了,lol.launcher_tencent.exe的功能大约理清楚了,再来回顾下League of Legends的客户端架构,以及猜测下服务端架构,根据我的理解,我画了一副图
架构图中的虚线部分,为服务器之间节点通讯,是我的猜测,并不是想客户端部分那样,按照分析客户端通讯得来的架构图。可能不准确。
- 匹配请求处理服务器,用来处理客户端的登录认证、符文、天赋修改、英雄列表,进入战斗的匹配请求等。
- 匹配服务器,应该是整个大区用同一个,这里会预算所有与大区玩家相关的数据,战斗力匹配,寻找想匹配对手,划分到对应服务器中。这是核心点,也是瓶颈点。
- 房间管理服务器,应该要独立拆分出来,与匹配服务器解耦,与战场服务器解耦。当匹配服务器完成后,只要将匹配结果的10名玩家,玩家的符文、天赋配置等发送给房间管理服务器,然后由房间管理服务器去创建对战进程,再获取对战服务器的IP PORT 以及每个人的加密KEY(也应该是认证该角色的凭证),比如“14.17.23.94 5156 QbwA4soB8Jq7COEQ+1xc4g== 4006319232”发送给每一个客户端。客户端拿到消息,再发送给lol.launcher.tencent.exe,再有它启动游戏进程。
- 房间管理服务器都是可以横向扩展的,尤其是游戏服务器,更是可以横向扩展,单个游戏进程,只跟本次游戏中的10个玩家有关。
port范围5000-5500,也就是说单台服务器最多开500个端口,也就是500场战斗,以每场战斗10个人来算,单台服务器是5000人的承载。实际上可能要依赖服务器的性能承载。15年初网上消息LOL同时在线750W,时隔1年,国服现在同时在线有多少人?能承载多少玩家同时在线?匹配服务器虽然是很难拆分的节点,但业务比较单一,CPU密集型,应该大概也许可能不难解决。其他节点,拓展起来就更方便了,堆堆服务器应该可以解决。当然,我没坐过类似的客户端游戏架构,只是基于页游、手游的经验猜。
回到正题,LOL launcher的业务确定了,与另外两个进程通讯的协议格式也确定了,看看实现时,我要怎么做
- 监听系统信号的线程
- 监听TCP 8393端口,等待LolClient.exe连接
- 监听TCP 8394端口,等待League of legends.exe连接
- 监听TCP 8395端口?暂时不监听了,目前不知道这端口是干啥的
- 启动client的线程
- 保持与client的心跳线程
- 启动game的线程
- 保持跟game的心跳线程
- 接收来自game消息的心跳线程
- 处理client消息转发到game的线程
- 处理game消息转发到client的线程
解析协议数据包,获取command,数据包长度,数据包内容
[Golang] 纯文本查看 复制代码
type Header struct {
pHead0 uint32 //默认0x10
pHead1 uint32 //默认0x01
pCommand uint32 //默认0x00
pLength uint32 //默认0x00
}
func (head *Header) Read(buf []byte) {
head.pHead0 = binary.LittleEndian.Uint32(buf[:4])
head.pHead1 = binary.LittleEndian.Uint32(buf[4:8])
head.pCommand = binary.LittleEndian.Uint32(buf[8:12])
head.pLength = binary.LittleEndian.Uint32(buf[12:16])
}
// Packet
type LolLauncherPacket struct {
pHead Header
pData []byte
}
分析command,心跳包直接回复。与game无关消息包,直接回复MAESTROMESSAGETYPE_REPLY类型已收到该消息。
[Golang] 纯文本查看 复制代码 func (this *LolLauncherClientCallback) OnMessage(c *gotcp.Conn, p gotcp.Packet) bool {
packet := p.(*LolLauncherPacket)
data := packet.GetData()
commandType := packet.GetCommand()
switch commandType {
case MAESTROMESSAGETYPE_GAMECLIENT_CREATE:
// 0x00 存储data数据,此数据为League of legends 启动参数
c.AsyncWritePacket(NewLolLauncherPacket(MAESTROMESSAGETYPE_REPLY, []byte{}), 0)
log.Println("LOGIN KEY:", string(data))
////启动游戏进程
this.PacketSendChanToMain <- packet
case MAESTROMESSAGETYPE_CLOSE:
//0x03 游戏进程退出
this.PacketSendChanToMain <- packet
case MAESTROMESSAGETYPE_HEARTBEAT:
//0x04 回复收到心跳
c.AsyncWritePacket(NewLolLauncherPacket(MAESTROMESSAGETYPE_REPLY, []byte{}), 0)
case MAESTROMESSAGETYPE_REPLY:
//0x05 不处理(一般不会有这种消息)
case MAESTROMESSAGETYPE_CHATMESSAGE_TO_GAME:
//0x0b 来自游戏大厅的消息,需要转发至游戏进程
this.PacketSendChanToMain <- packet
default:
//MAESTROMESSAGETYPE_INVALID
log.Println("Client->OnMessage->MAESTROMESSAGETYPE_INVALID:", commandType, " -- ", packet.pHead, " -- ", data)
}
部分消息需要转发给game进程,走chan转发,比如进入游戏后,好友还是可以在游戏大厅发消息到正在游戏中的玩家的,聊天消息转发MAESTROMESSAGETYPE_CHATMESSAGE_TO_GAME 就是从client发到launcher,再有launcher转发至game的。
处理client消息转发到game的线程、处理game消息转发到client的线程
[Golang] 纯文本查看 复制代码 //监听客户端通道消息
go func() {
for {
packet := <-clientPacketChan.packetSendChanToMain
data := packet.GetData()
commandType := packet.GetCommand()
switch commandType {
case proto.MAESTROMESSAGETYPE_GAMECLIENT_CREATE:
//获取参数,启动游戏进程
go lolCommands.LolGameCommand(strconv.Itoa(int(lolgame_port)), string(data))
//消息发送至game客户端
case proto.MAESTROMESSAGETYPE_CLOSE:
// 0X03 游戏关闭
case proto.MAESTROMESSAGETYPE_HEARTBEAT:
//0x04 回复收到心跳
case proto.MAESTROMESSAGETYPE_REPLY:
//0x05 确认收到消息包的回复(可以不做处理)
case proto.MAESTROMESSAGETYPE_CHATMESSAGE_TO_GAME:
//0x0b 来自游戏大厅的消息,需要转发至游戏进程(在ClientCallback中实现)
gamePacketChan.packetReceiveChanFromMain <- packet
default:
//MAESTROMESSAGETYPE_INVALID
log.Println("Client(main)->OnMessageFromMain->MAESTROMESSAGETYPE_INVALID:", commandType, " -- ", packet.GetHeader(), " -- ", data)
}
}
}()
当然,还得再启动两个线程来接收launcher转发来的消息,
[Golang] 纯文本查看 复制代码 func (this *LolLauncherClientCallback) OnMessageFromMain(c *gotcp.Conn) {
for {
packet := <-this.PacketReceiveChanFromMain
data := packet.GetData()
commandType := packet.GetCommand()
switch commandType {
case MAESTROMESSAGETYPE_GAMECLIENT_ABANDONED:
c.AsyncWritePacket(packet, 0)
case MAESTROMESSAGETYPE_GAMECLIENT_LAUNCHED:
//0X08
c.AsyncWritePacket(packet, 0)
case MAESTROMESSAGETYPE_GAMECLIENT_CONNECTED_TO_SERVER:
//0XOA 连接到服务器
c.AsyncWritePacket(packet, 0)
case MAESTROMESSAGETYPE_CHATMESSAGE_TO_GAME:
//0x0b 来自游戏大厅的消息,需要转发至游戏进程(在ClientCallback中实现)
c.AsyncWritePacket(packet, 0)
case MAESTROMESSAGETYPE_CHATMESSAGE_FROM_GAME:
c.AsyncWritePacket(packet, 0)
default:
//MAESTROMESSAGETYPE_INVALID
log.Println("Client->OnMessageFromMain->IGNOREMESSAGE:", commandType, " -- ", packet.pHead, " -- ", data)
}
}
}
等等等等,一系列代码写完后,本以为终于可以在osx上玩LOL国服了,没想到,我还是太年轻,想的太简单了。程序启动LolClient.exe之后就假死了,一番排查后,发现是TCP粘包问题
尼玛,是LolClient.exe发送给launcher.exe的TCP数据包分了两个TCP包发送的,尼玛,100个字节都不到,还分两个字节,尼玛。。累不累?
一系列代码写完后,本以为终于可以在osx上玩LOL国服了,没想到,我还是太年轻,想的太简单了。我写的lol launcher启动Lolclient.exe没问题,符文、天赋编辑也都没问题。但启动League of legends.exe时,却进程崩溃退出了,而我却百思不得其解,为什么?通信协议一样的,发送command时机一样的,为什么会崩溃呢?这突然的失败,让我思路中断,思绪全无,不知道如何进行下去。。。。。。冥冥中,我记得在静态分析lol.launcher.tencent.exe时,看到了一个特殊的函数名
再回到ProcessMonitor中看看League of legends.exe启动时的环境变量是什么
显然是__COMPAT_LAYER=ElevateCreateProcess
在OSX上,也找下环境变量是什么获取LeagueOfLegends.app进程执行时的环境变量,去除系统默认的几个,剩下的,就是游戏自己添加的
[Bash shell] 纯文本查看 复制代码 cfc4n@cnxct:~$ ps -ef|grep League
501 3955 3848 0 4:37下午 ?? 0:00.55 /Applications/League of Legends.app/Contents/LoL/RADS/solutions/lol_game_client_sln/releases/0.0.0.193/deploy/LeagueOfLegends.app/Contents/MacOS/LeagueofLegends 8394 LoLPatcher /Applications/League of Legends.app/Contents/LoL/RADS/projects/lol_air_client/releases/0.0.0.210/deploy/bin/LolClient 182.162.122.113 5102 yvqljG4isLEGWoQS4WEEUA== 39920272
cfc4n@cnxct:~$ ps -wwE -p 3955
PID TTY TIME CMD
3955 ?? 0:24.44 /Applications/League of Legends.app/Contents/LoL/RADS/solutions/lol_game_client_sln/releases/0.0.0.193/deploy/LeagueOfLegends.app/Contents/MacOS/LeagueofLegends 8394 LoLPatcher /Applications/League of Legends.app/Contents/LoL/RADS/projects/lol_air_client/releases/0.0.0.210/deploy/bin/LolClient 182.162.122.113 5102 yvqljG4isLEGWoQS4WEEUA== 39920272 LOGNAME=cfc4n SHELL=/bin/bash USER=cfc4n riot_launched=true SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.8MjNgu5Xfd/Listeners XPC_SERVICE_NAME=com.riotgames.MacContainer.67552 HOME=/Users/cfc4n __CF_USER_TEXT_ENCODING=0x1F5:0x19:0x34 TMPDIR=/var/folders/dn/qvv6vmxd6c5g8bf6t0dtwwcw0000gn/T/ PATH=/usr/bin:/bin:/usr/sbin:/sbin Apple_PubSub_Socket_Render=/private/tmp/com.apple.launchd.ZBoYrDDTEF/Render XPC_FLAGS=0x0
//对其格式,如下
LOGNAME=cfc4n
SHELL=/bin/bash
USER=cfc4n riot
launched=true
SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.8MjNgu5Xfd/Listeners
XPC_SERVICE_NAME=com.riotgames.MacContainer.67552
HOME=/Users/cfc4n
__CF_USER_TEXT_ENCODING=0x1F5:0x19:0x34
TMPDIR=/var/folders/dn/qvv6vmxd6c5g8bf6t0dtwwcw0000gn/T/
PATH=/usr/bin:/bin:/usr/sbin:/sbin
Apple_PubSub_Socket_Render=/private/tmp/com.apple.launchd.ZBoYrDDTEF/Render
XPC_FLAGS=0x0
[Golang] 纯文本查看 复制代码 func (this *launcher_commands) LolGameSetenv() {
var LolEnvKeys = [12]string{"LOGNAME", "SHELL", "USER", "SSH_AUTH_SOCK", "XPC_SERVICE_NAME", "HOME", "__CF_USER_TEXT_ENCODING", "TMPDIR", "PATH", "Apple_PubSub_Socket_Render", "XPC_FLAGS", "riot_launched"}
var LolEnvs map[string]string
LolEnvs = make(map[string]string)
for _, v := range LolEnvKeys {
switch v {
case "riot_launched":
LolEnvs[v] = "true"
case "XPC_SERVICE_NAME":
LolEnvs[v] = "com.riotgames.MacContainer.67552"
default:
LolEnvs[v] = os.Getenv(v)
}
}
os.Clearenv()
for k1, v1 := range LolEnvs {
os.Setenv(k1, v1)
}
}
一系列代码写完后,本以为终于可以在osx上玩LOL国服了,没想到,我还是太年轻,想的太简单了。除了程序执行时的环境变量外,还有一个程序执行时的当前目录:
[Bash shell] 纯文本查看 复制代码 cfc4n@cnxct:~$ ps -ef|grep League
501 3828 1 0 4:34下午 ?? 0:04.64 /Applications/League of Legends.app/Contents/LoL/RADS/system/UserKernel.app/Contents/MacOS/UserKernel updateandrun lol_launcher LoLLauncher.app
501 3847 3828 0 4:34下午 ?? 0:00.32 /Applications/League of Legends.app/Contents/LoL/RADS/projects/lol_launcher/releases/0.0.0.170/deploy/LoLLauncher.app/Contents/MacOS/LoLLauncher
501 3848 3847 0 4:34下午 ?? 0:00.24 /Applications/League of Legends.app/Contents/LoL/RADS/projects/lol_patcher/releases/0.0.0.42/deploy/LoLPatcher.app/Contents/MacOS/LoLPatcher
501 3871 3848 0 4:34下午 ?? 0:02.26 /Applications/League of Legends.app/Contents/LoL/RADS/projects/lol_air_client/releases/0.0.0.210/deploy/bin/LolClient -runtime .\ -nodebug META-INF\AIR\application.xml .\ -- 8393
501 3955 3848 0 4:37下午 ?? 0:00.55 /Applications/League of Legends.app/Contents/LoL/RADS/solutions/lol_game_client_sln/releases/0.0.0.193/deploy/LeagueOfLegends.app/Contents/MacOS/LeagueofLegends 8394 LoLPatcher /Applications/League of Legends.app/Contents/LoL/RADS/projects/lol_air_client/releases/0.0.0.210/deploy/bin/LolClient 182.162.122.113 5102 yvqljG4isLEGWoQS4WEEUA== 39920272
501 3880 824 0 4:34下午 ttys000 0:00.00 grep League
cfc4n@cnxct:~$ lsof -a -d cwd -p 3828
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
UserKerne 3828 cfc4n cwd DIR 1,4 136 15223994 /Applications/League of Legends.app/Contents/LoL/RADS/projects/lol_launcher/releases/0.0.0.170/deploy
cfc4n@cnxct:~$ lsof -a -d cwd -p 3847
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
LoLLaunch 3847 cfc4n cwd DIR 1,4 136 15223994 /Applications/League of Legends.app/Contents/LoL/RADS/projects/lol_launcher/releases/0.0.0.170/deploy
cfc4n@cnxct:~$ lsof -a -d cwd -p 3848
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
LoLPatche 3848 cfc4n cwd DIR 1,4 170 15224042 /Applications/League of Legends.app/Contents/LoL/RADS/projects/lol_patcher/releases/0.0.0.42/deploy
cfc4n@cnxct:~$ lsof -a -d cwd -p 3871
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
LolClient 3871 cfc4n cwd DIR 1,4 680 15258110 /Applications/League of Legends.app/Contents/LoL/RADS/projects/lol_air_client/releases/0.0.0.210/deploy/bin
cfc4n@cnxct:~$ lsof -a -d cwd -p 3955
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
LeagueofL 3955 cfc4n cwd DIR 1,4 170 15490389 /Applications/League of Legends.app/Contents/LoL/RADS/solutions/lol_game_client_sln/releases/0.0.0.193/deploy
记得要chdir一下,这么改进后,本以为终于可以在osx上玩LOL国服了,没想到,它确实在windows上登录国服,终于运行起来了
它也在OSX上登录美服,成功运行起来了
那么我只要把OSX上启动LolClient的命令行参数改为国服的参数,就可以登录国服,玩国服游戏咯,尝试一下
果然可以,我情不自禁的开始佩服我的聪慧,机智,下一步就开房间,匹配打国服了?然而,我还是太年轻,想的太简单了。程序启动League of Legend.app时出错了,进不去,日志中记录
[Bash shell] 纯文本查看 复制代码 000004.769| 0.0000kb| 0.0000kb added| ALWAYS| r3dRenderLayer::RecreateOwnedResources
000005.436| 0.0000kb| 0.0000kb added| ALWAYS| r3dRenderLayer::InitResources exit successfully
000005.463| 0.0000kb| 0.0000kb added| ALWAYS| Waiting for client ID
000005.704| 0.0000kb| 0.0000kb added| ALWAYS| {"messageType":"riot__game_client__connection_info","message_body":"Hard Connect"}
000005.704| 0.0000kb| 0.0000kb added| ALWAYS| Received client ID
000005.704| 0.0000kb| 0.0000kb added| ALWAYS| Set focus to app
000005.704| 0.0000kb| 0.0000kb added| ALWAYS| Input started
000005.718| 0.0000kb| 0.0000kb added| ALWAYS| Query Status Req started
000006.864| 0.0000kb| 0.0000kb added| ALWAYS| Query Status Req ended
000006.864| 0.0000kb| 0.0000kb added| ALWAYS| Waiting for server response...
之后,游戏进程就报错退出了。我太年轻了,想的太简单了……可是,我好像已经不年轻了……
这到底是为什么,玩个游戏怎么这么难,这么难?我只是想玩个游戏而已,为什么这么难?腾讯LOL项目组的朋友,能不能告诉我,你们还验证了什么?很明显不是版本号,是不是操作系统?是不是拒绝了OSX的登录? 曾经,我曾尝试过从协议上入手,看看LOL游戏协议能不能搞定,可难度比较大,花了好几个星期的空余时间,大约确认了是UDP Enet协议格式,后来,也在网上搜到相关信息,确认了。但网上公开的协议格式,是4.23以前的,现在都6.*的版本,早就改了….唉,以后再说吧…
最后,OSX上,美服启动成功。国服启动失败。WINDOWS上,国服启动成功,美服未测。
代码地址: LOL启动器的github地址 :https://github.com/cfc4n/lol_launcher
求认识android 逆向反编译的大神,谢谢。 |