wan玩的好手游 » 英雄联盟 » Mac osx平台上lol英雄联盟launcher启动器的分析实现

Mac osx平台上lol英雄联盟launcher启动器的分析实现

来源:互联网 编辑:wan玩的好手游小编 更新:2024-03-18 07:09:57 人气:

想在 Mac 上玩League of legends 英雄联盟,是有mac版本的,在riot拳头运营的地区中,有韩服、美服、欧服之类国外服务器。而腾讯运营国服版,却没有OSX版本。上次的录像分析中,是腾讯运营的windows版本产生的录像文件,可以在OSX版本的美服上解析、渲染、播放,也就以为着协议是相通的,也就意味着用riot运营的OSX客户端,连接腾讯运营的服务器,应该可以正常通讯,正常游戏的。

先来看下国服League of legends英雄联盟的目录结构


Air目录为LolClient.exe所在目录,即游戏大厅目录,air的,也就是flash as的,意味着反编译比较简单...

  1. Config目录确定

  2. Cross目录为腾讯游戏必装的各种乱七八糟工具包目录

  3. Game目录为game进程League of legends.exe所在目录

  4. log目录,我也不知道这是记录啥日志的,一直是空的

  5. logs目录,这是launcher启动器记录通讯数据的目录

  6. TCLS目录是腾讯登录SSO的客户端程序目录

  7. TQM目录,腾讯自己的,跟游戏无关


先来对比Windows跟osx上区别,riot运营的(以下以美服代表)游戏,是先选地区,再进入登录界面,再进入游戏。而腾讯运营的是先登录,再选择大区,再进入游戏。显然,腾讯运营的选大区,可以理解为riot运营的选地区,之后再进入游戏。区别是riot先选区,再登录;腾讯(以下简称国服)的先登录,再选区。先看国服在windows上流程,快捷方式打开的是TCLSClient.exe,内嵌登录的DLL,登录之后,选大区,直接开启LolClient.exe进入对应大区游戏。而美服的登录窗口是在LolClient.exe中完成的,不难看出区别是美服手动登录,国服自动登录。显然,是LolClient.exe启动时,CLI参数包含了一些KEY,来实现了自动登录。

也就是说,问题在登录认证部分,只要我在OSX上能实现LolClient.exe的自动登录就可以...当然要在windows上获取登录的Game Signature Key,先来看下win上的进程启动信息:

果然是CLI中参数带有signature key,参数如下:


AirLolClient.exe -runtime . -nodebug META-INFAIRapplication.xml . -- 8393 gameSignatureLength=120 

szGameSignature=000156AF4DBF0070990736102EF3C9F53FFD8DF469360BB7CD6B08C6380AE617E088291ED9FEA0395EEF92E1CCF80D6421305D3900A76AC4B6DD4E039DB9D2B84EAC0AB881FFFC045139F733D32CCED53C44DCF698E9CD636EE732F5E9006BA0C16C2B2144496E907077F5D6006824FAA52701BB0B68EFF8 

cltkeyLength=16 cltkey=h4.ac}w)gsq53_6Y uId=2669***1 --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出 GameLeague 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 ,先查看进程监听列表

 

C:Userschenchi>tasklist|find "lol.launcher_tencent.exe"

lol.launcher_tencent.exe      5416 Console                    1     10,564 K

C:Userschenchi>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来抓包)


E:crt_downloadProcessMonitor>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数据包:


    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个包作为例子,不难看出,他们的数据包格式拆分为前8bytes 10 00 00 00 01 00 00 00 为固定值(暂且认为是固定值);后面4bytes 04 00 00 00 05 00 00 00 为一段;再后面的4bytes 00 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 等字样

F5导出





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


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. ");

    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] ", &v5);

    Maestro_Log(0, &v6);

  }

  return 1;

}


再看看 MaestroMessageAgent_SendMessage 的调用处:


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. ");

    result = 0;

  }

  return result;

}


MaestroGameController_SendGameConnectedToServerMessage 函数名的字面含义上理解,此函数为『当游戏连接到服务器时,发送消息』,继续跟进...可是,我静态分析水平比较弱,越是继续跟进,我的思路越混乱,决定另辟蹊径。

在文中最初的LOL目录结构中,有个Logs目录,这个目录下Maestro Logs目录中的maestro-server.log、maestro-game_client.log中有一些日志,是开发人员用来排查调试程序执行结果的信息,大约如下:

 

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的客户端架构,以及猜测下服务端架构,根据我的理解,我画了一副图

