2022-04-30 09:41:17 -04:00
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2022
// www.magicbane.com
package engine.server.login ;
import engine.Enum ;
import engine.Enum.DispatchChannel ;
import engine.Enum.GameObjectType ;
2022-04-30 14:19:33 -04:00
import engine.gameManager.ConfigManager ;
2022-04-30 09:41:17 -04:00
import engine.gameManager.DbManager ;
import engine.gameManager.SessionManager ;
import engine.job.JobScheduler ;
import engine.jobs.DisconnectJob ;
import engine.net.Dispatch ;
import engine.net.DispatchMessage ;
import engine.net.NetMsgHandler ;
import engine.net.client.ClientConnection ;
import engine.net.client.Protocol ;
import engine.net.client.msg.ClientNetMsg ;
import engine.net.client.msg.ServerInfoMsg ;
import engine.net.client.msg.login.* ;
2024-03-09 20:35:00 -06:00
import engine.objects.* ;
2022-04-30 09:41:17 -04:00
import engine.server.MBServerStatics ;
import engine.session.CSSession ;
import engine.session.Session ;
import engine.util.ByteUtils ;
import engine.util.StringUtils ;
import org.pmw.tinylog.Logger ;
public class LoginServerMsgHandler implements NetMsgHandler {
private final LoginServer server ;
LoginServerMsgHandler ( LoginServer server ) {
super ();
this . server = server ;
}
2023-07-15 09:23:48 -04:00
public static void sendInvalidNameMsg ( String firstName , String lastName , int errorCode , ClientConnection clientConnection ) {
InvalidNameMsg invalidNameMessage ;
if ( firstName . length () > 256 || lastName . length () > 256 )
invalidNameMessage = new InvalidNameMsg ( firstName , lastName , errorCode );
else
invalidNameMessage = new InvalidNameMsg ( firstName , lastName , errorCode );
clientConnection . sendMsg ( invalidNameMessage );
}
2022-04-30 09:41:17 -04:00
/*
* =========================================================================
* Client Messages
* =========================================================================
*/
@Override
public boolean handleClientMsg ( ClientNetMsg clientNetMsg ) {
if ( clientNetMsg == null ) {
Logger . error ( "Recieved null msg. Returning." );
return false ;
}
ClientConnection origin = ( ClientConnection ) clientNetMsg . getOrigin ();
Protocol protocolMsg = clientNetMsg . getProtocolMsg ();
try {
switch ( protocolMsg ) {
case VERSIONINFO :
this . VerifyCorrectClientVersion (( VersionInfoMsg ) clientNetMsg );
break ;
case LOGIN :
if ( LoginServer . loginServerRunning == true )
this . Login (( ClientLoginInfoMsg ) clientNetMsg , origin );
else
this . KickToLogin ( MBServerStatics . LOGINERROR_LOGINSERVER_BUSY , "" , origin );
break ;
case KEEPALIVESERVERCLIENT :
// echo the keep alive back
origin . sendMsg ( clientNetMsg );
break ;
case SELECTSERVER :
this . SendServerInfo ( origin );
break ;
case CREATECHAR :
this . CommitNewCharacter (( CommitNewCharacterMsg ) clientNetMsg , origin );
break ;
case REMOVECHAR :
this . DeleteCharacter (( DeleteCharacterMsg ) clientNetMsg , origin );
break ;
case SELECTCHAR :
this . RequestGameServer (( GameServerIPRequestMsg ) clientNetMsg , origin );
break ;
case SETSELECTEDOBECT :
// Why is this being sent to login server?
break ;
default :
String ocHex = StringUtils . toHexString ( protocolMsg . opcode );
Logger . error ( "Cannot not handle Opcode: " + ocHex );
return false ;
}
} catch ( Exception e ) {
Logger . error ( "protocolMsg:" + protocolMsg + e . toString ());
return false ;
}
return true ;
}
private void VerifyCorrectClientVersion ( VersionInfoMsg vim ) {
ClientConnection cc ;
String cMajorVer ;
String cMinorVer ;
VersionInfoMsg outVim ;
cc = ( ClientConnection ) vim . getOrigin ();
cMajorVer = vim . getMajorVersion ();
cMinorVer = vim . getMinorVersion ();
2023-07-15 09:23:48 -04:00
if ( ! cMajorVer . equals ( this . server . getDefaultVersionInfo (). getMajorVersion ())) {
2022-04-30 09:41:17 -04:00
this . KickToLogin ( MBServerStatics . LOGINERROR_INCORRECT_CLIENT_VERSION , "Major Version Failure: " + cMajorVer , cc );
return ;
}
/* if (!cMinorVer.equals(this.server.getDefaultVersionInfo().getMinorVersion())) {
this.KickToLogin(MBServerStatics.LOGINERROR_INCORRECT_CLIENT_VERSION, "Minor Version Failure: " + cMinorVer, cc);
return;
} */
if ( cMinorVer == null ) {
this . KickToLogin ( MBServerStatics . LOGINERROR_INCORRECT_CLIENT_VERSION , "Minor Version Failure: " , cc );
return ;
}
2023-07-15 09:23:48 -04:00
if ( cMinorVer . length () < 8 || cMinorVer . length () > 16 ) {
2022-04-30 09:41:17 -04:00
this . KickToLogin ( MBServerStatics . LOGINERROR_INCORRECT_CLIENT_VERSION , "Minor Version Failure: " , cc );
return ;
}
// set MachineID for this connection
cc . machineID = cMinorVer ;
// send fake right back to the client
2023-07-15 09:23:48 -04:00
outVim = new VersionInfoMsg ( vim . getMajorVersion (), this . server . getDefaultVersionInfo (). getMinorVersion ());
2022-04-30 09:41:17 -04:00
cc . sendMsg ( outVim );
}
// our data access should be in a separate object
private void Login ( ClientLoginInfoMsg clientLoginInfoMessage , ClientConnection clientConnection ) {
// Add zero length strings to eliminate the need for null checking.
String uname = clientLoginInfoMessage . getUname ();
String pass = clientLoginInfoMessage . getPword ();
// Check to see if there is actually any data in uname.pass
if ( uname . length () == 0 ) {
this . KickToLogin ( MBServerStatics . LOGINERROR_UNABLE_TO_LOGIN , "The username provided was zero length." , clientConnection );
return ;
}
if ( pass . length () == 0 ) {
this . KickToLogin ( MBServerStatics . LOGINERROR_UNABLE_TO_LOGIN , "The password provided was zero length." , clientConnection );
return ;
}
Account account ;
account = DbManager . AccountQueries . GET_ACCOUNT ( uname );
2022-04-30 14:19:33 -04:00
// Create the account if it doesn't exist and MB_LOGIN_AUTOREG is TRUE;
// This is to support MagicBox users without a web hosting skillset.
2022-04-30 09:41:17 -04:00
if ( account == null ) {
2022-06-06 03:21:27 -04:00
if ( ConfigManager . MB_LOGIN_AUTOREG . getValue (). equalsIgnoreCase ( "false" )) {
2022-04-30 14:19:33 -04:00
this . KickToLogin ( MBServerStatics . LOGINERROR_INVALID_USERNAME_PASSWORD , "Could not find account (" + uname + ')' , clientConnection );
Logger . info ( "Could not find account (" + uname + ')' );
return ;
}
2022-04-30 09:41:17 -04:00
2023-07-15 09:23:48 -04:00
Logger . info ( "AutoRegister: " + uname + "/" + pass );
DbManager . AccountQueries . CREATE_SINGLE ( uname , pass );
account = DbManager . AccountQueries . GET_ACCOUNT ( uname );
2022-04-30 14:19:33 -04:00
if ( account == null ) {
this . KickToLogin ( MBServerStatics . LOGINERROR_INVALID_USERNAME_PASSWORD , "Could not find account (" + uname + ')' , clientConnection );
Logger . info ( "Could not auto-create (" + uname + ')' );
return ;
}
2022-04-30 09:41:17 -04:00
}
if ( account . getLastLoginFailure () + MBServerStatics . RESET_LOGIN_ATTEMPTS_AFTER < System . currentTimeMillis ())
account . resetLoginAttempts ();
// TODO: Log the login attempts IP, name, password and timestamp
// Check number invalid login attempts. If 5 or greater, kick to login.
if ( account . getLoginAttempts () >= MBServerStatics . MAX_LOGIN_ATTEMPTS ) {
this . KickToLogin ( MBServerStatics . LOGINERROR_UNABLE_TO_LOGIN , "Too many login in attempts for '" + uname + '\'' , clientConnection );
Logger . info ( "Too many login in attempts for '" + uname + '\'' );
return ;
}
if ( account . lastPasswordCheck < System . currentTimeMillis ()) {
account . lastPasswordCheck = System . currentTimeMillis () + MBServerStatics . ONE_MINUTE ;
}
// Attempt to validate login
try {
if ( ! account . passIsValid ( pass , clientConnection . getClientIpAddress (), clientConnection . machineID )) {
account . incrementLoginAttempts ();
this . KickToLogin ( MBServerStatics . LOGINERROR_INVALID_USERNAME_PASSWORD , "" , clientConnection );
Logger . info ( "Incorrect password(" + uname + ')' );
return ;
}
} catch ( IllegalArgumentException e1 ) {
this . KickToLogin ( MBServerStatics . LOGINERROR_UNABLE_TO_LOGIN , "" , clientConnection );
Logger . info ( "Failed forum account validation(" + uname + ')' );
}
// Account deactivated
if ( account . status . equals ( Enum . AccountStatus . BANNED )) {
this . KickToLogin ( MBServerStatics . LOGINERROR_NO_MORE_PLAYTIME_ON_ACCOUNT , "" , clientConnection );
return ;
}
// Check to see if we have a Session mapped with this Account:
Session session = SessionManager . getSession ( account );
// If there is, then the account is in use and must be handled:
// kick the 'other connection'
if ( session != null )
this . KickToLogin ( MBServerStatics . LOGINERROR_UNABLE_TO_LOGIN , "Your account has been accessed from a different IP & Port." , session . getConn ()); // Logout the character
// TODO implement character logout
// Get a new session
session = SessionManager . getNewSession ( account , clientConnection );
// Set Invalid Login Attempts to 0
account . resetLoginAttempts ();
// Send Login Response
ClientLoginInfoMsg loginResponse = new ClientLoginInfoMsg ( clientLoginInfoMessage );
loginResponse . setUnknown06 ( 8323072 );
loginResponse . setUnknown07 ( 3276800 );
loginResponse . setUnknown08 ( 196608 );
loginResponse . setUnknown09 (( short ) 15 );
clientConnection . sendMsg ( loginResponse );
// send character select screen
try {
this . sendCharacterSelectScreen ( session );
} catch ( Exception e ) {
Logger . error ( "Unable to Send Character Select Screen to client" );
this . KickToLogin ( MBServerStatics . LOGINERROR_UNABLE_TO_LOGIN , "Unable to send Character Select Screen to client." , clientConnection );
return ;
}
// Logging
String addyPort = clientConnection . getRemoteAddressAndPortAsString ();
int id = account . getObjectUUID ();
Logger . info ( uname + '(' + id + ") has successfully logged in from " + addyPort );
}
private void KickToLogin ( int errCode , String message , ClientConnection origin ) {
LoginErrorMsg msg = new LoginErrorMsg ( errCode , message );
PlayerCharacter player = origin . getPlayerCharacter ();
if ( player == null ) {
origin . sendMsg ( msg );
} else {
Dispatch dispatch = Dispatch . borrow ( player , msg );
DispatchMessage . dispatchMsgDispatch ( dispatch , DispatchChannel . PRIMARY );
}
Logger . info ( "Kicking to Login. Message: '" + message + '\'' );
DisconnectJob dj = new DisconnectJob ( origin );
JobScheduler . getInstance (). scheduleJob ( dj , 250 );
}
protected void sendCharacterSelectScreen ( Session s ) {
sendCharacterSelectScreen ( s , false );
}
private void sendCharacterSelectScreen ( Session s , boolean fromCommit ) {
if ( s . getAccount () != null ) {
CharSelectScreenMsg cssm = new CharSelectScreenMsg ( s , fromCommit );
s . getConn (). sendMsg ( cssm );
} else {
Logger . error ( "No Account Found: Unable to Send Character Select Screen" );
this . KickToLogin ( MBServerStatics . LOGINERROR_UNABLE_TO_LOGIN , "Unable to send Character Select Screen to client." , s . getConn ());
}
}
private void SendServerInfo ( ClientConnection conn ) {
ServerInfoMsg sim = new ServerInfoMsg ();
if ( ! conn . sendMsg ( sim )) {
Logger . error ( "Failed to send message" );
this . KickToLogin ( MBServerStatics . LOGINERROR_UNABLE_TO_LOGIN , "Unable to send ServerInfoMsg to client." , conn );
}
}
private void CommitNewCharacter ( CommitNewCharacterMsg commitNewCharacterMessage , ClientConnection clientConnection ) {
Session session = SessionManager . getSession ( clientConnection );
if ( session . getAccount () == null )
return ;
try {
// Check to see if there is an available slot.
if ( session . getAccount (). characterMap . size () >= MBServerStatics . MAX_NUM_OF_CHARACTERS ) {
this . sendCharacterSelectScreen ( session );
return ;
}
PlayerCharacter pc = PlayerCharacter . generatePCFromCommitNewCharacterMsg ( session . getAccount (), commitNewCharacterMessage , clientConnection );
if ( pc == null ) {
Logger . info ( "Player returned null while creating character." );
this . sendCharacterSelectScreen ( session , true );
return ;
}
PlayerCharacter . initializePlayer ( pc );
session . getAccount (). characterMap . putIfAbsent ( pc . getObjectUUID (), pc );
// Send back to Character Select Screen
this . sendCharacterSelectScreen ( session , true );
} catch ( Exception e ) {
Logger . error ( e );
this . sendCharacterSelectScreen ( session , true );
}
}
private void DeleteCharacter ( DeleteCharacterMsg msg , ClientConnection origin ) {
try {
2023-08-11 08:20:01 -04:00
PlayerCharacter playerCharacter ;
2022-04-30 09:41:17 -04:00
Session session ;
session = SessionManager . getSession ( origin );
2023-08-11 08:20:01 -04:00
playerCharacter = PlayerCharacter . getPlayerCharacter ( msg . getCharacterUUID ());
2022-04-30 09:41:17 -04:00
2023-08-11 08:20:01 -04:00
if ( playerCharacter == null ) {
2022-04-30 09:41:17 -04:00
Logger . error ( "Delete Error: PlayerID=" + msg . getCharacterUUID () + " not found." );
this . sendCharacterSelectScreen ( session );
return ;
}
if ( session . getAccount () == null ) {
Logger . error ( "Delete Error: Account not found." );
this . sendCharacterSelectScreen ( session );
return ;
}
2023-08-11 08:20:01 -04:00
if ( playerCharacter . getAccount (). equals ( session . getAccount ()) == false ) {
Logger . error ( "Delete Error: Character " + playerCharacter . getName () + " does not belong to account " + origin . getAccount (). getUname ());
2022-04-30 09:41:17 -04:00
this . sendCharacterSelectScreen ( session );
return ;
}
//Can't delete as Guild Leader
//TODO either find an error or just gdisband.
2023-08-11 08:20:01 -04:00
if ( GuildStatusController . isGuildLeader ( playerCharacter . getGuildStatus ())) {
2022-04-30 09:41:17 -04:00
this . KickToLogin ( MBServerStatics . LOGINERROR_UNABLE_TO_LOGIN , "Cannot delete a guild leader." , origin );
return ;
}
// check for active banes
2023-08-11 08:20:01 -04:00
if ( LoginServer . getActiveBaneQuery ( playerCharacter )) {
Logger . info ( "Character " + playerCharacter . getName () + " has unresolved bane" );
2022-04-30 09:41:17 -04:00
this . KickToLogin ( MBServerStatics . LOGINERROR_UNABLE_TO_LOGIN , "Player has unresolved bane." , origin );
return ;
}
2023-08-11 08:20:01 -04:00
playerCharacter . getAccount (). characterMap . remove ( playerCharacter . getObjectUUID ());
playerCharacter . deactivateCharacter ();
2022-04-30 09:41:17 -04:00
// TODO Delete Equipment
// Resend Character Select Screen.
this . sendCharacterSelectScreen ( session );
} catch ( Exception e ) {
Logger . error ( e );
}
}
private void RequestGameServer ( GameServerIPRequestMsg gameServerIPRequestMessage , ClientConnection conn ) {
Session session ;
PlayerCharacter player ;
session = SessionManager . getSession ( conn );
player = ( PlayerCharacter ) DbManager . getObject ( GameObjectType . PlayerCharacter , gameServerIPRequestMessage . getCharacterUUID ());
if ( player == null ) {
Logger . info ( "Unable to find character ID " + gameServerIPRequestMessage . getCharacterUUID ());
this . KickToLogin ( MBServerStatics . LOGINERROR_UNABLE_TO_LOGIN , "PlayerCharacter lookup failed in .RequestGameServer()." , conn );
return ;
}
try {
if ( ! CSSession . updateCrossServerSession ( ByteUtils . byteArrayToSafeStringHex ( conn . getSecretKeyBytes ()), gameServerIPRequestMessage . getCharacterUUID ())) {
Logger . info ( "Failed to update Cross server session, Kicking to Login for Character " + player . getObjectUUID ());
this . KickToLogin ( MBServerStatics . LOGINERROR_UNABLE_TO_LOGIN , "Failed to update Session Information" , conn );
return ;
}
} catch ( Exception e ) {
Logger . info ( "Failed to update Cross server session, Kicking to Login for Character " + player . getObjectUUID ());
Logger . error ( e );
}
// Set the last character used.
Account account = session . getAccount ();
account . setLastCharIDUsed ( gameServerIPRequestMessage . getCharacterUUID ());
2023-05-18 08:10:54 -04:00
GameServerIPResponseMsg gameServerIPResponseMsg = new GameServerIPResponseMsg ();
2022-04-30 09:41:17 -04:00
2023-05-18 08:10:54 -04:00
if ( ! conn . sendMsg ( gameServerIPResponseMsg )) {
2022-04-30 09:41:17 -04:00
Logger . error ( "Failed to send message" );
this . KickToLogin ( MBServerStatics . LOGINERROR_UNABLE_TO_LOGIN , "Unable to send GameServerIPResponseMsg to client." , conn );
}
}
}