/ login-server / source / service / GameServerClient.ts
GameServerClient.ts
  1  import { Socket } from 'net'
  2  import { GameServerManager } from './GameServerManager'
  3  import { LoginManager } from '../cache/LoginManager'
  4  import { BanManager } from '../cache/BanManager'
  5  import { RpcManager } from '../rpc/manager'
  6  import {
  7      L2GameServerParameters,
  8      L2LoginAccountBanEvent,
  9      L2LoginIPBanEvent,
 10      L2LoginPlayerAuthenticationEvent,
 11      L2LoginPlayerChangesAccessLevelEvent,
 12      L2LoginPlayerChangesPasswordEvent,
 13      L2LoginPlayerCharactersOnServerEvent,
 14      L2LoginPlayerLoginEvent,
 15      L2LoginPlayerLogoutEvent,
 16      L2LoginRegisterServerEvent,
 17      L2LoginServerEvent,
 18      L2LoginServerOperations,
 19      L2LoginServerStatusEvent,
 20  } from '../rpc/interface/LoginServerOperations'
 21  import {
 22      L2GameLoginServerErrorEvent,
 23      L2GamePlayerAuthenticationOutcomeEvent,
 24      L2GamePlayerChangesPasswordOutcomeEvent,
 25      L2GameRequestPlayerCharactersEvent,
 26      L2GameRequestPlayerDisconnectEvent,
 27      L2GameServerEvent,
 28      L2GameServerOperations,
 29      L2GameServerRegisteredOutcomeEvent,
 30  } from '../rpc/interface/GameServerOperations'
 31  import { SessionKeyHelper } from './SessionKey'
 32  import { DatabaseManager } from '../database/manager'
 33  import { ConfigManager } from '../config/ConfigManager'
 34  import { ConnectedGameServer } from '../models/RegisteredGameServer'
 35  import { AvailableServerNames } from '../config/serverNames'
 36  import { L2LoginClient } from './L2LoginClient'
 37  import _ from 'lodash'
 38  import to from 'await-to-js'
 39  import { ServerLog } from '../logger/Logger'
 40  import { HashVersion, validatePasswordHash } from '../security/HashHelper'
 41  
 42  export class L2GameServerClient {
 43      connection: Socket
 44      connectionHash: string
 45      accountsOnGameServer: Set<string> = new Set<string>()
 46      serverProperties: L2GameServerParameters
 47  
 48      constructor( connection : Socket ) {
 49          /*
 50              Disabling Nagle's algorithm delay before sending packet data.
 51           */
 52          connection.setNoDelay( true )
 53  
 54          this.connection = connection
 55          this.connectionHash = this.connection.remoteAddress
 56  
 57          this.connection.on( 'data', this.onProcessData.bind( this ) )
 58          this.connection.on( 'close', this.onProcessClose.bind( this ) )
 59          this.connection.on( 'error', this.onProcessError.bind( this ) )
 60      }
 61  
 62      async onProcessData( incomingData: Buffer ): Promise<void> {
 63          try {
 64              const events: Array<L2LoginServerEvent> = RpcManager.getDataConverter().unpack<L2LoginServerEvent>( incomingData )
 65              await Promise.all( events.map( event => this.onLoginServerEvent( event ) ) )
 66          } catch ( error ) {
 67              ServerLog.fatal( error, 'Failed to process game server event' )
 68          }
 69      }
 70  
 71      private async onLoginServerEvent( result: L2LoginServerEvent ) : Promise<void> {
 72          switch ( result.operationId ) {
 73              case L2LoginServerOperations.RegisterServer:
 74                  const { serverId } = result as L2LoginRegisterServerEvent
 75                  if ( this.serverProperties && this.serverProperties.serverId !== serverId ) {
 76                      let error: L2GameLoginServerErrorEvent = {
 77                          operationId: L2GameServerOperations.LoginServerError,
 78                          reason: `Cannot re-register game server on already occupied serverId=${serverId}`,
 79                      }
 80  
 81                      return this.sendEvent( error )
 82                  }
 83  
 84                  this.processServerRegistration( result as L2LoginRegisterServerEvent )
 85                  this.sendGameServerUpdates()
 86                  this.requestConnectedClientCharacters()
 87  
 88                  return
 89  
 90              case L2LoginServerOperations.AccountLogin:
 91                  this.addAccountOnGameServer( ( result as L2LoginPlayerLoginEvent ).accountName )
 92                  return
 93  
 94              case L2LoginServerOperations.AccountLogout:
 95                  this.accountsOnGameServer.delete( ( result as L2LoginPlayerLogoutEvent ).accountName )
 96                  return
 97  
 98              case L2LoginServerOperations.AccountStatus:
 99                  const { accountName, level } = result as L2LoginPlayerChangesAccessLevelEvent
100                  return LoginManager.setAccountAccessLevel( accountName, level )
101  
102              case L2LoginServerOperations.AccountAuthentication:
103                  const authenticationEvent = result as L2LoginPlayerAuthenticationEvent
104                  const existingKey = LoginManager.getKeyForAccount( authenticationEvent.accountName )
105                  const response: L2GamePlayerAuthenticationOutcomeEvent = {
106                      isSuccess: true,
107                      operationId: L2GameServerOperations.PlayerAuthenticationOutcome,
108                      accountName: authenticationEvent.accountName,
109                  }
110  
111                  if ( existingKey && SessionKeyHelper.isSame( existingKey, authenticationEvent.key ) ) {
112  
113                      LoginManager.removeLoggedInClient( authenticationEvent.accountName )
114                      return this.sendEvent( response )
115                  }
116  
117                  response.isSuccess = false
118                  return this.sendEvent( response )
119  
120              case L2LoginServerOperations.ServerStatus:
121                  this.serverProperties = result as L2LoginServerStatusEvent
122                  this.sendGameServerUpdates()
123  
124                  return
125  
126  
127              case L2LoginServerOperations.PlayerCharactersOnServer:
128                  LoginManager.setCharactersOnServer( result as L2LoginPlayerCharactersOnServerEvent, this.serverProperties.serverId )
129                  return
130  
131              case L2LoginServerOperations.AccountBan:
132                  const accountBanData = result as L2LoginAccountBanEvent
133                  BanManager.addBanExpiration( accountBanData.accountName, accountBanData.expirationTime )
134                  return
135  
136              case L2LoginServerOperations.IPBan:
137                  const ipBanData = result as L2LoginIPBanEvent
138                  BanManager.addBanExpiration( ipBanData.ipAddress, ipBanData.expirationTime )
139                  return
140  
141              case L2LoginServerOperations.AccountChangesPassword:
142                  const passwordEvent = result as L2LoginPlayerChangesPasswordEvent
143                  const playerPasswordOutcome: L2GamePlayerChangesPasswordOutcomeEvent = {
144                      isSuccess: false,
145                      operationId: L2GameServerOperations.AccountChangesPasswordOutcome,
146                      message: undefined,
147                      accountName: passwordEvent.accountName,
148                  }
149  
150                  if ( !this.hasAccountOnGameServer( passwordEvent.accountName ) ) {
151                      playerPasswordOutcome.message = 'Cannot change player password at this time.'
152                      return this.sendEvent( playerPasswordOutcome )
153                  }
154  
155                  if ( !passwordEvent.currentPassword || !passwordEvent.futurePassword ) {
156                      playerPasswordOutcome.message = 'Empty password data! Try again.'
157                      return this.sendEvent( playerPasswordOutcome )
158                  }
159  
160                  if ( !HashVersion[ passwordEvent.hashVersion ] ) {
161                      playerPasswordOutcome.message = 'Unsupported password hash version.'
162                      return this.sendEvent( playerPasswordOutcome )
163                  }
164  
165                  const [ validationError ] = await to( validatePasswordHash( passwordEvent.hashVersion, passwordEvent.currentPassword, '' ) )
166                  if ( validationError ) {
167                      playerPasswordOutcome.message = 'Unsupported hash parameters.'
168                      return this.sendEvent( playerPasswordOutcome )
169                  }
170  
171                  const [ error ] = await to( DatabaseManager.getAccounts().updateAccountPassword( passwordEvent.accountName, passwordEvent.futurePassword, passwordEvent.hashVersion ) )
172  
173                  if ( error ) {
174                      ServerLog.error( error, 'Failed updating account password via PlayerChangesPassword event from game server' )
175  
176                      playerPasswordOutcome.message = 'The password change was unsuccessful!'
177                      return this.sendEvent( playerPasswordOutcome )
178                  }
179  
180                  playerPasswordOutcome.message = 'You have successfully changed your password!'
181                  playerPasswordOutcome.isSuccess = true
182  
183                  return this.sendEvent( playerPasswordOutcome )
184          }
185  
186          const reason = `Unknown operationId ${ result.operationId } in GameServerClient.onProcessData from game server, closing connection!`
187          ServerLog.error( reason )
188  
189          const error: L2GameLoginServerErrorEvent = {
190              operationId: L2GameServerOperations.LoginServerError,
191              reason,
192          }
193  
194          return this.closeConnectionEvent( error )
195      }
196  
197      onProcessClose() {
198          const name = AvailableServerNames[ this.serverProperties?.serverId ] ?? 'Unknown'
199          ServerLog.warn( 'Game Server \'%s\' disconnected.', name )
200          GameServerManager.removeClient( this.connectionHash )
201      }
202  
203      onProcessError() {
204          GameServerManager.removeClient( this.connectionHash )
205      }
206  
207      getConnectionHash() {
208          return this.connectionHash
209      }
210  
211      sendEvent( event : L2GameServerEvent ) {
212          this.connection.write( RpcManager.getDataConverter().pack( event ) )
213      }
214  
215      closeConnectionEvent( event : L2GameServerEvent ) {
216          this.connection.end( RpcManager.getDataConverter().pack( event ) )
217      }
218  
219      getServerProperties(): L2GameServerParameters {
220          return this.serverProperties
221      }
222  
223      addAccountOnGameServer( accountName: string ) {
224          this.accountsOnGameServer.add( accountName )
225      }
226  
227      hasAccountOnGameServer( accountName: string ) {
228          return this.accountsOnGameServer.has( accountName )
229      }
230  
231      getPlayerCount() {
232          return this.accountsOnGameServer.size
233      }
234  
235      attemptAutoRegistration( data: L2LoginRegisterServerEvent ): void {
236          let acceptedServerId: number = GameServerManager.registerWithFirstAvailableId( this, data )
237          if ( acceptedServerId === 0 ) {
238              let error: L2GameLoginServerErrorEvent = {
239                  operationId: L2GameServerOperations.LoginServerError,
240                  reason: 'No server slots available to register!',
241              }
242  
243              return this.sendEvent( error )
244          }
245  
246          this.serverProperties = structuredClone( data )
247          this.serverProperties.serverId = acceptedServerId
248  
249          let event: L2GameServerRegisteredOutcomeEvent = {
250              serverName: AvailableServerNames[ this.serverProperties.serverId ],
251              acceptedServerId: this.serverProperties.serverId,
252              operationId: L2GameServerOperations.ServerRegisteredOutcome,
253          }
254  
255          return this.sendEvent( event )
256      }
257  
258      attemptDirectRegistration( data: L2LoginRegisterServerEvent ): void {
259          if ( !GameServerManager.registerServer( this, data ) ) {
260              let error: L2GameLoginServerErrorEvent = {
261                  operationId: L2GameServerOperations.LoginServerError,
262                  reason: `Cannot update information for ServerId ${ data.serverId }!`,
263              }
264  
265              return this.sendEvent( error )
266          }
267  
268          this.serverProperties = structuredClone( data )
269  
270          let event: L2GameServerRegisteredOutcomeEvent = {
271              serverName: AvailableServerNames[ data.serverId ],
272              acceptedServerId: data.serverId,
273              operationId: L2GameServerOperations.ServerRegisteredOutcome,
274          }
275  
276          return this.sendEvent( event )
277      }
278  
279      processServerRegistration( data: L2LoginRegisterServerEvent ): void {
280          let existingInformation: ConnectedGameServer = GameServerManager.getRegisteredGameServerById( data.serverId )
281  
282          if ( this.serverProperties ) {
283              if ( existingInformation.host === data.ip.join( '.' )
284                      && existingInformation.port === data.port
285                      && existingInformation.serverId === data.serverId ) {
286  
287                  return this.attemptDirectRegistration( data )
288              }
289  
290              if ( ConfigManager.server.isGameServerAutoAssignId() && data.isAcceptAlternativeId ) {
291                  return this.attemptAutoRegistration( data )
292              }
293  
294              return
295          }
296  
297          if ( !AvailableServerNames[ data.serverId ] ) {
298              let error: L2GameLoginServerErrorEvent = {
299                  operationId: L2GameServerOperations.LoginServerError,
300                  reason: `ServerId ${ data.serverId } does not correspond to valid set of ids!`,
301              }
302  
303              return this.sendEvent( error )
304          }
305  
306          if ( !GameServerManager.canRegister( data.serverId ) ) {
307              if ( ConfigManager.server.isGameServerAutoAssignId() && data.isAcceptAlternativeId ) {
308                  return this.attemptAutoRegistration( data )
309              }
310  
311              let error: L2GameLoginServerErrorEvent = {
312                  operationId: L2GameServerOperations.LoginServerError,
313                  reason: `ServerId ${ data.serverId } has already been claimed!`,
314              }
315  
316              return this.sendEvent( error )
317          }
318  
319          return this.attemptDirectRegistration( data )
320      }
321  
322      requestServerCharacters( accountName: string ): void {
323          let data: L2GameRequestPlayerCharactersEvent = {
324              accountName,
325              operationId: L2GameServerOperations.RequestPlayerCharacters,
326          }
327  
328          return this.sendEvent( data )
329      }
330  
331      disconnectAccount( accountName: string ): void {
332          let data: L2GameRequestPlayerDisconnectEvent = {
333              accountName,
334              operationId: L2GameServerOperations.RequestPlayerDisconnect,
335          }
336  
337          return this.sendEvent( data )
338      }
339  
340      sendGameServerUpdates() {
341          _.each( LoginManager.getLoggedInClients(), ( client : L2LoginClient ) => {
342              if ( client.enableServerListUpdates ) {
343                  client.sendServerList()
344              }
345          } )
346      }
347  
348      requestConnectedClientCharacters() : void {
349          for ( const client of Object.values( LoginManager.getLoggedInClients() ) ) {
350              if ( client.enableServerListUpdates ) {
351                  this.requestServerCharacters( client.getAccountName() )
352              }
353          }
354      }
355  }