架构图中的虚线部分,为服务器之间节点通讯,是我的猜测,并不是想客户端部分那样,依照分析客户端通讯得来的架构图。可能不准确。


  1. 匹配请求处理服务器,用来处理客户端的登录认证、符文、天赋修改、英雄列表,进入战斗的匹配请求等。

  2. 匹配服务器,应该是整个大区用同一个,这里会预算所有与大区玩家相关的数据,战斗力匹配,寻找想匹配对手,划分到对应服务器中。这是核心点,也是瓶颈点。

  3. 房间管理服务器,应该要独立拆分出来,与匹配服务器解耦,与战场服务器解耦。当匹配服务器完成后,只要将匹配结果的10名玩家,玩家的符文、天赋配置等发送给房间管理服务器,然后由房间管理服务器去创建对战进程,再获取对战服务器的IP PORT 以及每个人的加密KEY(也应该是认证该角的凭证),好比"14.17.23.94 5156 QbwA4soB8Jq7COEQ+1xc4g== 4006319232"发送给每一个客户端。客户端拿到消息,再发送给lol.launcher.tencent.exe,再有它启动游戏进程。

  4. 房间管理服务器都是可以横向扩展的,尤其是游戏服务器,更是可以横向扩展,单个游戏进程,只跟本次游戏中的10个玩家有关。


port范围5000-5500,也就是说单台服务器最多开500个端口,也就是500场战斗,以每场战斗10个人来算,单台服务器是5000人的承载。实际上可能要依赖服务器的性能承载。15年初网上消息LOL同时在线750W,时隔1年,国服现在同时在线有多少人?能承载多少玩家同时在线?匹配服务器虽然是很难拆分的节点,但业务比较单一,CPU密集型,应该大概也许可能不难解决。其他节点,拓展起来就更方便了,堆堆服务器应该可以解决。当然,我没坐过类似的客户端游戏架构,只是基于页游、手游的经验猜。




回到正题,LOL launcher的业务确定了,与另外两个进程通讯的协议格式也确定了,看看实现时,我要怎么做


  1. 监听系统信号的线程

  2. 监听TCP 8393端口,等待LolClient.exe连接

  3. 监听TCP 8394端口,等待League of legends.exe连接

  4. 监听TCP 8395端口?暂时不监听了,目前不知道这端口是干啥的

  5. 启动client的线程

  6. 连结与client的心跳线程

  7.  启动game的线程

  8. 连结跟game的心跳线程

  9. 接收来自game消息的心跳线程

  10. 处理client消息转发到game的线程

  11. 处理game消息转发到client的线程


解析协议数据包,获取command,数据包长度,数据包内容


  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类型已收到该消息。


      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

case MAESTROMESSAGETYPE_CLOSE:

//0x03 游戏进程退出

this.PacketSendChanToMain

case MAESTROMESSAGETYPE_HEARTBEAT:

//0x04 回复收到心跳

c.AsyncWritePacket(NewLolLauncherPacket(MAESTROMESSAGETYPE_REPLY, []byte{}), 0)

case MAESTROMESSAGETYPE_REPLY:

//0x05 不处理(一般不会有这种消息)

case MAESTROMESSAGETYPE_CHATMESSAGE_TO_GAME:

//0x0b 来自游戏大厅的消息,需要转发至游戏进程

this.PacketSendChanToMain

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的线程


//监听客户端通道消息

go func() {

for {

packet :=

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

default:

//MAESTROMESSAGETYPE_INVALID

log.Println("Client(main)->OnMessageFromMain->MAESTROMESSAGETYPE_INVALID:", commandType, " -- ", packet.GetHeader(), " -- ", data)

}

}

}()


当然,还得再启动两个线程来接收launcher转发来的消息,


func (this *LolLauncherClientCallback) OnMessageFromMain(c *gotcp.Conn) {

for {

packet :=

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进程执行时的环境变量,去除系统默认的几个,剩下的,就是游戏自己添加的


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


代码如下:


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国服了,然而,还是不能。除了程序执行时的环境变量外,还有一个程序执行时的当前目录:


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-INFAIRapplication.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时出错了,进不去,日志中记录

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...


之后,游戏进程就报错退出了。之后也没再继续花时间研究了。最终没能实现在 MAC 上打 LOL,不过本文跟多的是技术分析,与大家分享如何对进行网络协议分析,以及逆向实现中间客户端,希望本文对你有一定的帮忙。


更多详情,请扫描



欢迎玩家到【 wan玩的好手游】查看最新变态版手游攻略,只需要在百度输入【 wan玩的好手游】就可以浏览最新上线送满vip的变态手游攻略了,更多有关手游的攻略和资讯,敬请关注玩的好手游网!

标签: Mac osx平台上lol英雄联盟launcher启动器的分析实现
玩的好手游为玩家们提供非常多最新好玩的手游下载,热门的手机app下载,常用安卓应用软件下载,热门手游排行榜发现更多好游戏,还为玩家推荐近期热门手游攻略,手游公益服。 简繁切换 繁體中文