LeagueClient通信(0)-LCU通讯原理&如何让熊孩子玩不了LOL

缘起

之前看到 @MarioCrane 的自定义创建 5V5 训练模式工具,让我对 LOL 客户端的 API 又产生了点兴趣。

仓库地址: https://github.com/MarioCrane/LeaueLobby

为啥要说又。。因为之前对 LOL 的 wad 资源拆包的时候我曾了解过 LeagueClient 的一些运行原理

Riot 的开发者博客曾经在更新客户端的时候解析了其中的技术原理

https://technology.riotgames.com/news/architecture-league-client-update

相比之前的了解,目前 Riot 的开发文档已经完善了很多,值得去探索一番。

LCU 客户端结构

简单来说,目前 LOL 的 LCU 客户端分为两部分

上层(LeagueClientUx.exe, LeagueClientUxRender.exe)基于 Chromium Embedded Framework (CEF) 主要用于显示界面并通过 RTMP 协议与底层进行通信。简单说就是个定制化的浏览器,用于数据交互。

底层(LeagueClient.exe)是一个 Server 与上层和服务器进行通信,同时会启动一个 HTTPS 的 Web Server 以供第三方程序来与之通信。

用户在客户端很大一部分的操作,都是通过客户端的 HTTP 通讯来完成的。

LCU 有一套完善的REST API用以通讯交互,你可以在这个网站中查询到大多数的 REST API:

http://lcu.vivide.re/

如何与本地服务器交互

LCU 在每次启动客户端之后,都会在LeagueClient.exe的根目录下生成一个lockfile文件

其中内容一般是这样

1
2
3
LeagueClient:13440:27491:oL1ABkjI0LuUpP4ib55QAw:https
以冒号分开,分别是: 进程名,PID,端口号,token,协议
每次启动客户端时,除了进程名和协议都会发生变化

直接访问https://127.0.0.1:56509会要求Http Basic Authorization方式登录你需要输入如下数据

username password
riot token

登录之后首先会返回

1
{"errorCode":"RESOURCE_NOT_FOUND","httpStatus":404,"message":"Invalid function"}

因为没有向任何 API 发送请求,默认在首页返回了一个 404,并说你执行了一个无效函数,很正常。

同时,你的 token 被记录在了 HTTP 请求头的authorization属性中, 这是短时间内验证身份的一种方式。

浏览器访问网页是GET方式,所以这里找一个比较适合的例子来看看:

GET /riotclient/command-line-args

Tags: riotclient

Get the command line parameters for the application

获取 LCU 客户端的命令行参数

那么,我们访问 https://127.0.0.1:27491/riotclient/command-line-args

于是我们就得到了目前 LCU 的运行参数,这个和任务管理器里的内容是一样的。

建立游戏房间

上面只是提到了GET方法,而很多操作需要用到POST方法,甚至其PUT,DELETE

在一个非官方的 API 文档中,提及到了 LCU 客户端建立房间的方法

https://riot-api-libraries.readthedocs.io/en/latest/lcu.html#rift-explorer

其实创建房间调用了POST /lol-lobby/v2/lobby这个接口

You can create a normal game mode lobby by passing the queue ID ({"queueId":430}) to the /lol-lobby/v2/ POST endpoint.

你可以向/lol-lobby/v2/lobby 发送一个 POST 请求,当然,要携带数据{"queueId":430}

Riot 的官方文档中也给出了所有模式的queueId:

http://static.developer.riotgames.com/docs/lol/queues.json

开头提到的 5V5 训练模式,其实就是把这里的数据完善了一下,具体参数可以参考官方文档

浏览器只能GET,那怎么发送这种请求?发送的时候又要如何验证身份?

先说一下如何直接在 URL 中解决Http authorization:

访问的时候,在 URL 中加入 username:password的组合字符串,并用@连接字符串和主机地址

1
https://riot:oL1ABkjI0LuUpP4ib55QAw@127.0.0.1:27491

不论是GET还是POST,都可以这样实现携带身份访问,无需验证

那怎么发送POST请求?

以 Python 为例,有一个Requests库很适合用来进行 HTTP 通信,支持多种 HTTP 方法。

1
request = request.post(url)

这就是一个最简单的 POST 请求

其他语言也有各自类似的 HTTP 库,可以自行查阅实现

回归正题

跑的有点远了..那么回归标题,如何让熊孩子玩不了 LOL

我们可以在之前的那个网站上查到这个接口:

http://lcu.vivide.re/#operation--riotclient-ux-minimize-post

POST /riotclient/ux-minimize

Minimize the ux process and all its windows if it exists. This does not kill the ux.

最小化 LCU 客户端窗口,但并不结束相关进程

这里给出一个 Python 写法:

1
2
3
4
5
import requests

request = requests.post('https://riot:oL1ABkjI0LuUpP4ib55QAw@127.0.0.1:27491/riotclient/ux-minimize',verify=False)
# 因为证书问题,这里需要关掉SSL验证,就是verify=False. 执行后会出现一个警告,可无视
print(request)

执行之后会看到

1
<Response [204]>

同时,客户端最小化了。

那么我尝试来写一个工具,只要 LCU 在运行,就不断最小化。

核心代码

1
2
3
4
import subprocess # 读CommandLine
import psutil # 读PID和进程名
import requests # HTTP交互
# 因为涉及到一些高权限操作,需要有管理员权限才能正常运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class LCU:

def __init__(self):
'''
init params
'''
self.port = None
self.host = '127.0.0.1'
self.token = None
self.protocol = 'https'
self.url = None
self.pid = None

def checkProcess(self):
'''
check whether LCU is running, if so, return the PID. If not, return False
'''
client_pid = None
pids = psutil.pids()
for pid in pids:
if psutil.Process(pid).name() == 'LeagueClient.exe':
client_pid = pid
if isinstance(client_pid,int):
return client_pid
else:
return False

def getParam(self):
'''
From https://github.com/minhluu2000/Project-Lego
Get necessary for client-specific info through CommandLine
'''

raw_output = subprocess.check_output(
['wmic', 'PROCESS', 'WHERE', "name='LeagueClientUx.exe'", 'GET', 'commandline']).decode('gbk')
# Use GBK in case of Chinese character in path
app_port = raw_output.split('--app-port=')[-1].split(' ')[0].strip('\"')
auth_token = raw_output.split('--remoting-auth-token=')[-1].split(' ')[0].strip('\"')
url = self.protocol + '://' + 'riot:' + auth_token + '@' + self.host + ':' + app_port
return app_port, auth_token, url

def init(self):
'''
Init the program
'''
while True:
if not self.checkProcess():
print('未检测到LeagueClient.exe')
else:
self.pid = self.checkProcess()
self.port, self.token, self.url = lcu.getParam()
break

def pidCheck(self):
'''
Check whether the PID of LCU is changed(or LCU restarted)
'''
if psutil.Process(self.pid).name() == 'LeagueClient.exe':
pass
else:
print('检测到PID变化')


def ux_minimize(self):
'''
Minimize the ux process and all its windows if it exists. This does not kill the ux.
'''
rest = "/riotclient/ux-minimize"
request = requests.post(self.url + rest , verify=False)
return request.text

至于后面怎么写,看自己需求了

简单效果示范

https://www.bilibili.com/video/BV1n54y1B7M3/

LeagueClient通信(0)-LCU通讯原理&如何让熊孩子玩不了LOL

https://bakaft.github.io/2020/06/23/LeagueClient通信-0-如何让熊孩子玩不了LOL/

Author

BakaFT

Posted on

2020-06-23

Updated on

2023-12-28

Licensed under

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×