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 }