protocollogin.lua

main 322 lines · 9.1 KB Raw
Alex Alex Commit Client Upload Part 2 19/10/2026 00:06
1-- @docclass
2ProtocolLogin = extends(Protocol, "ProtocolLogin")
3
4LoginServerError = 10
5LoginServerTokenSuccess = 12
6LoginServerTokenError = 13
7LoginServerUpdate = 17
8LoginServerMotd = 20
9LoginServerUpdateNeeded = 30
10LoginServerSessionKey = 40
11LoginServerCharacterList = 100
12LoginServerExtendedCharacterList = 101
13LoginServerProxyList = 110
14
15-- Since 10.76
16LoginServerRetry = 10
17LoginServerErrorNew = 11
18
19function ProtocolLogin:login(host, port, accountName, accountPassword, authenticatorToken, stayLogged)
20 if string.len(host) == 0 or port == nil or port == 0 then
21 signalcall(self.onLoginError, self, tr("You must enter a valid server address and port."))
22 return
23 end
24
25 self.accountName = accountName
26 self.accountPassword = accountPassword
27 self.authenticatorToken = authenticatorToken
28 self.stayLogged = stayLogged
29 self.connectCallback = self.sendLoginPacket
30
31 self:connect(host, port)
32end
33
34function ProtocolLogin:cancelLogin()
35 self:disconnect()
36end
37
38function ProtocolLogin:sendLoginPacket()
39 local msg = OutputMessage.create()
40 msg:addU8(ClientOpcodes.ClientEnterAccount)
41 msg:addU16(g_game.getOs())
42 if g_game.getCustomProtocolVersion() > 0 then
43 msg:addU16(g_game.getCustomProtocolVersion())
44 else
45 msg:addU16(g_game.getProtocolVersion())
46 end
47
48 if g_game.getFeature(GameClientVersion) then
49 msg:addU32(g_game.getClientVersion())
50 end
51
52 if g_game.getFeature(GameContentRevision) then
53 msg:addU16(g_things.getContentRevision())
54 msg:addU16(0)
55 else
56 msg:addU32(g_things.getDatSignature())
57 end
58 msg:addU32(g_sprites.getSprSignature())
59 msg:addU32(PIC_SIGNATURE)
60
61 if g_game.getFeature(GamePreviewState) then
62 msg:addU8(0)
63 end
64
65 local offset = msg:getMessageSize()
66 if g_game.getFeature(GameLoginPacketEncryption) then
67 -- first RSA byte must be 0
68 msg:addU8(0)
69
70 -- xtea key
71 self:generateXteaKey()
72 local xteaKey = self:getXteaKey()
73 msg:addU32(xteaKey[1])
74 msg:addU32(xteaKey[2])
75 msg:addU32(xteaKey[3])
76 msg:addU32(xteaKey[4])
77 end
78
79 if g_game.getFeature(GameAccountNames) then
80 msg:addString(self.accountName)
81 else
82 msg:addU32(tonumber(self.accountName))
83 end
84
85 msg:addString(self.accountPassword)
86
87 if self.getLoginExtendedData then
88 local data = self:getLoginExtendedData()
89 msg:addString(data)
90 else
91 msg:addString("OTCv8")
92 local version = g_app.getVersion():split(" ")[1]:gsub("%.", "")
93 if version:len() == 2 then
94 version = version .. "0"
95 end
96 msg:addU16(tonumber(version))
97 end
98
99 local paddingBytes = g_crypt.rsaGetSize() - (msg:getMessageSize() - offset)
100 assert(paddingBytes >= 0)
101 for i = 1, paddingBytes do
102 msg:addU8(math.random(0, 0xff))
103 end
104
105 if g_game.getFeature(GameLoginPacketEncryption) then
106 msg:encryptRsa()
107 end
108
109 if g_game.getFeature(GameOGLInformation) then
110 msg:addU8(1) --unknown
111 msg:addU8(1) --unknown
112
113 if g_game.getClientVersion() >= 1072 then
114 msg:addString(string.format('%s %s', g_graphics.getVendor(), g_graphics.getRenderer()))
115 else
116 msg:addString(g_graphics.getRenderer())
117 end
118 msg:addString(g_graphics.getVersion())
119 end
120
121 -- add RSA encrypted auth token
122 if g_game.getFeature(GameAuthenticator) then
123 offset = msg:getMessageSize()
124
125 -- first RSA byte must be 0
126 msg:addU8(0)
127 msg:addString(self.authenticatorToken)
128
129 if g_game.getFeature(GameSessionKey) then
130 msg:addU8(booleantonumber(self.stayLogged))
131 end
132
133 paddingBytes = g_crypt.rsaGetSize() - (msg:getMessageSize() - offset)
134 assert(paddingBytes >= 0)
135 for i = 1, paddingBytes do
136 msg:addU8(math.random(0, 0xff))
137 end
138
139 msg:encryptRsa()
140 end
141
142 if g_game.getFeature(GamePacketSizeU32) then
143 self:enableBigPackets()
144 end
145
146 if g_game.getFeature(GameProtocolChecksum) then
147 self:enableChecksum()
148 end
149
150 self:send(msg)
151 if g_game.getFeature(GameLoginPacketEncryption) then
152 self:enableXteaEncryption()
153 end
154 self:recv()
155end
156
157function ProtocolLogin:onConnect()
158 self.gotConnection = true
159 self:connectCallback()
160 self.connectCallback = nil
161end
162
163function ProtocolLogin:onRecv(msg)
164 while not msg:eof() do
165 local opcode = msg:getU8()
166 if opcode == LoginServerErrorNew then
167 self:parseError(msg)
168 elseif opcode == LoginServerError then
169 self:parseError(msg)
170 elseif opcode == LoginServerMotd then
171 self:parseMotd(msg)
172 elseif opcode == LoginServerUpdateNeeded then
173 signalcall(self.onLoginError, self, tr("Client needs update."))
174 elseif opcode == LoginServerTokenSuccess then
175 local unknown = msg:getU8()
176 elseif opcode == LoginServerTokenError then
177 -- TODO: prompt for token here
178 local unknown = msg:getU8()
179 signalcall(self.onLoginError, self, tr("Invalid authentification token."))
180 elseif opcode == LoginServerCharacterList then
181 self:parseCharacterList(msg)
182 elseif opcode == LoginServerExtendedCharacterList then
183 self:parseExtendedCharacterList(msg)
184 elseif opcode == LoginServerUpdate then
185 local signature = msg:getString()
186 signalcall(self.onUpdateNeeded, self, signature)
187 elseif opcode == LoginServerSessionKey then
188 self:parseSessionKey(msg)
189 elseif opcode == LoginServerProxyList then
190 local proxies = {}
191 local proxiesCount = msg:getU8()
192 for i=1, proxiesCount do
193 local host = msg:getString()
194 local port = msg:getU16()
195 local priority = msg:getU16()
196 table.insert(proxies, {host=host, port=port, priority=priority})
197 end
198 signalcall(self.onProxyList, self, proxies)
199 else
200 self:parseOpcode(opcode, msg)
201 end
202 end
203 self:disconnect()
204end
205
206function ProtocolLogin:parseError(msg)
207 local errorMessage = msg:getString()
208 signalcall(self.onLoginError, self, errorMessage)
209end
210
211function ProtocolLogin:parseMotd(msg)
212 local motd = msg:getString()
213 signalcall(self.onMotd, self, motd)
214end
215
216function ProtocolLogin:parseSessionKey(msg)
217 local sessionKey = msg:getString()
218 signalcall(self.onSessionKey, self, sessionKey)
219end
220
221function ProtocolLogin:parseCharacterList(msg)
222 local characters = {}
223
224 if g_game.getClientVersion() > 1010 then
225 local worlds = {}
226
227 local worldsCount = msg:getU8()
228 for i=1, worldsCount do
229 local world = {}
230 local worldId = msg:getU8()
231 world.worldName = msg:getString()
232 world.worldIp = msg:getString()
233 world.worldPort = msg:getU16()
234 world.previewState = msg:getU8()
235 worlds[worldId] = world
236 end
237
238 local charactersCount = msg:getU8()
239 for i = 1, charactersCount do
240 local character = {}
241 local worldId = msg:getU8()
242 character.name = msg:getString()
243 character.level = msg:getU32()
244 character.vocation = msg:getU16()
245 character.looktype = msg:getU16()
246 character.lookhead = msg:getU16()
247 character.lookbody = msg:getU16()
248 character.looklegs = msg:getU16()
249 character.lookfeet = msg:getU16()
250 character.lookaddons = msg:getU16()
251 character.lookwings = msg:getU16()
252 character.lookaura = msg:getU16()
253 character.lookshader = msg:getString()
254 character.worldName = worlds[worldId].worldName
255 character.worldIp = worlds[worldId].worldIp
256 character.worldPort = worlds[worldId].worldPort
257 character.previewState = worlds[worldId].previewState
258 characters[i] = character
259 end
260
261 else
262 local charactersCount = msg:getU8()
263 for i=1,charactersCount do
264 local character = {}
265 character.name = msg:getString()
266 character.level = msg:getU32()
267 character.vocation = msg:getU16()
268 character.looktype = msg:getU16()
269 character.lookhead = msg:getU16()
270 character.lookbody = msg:getU16()
271 character.looklegs = msg:getU16()
272 character.lookfeet = msg:getU16()
273 character.lookaddons = msg:getU16()
274 character.lookwings = msg:getU16()
275 character.lookaura = msg:getU16()
276 character.lookshader = msg:getString()
277 character.worldName = msg:getString()
278 character.worldIp = iptostring(msg:getU32())
279 character.worldPort = msg:getU16()
280
281 if g_game.getFeature(GamePreviewState) then
282 character.previewState = msg:getU8()
283 end
284
285 characters[i] = character
286 end
287 end
288
289 local account = {}
290 if g_game.getProtocolVersion() > 1077 then
291 account.status = msg:getU8()
292 account.subStatus = msg:getU8()
293
294 account.premDays = msg:getU32()
295 if account.premDays ~= 0 and account.premDays ~= 65535 then
296 account.premDays = math.floor((account.premDays - os.time()) / 86400)
297 end
298 else
299 account.status = AccountStatus.Ok
300 account.premDays = msg:getU16()
301 account.subStatus = account.premDays > 0 and SubscriptionStatus.Premium or SubscriptionStatus.Free
302 end
303
304 signalcall(self.onCharacterList, self, characters, account)
305end
306
307function ProtocolLogin:parseExtendedCharacterList(msg)
308 local characters = msg:getTable()
309 local account = msg:getTable()
310 local otui = msg:getString()
311 signalcall(self.onCharacterList, self, characters, account, otui)
312end
313
314function ProtocolLogin:parseOpcode(opcode, msg)
315 signalcall(self.onOpcode, self, opcode, msg)
316end
317
318function ProtocolLogin:onError(msg, code)
319 local text = translateNetworkError(code, self:isConnecting(), msg)
320 signalcall(self.onLoginError, self, text)
321end
322
Top