You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
350 lines
12 KiB
350 lines
12 KiB
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . |
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· |
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ |
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ |
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ |
|
// Magicbane Emulator Project © 2013 - 2022 |
|
// www.magicbane.com |
|
|
|
|
|
package engine.net.client; |
|
|
|
import engine.Enum; |
|
import engine.gameManager.ConfigManager; |
|
import engine.gameManager.SessionManager; |
|
import engine.job.JobScheduler; |
|
import engine.jobs.DisconnectJob; |
|
import engine.net.AbstractConnection; |
|
import engine.net.AbstractNetMsg; |
|
import engine.net.Network; |
|
import engine.net.client.msg.ClientNetMsg; |
|
import engine.net.client.msg.login.LoginErrorMsg; |
|
import engine.objects.Account; |
|
import engine.objects.PlayerCharacter; |
|
import engine.server.MBServerStatics; |
|
import engine.session.SessionID; |
|
import org.pmw.tinylog.Logger; |
|
|
|
import java.io.IOException; |
|
import java.nio.ByteBuffer; |
|
import java.nio.channels.ClosedChannelException; |
|
import java.nio.channels.NotYetConnectedException; |
|
import java.nio.channels.SocketChannel; |
|
import java.util.concurrent.locks.ReentrantLock; |
|
|
|
public class ClientConnection extends AbstractConnection { |
|
|
|
private final ClientAuthenticator crypto; |
|
private final String clientIpAddress; |
|
public String machineID; |
|
public long guildtreespam = 0; |
|
public long ordernpcspam = 0; |
|
public ReentrantLock trainLock = new ReentrantLock(); |
|
public ReentrantLock sellLock = new ReentrantLock(); |
|
public ReentrantLock buyLock = new ReentrantLock(); |
|
public boolean desyncDebug = false; |
|
public byte[] lastByteBuffer; |
|
protected SessionID sessionID = null; |
|
private byte cryptoInitTries = 0; |
|
|
|
public ClientConnection(ClientConnectionManager connMan, |
|
SocketChannel sockChan) { |
|
super(connMan, sockChan, true); |
|
this.crypto = new ClientAuthenticator(this); |
|
|
|
this.clientIpAddress = sockChan.socket().getRemoteSocketAddress() |
|
.toString().replace("/", "").split(":")[0]; |
|
} |
|
|
|
@Override |
|
protected boolean _sendMsg(AbstractNetMsg msg) { |
|
try { |
|
msg.setOrigin(this); |
|
ByteBuffer bb = msg.serialize(); |
|
|
|
// Application protocol logging toggled via |
|
// DevCmd: netdebug on|off |
|
|
|
if (MBServerStatics.DEBUG_PROTOCOL) |
|
applicationProtocolLogger(msg, MessageSource.SOURCE_SERVER); |
|
|
|
boolean retval = this.sendBB(bb); |
|
Network.byteBufferPool.putBuffer(bb);//return here. |
|
|
|
return retval; |
|
|
|
} catch (Exception e) { // Catch-all |
|
Logger.error(e); |
|
return false; |
|
} |
|
} |
|
|
|
/** |
|
* Sending a NetMsg to the client involves NOT including a dataLen parameter |
|
* and also involves encrypting the data. |
|
*/ |
|
@Override |
|
protected boolean _sendBB(ByteBuffer bb) { |
|
boolean useCrypto = this.crypto.initialized(); |
|
boolean retVal; |
|
|
|
// Logger.debug("useCrypto: " + useCrypto + ". bb.cap(): " + |
|
// bb.capacity()); |
|
if (useCrypto == false) |
|
retVal = super._sendBB(bb); |
|
else { |
|
if (bb == null) |
|
Logger.error("Incoming bb is null"); |
|
ByteBuffer encrypted = Network.byteBufferPool.getBufferToFit(bb.capacity()); |
|
if (encrypted == null) |
|
Logger.error("Encrypted bb is null"); |
|
this.crypto.encrypt(bb, encrypted); |
|
retVal = super._sendBB(encrypted); |
|
} |
|
|
|
return retVal; |
|
} |
|
|
|
/** |
|
* Receiving data from a client involves the initial Crypto Key Exchange, |
|
* waiting for a complete NetMsg to arrive using an accumulation factory and |
|
* decrypting the data. |
|
*/ |
|
//FIXME the int return value on this means nothing! Clean it up! |
|
@Override |
|
protected int read() { |
|
|
|
if (readLock.tryLock()) |
|
try { |
|
|
|
// First and foremost, check to see if we the Crypto is initted yet |
|
if (!this.crypto.initialized()) |
|
this.crypto.initialize(this); |
|
|
|
if (!this.crypto.initialized()) { |
|
++this.cryptoInitTries; |
|
if (this.cryptoInitTries >= MBServerStatics.MAX_CRYPTO_INIT_TRIES) { |
|
Logger.info("Failed to initialize after " |
|
+ MBServerStatics.MAX_CRYPTO_INIT_TRIES |
|
+ " tries. Disconnecting."); |
|
this.disconnect(); |
|
} |
|
return 0; |
|
} |
|
|
|
// check to see if SessionID == null; |
|
if (this.sessionID == null) |
|
this.sessionID = new SessionID(this.crypto.getSecretKeyBytes()); |
|
|
|
// Get ByteBuffers out of pool. |
|
ByteBuffer bb = Network.byteBufferPool.getBuffer(16); |
|
ByteBuffer decrypted = Network.byteBufferPool.getBuffer(16); |
|
// ByteBuffer bb = ByteBuffer.allocate(1024 * 4); |
|
|
|
int totalBytesRead = 0; |
|
int lastRead = 0; |
|
do { |
|
try { |
|
bb.clear(); |
|
decrypted.clear(); |
|
lastRead = this.sockChan.read(bb); |
|
// On EOF on the SocketChannel, disconnect. |
|
if (lastRead <= -1) { |
|
this.disconnect(); |
|
break; |
|
} |
|
|
|
if (lastRead == 0) |
|
continue; |
|
|
|
// ByteBuffer decrypted = ByteBuffer.allocate(lastRead); |
|
this.crypto.decrypt(bb, decrypted); |
|
this.factory.addData(decrypted); |
|
|
|
|
|
this.checkInternalFactory(); |
|
|
|
totalBytesRead += lastRead; |
|
|
|
|
|
} catch (NotYetConnectedException e) { |
|
Logger.error(e.getLocalizedMessage()); |
|
totalBytesRead = -1; // Set error retVal |
|
break; |
|
|
|
} catch (ClosedChannelException e) { |
|
// TODO Should a closed channel be logged or just cleaned up? |
|
// this.logEXCEPTION(e); |
|
this.disconnect(); |
|
totalBytesRead = -1; // Set error retVal |
|
break; |
|
|
|
} catch (IOException e) { |
|
if (e.getLocalizedMessage() != null && (!e.getLocalizedMessage().equals(MBServerStatics.EXISTING_CONNECTION_CLOSED) && !e.getLocalizedMessage().equals(MBServerStatics.RESET_BY_PEER))) { |
|
Logger.info("Error Reading message opcode " + this.lastOpcode); |
|
Logger.error(e); |
|
} |
|
this.disconnect(); |
|
totalBytesRead = -1; // Set error retVal |
|
break; |
|
|
|
} catch (Exception e) { |
|
Logger.info("Error Reading message opcode " + this.lastOpcode); |
|
Logger.error(e); |
|
totalBytesRead = -1; // Set error retVal |
|
this.disconnect(); |
|
break; |
|
} |
|
} |
|
|
|
while (lastRead > 0); |
|
|
|
Network.byteBufferPool.putBuffer(bb); |
|
Network.byteBufferPool.putBuffer(decrypted); |
|
|
|
return totalBytesRead; |
|
|
|
} finally { |
|
readLock.unlock(); |
|
} |
|
else { |
|
Logger.debug("Another thread already has a read lock! Skipping."); |
|
return 0; |
|
} |
|
} |
|
|
|
@Override |
|
public void disconnect() { |
|
super.disconnect(); |
|
try { |
|
|
|
if (ConfigManager.serverType.equals(Enum.ServerType.WORLDSERVER)) |
|
ConfigManager.worldServer.removeClient(this); |
|
else |
|
ConfigManager.loginServer.removeClient(this); |
|
|
|
// TODO There has to be a more direct way to do this... |
|
SessionManager.remSession( |
|
SessionManager.getSession(sessionID)); |
|
} catch (NullPointerException e) { |
|
Logger |
|
.error( |
|
"Tried to remove improperly initialized session. Skipping." + |
|
e); |
|
} |
|
} |
|
|
|
public void forceDisconnect() { |
|
super.disconnect(); |
|
} |
|
|
|
public SessionID getSessionID() { |
|
return sessionID; |
|
} |
|
|
|
/* |
|
* Getters n setters |
|
*/ |
|
|
|
public byte[] getSecretKeyBytes() { |
|
return this.crypto.getSecretKeyBytes(); |
|
} |
|
|
|
/* |
|
* Convenience getters for SessionManager |
|
*/ |
|
public Account getAccount() { |
|
return SessionManager.getAccount(this); |
|
} |
|
|
|
public PlayerCharacter getPlayerCharacter() { |
|
return SessionManager.getPlayerCharacter(this); |
|
} |
|
|
|
@Override |
|
public boolean handleClientMsg(ClientNetMsg msg) { |
|
|
|
Protocol protocolMsg = msg.getProtocolMsg(); |
|
|
|
switch (protocolMsg) { |
|
case KEEPALIVESERVERCLIENT: |
|
this.setLastKeepAliveTime(); |
|
break; |
|
// case ClientOpcodes.OpenVault: |
|
// case ClientOpcodes.Random: |
|
// case ClientOpcodes.DoorTryOpen: |
|
// case ClientOpcodes.SetSelectedObect: |
|
// case ClientOpcodes.MoveObjectToContainer: |
|
// case ClientOpcodes.ToggleSitStand: |
|
// case ClientOpcodes.SocialChannel: |
|
// case ClientOpcodes.OpenFriendsCondemnList: |
|
case SELLOBJECT: |
|
this.setLastMsgTime(); |
|
break; |
|
case MOVETOPOINT: |
|
case ARCCOMBATMODEATTACKING: |
|
this.setLastMsgTime(); |
|
break; |
|
default: |
|
this.setLastMsgTime(); |
|
break; |
|
} |
|
|
|
// Application protocol logging toggled via |
|
// DevCmd: netdebug on|off |
|
|
|
if (MBServerStatics.DEBUG_PROTOCOL) |
|
applicationProtocolLogger(msg, MessageSource.SOURCE_CLIENT); |
|
|
|
return ConfigManager.handler.handleClientMsg(msg); // *** Refactor : Null check then call |
|
} |
|
|
|
private void applicationProtocolLogger(AbstractNetMsg msg, MessageSource origin) { |
|
|
|
String outString = ""; |
|
PlayerCharacter tempPlayer = null; |
|
|
|
// Log the protocolMsg |
|
if (origin == MessageSource.SOURCE_CLIENT) |
|
outString = " Incoming protocolMsg: "; |
|
else |
|
outString = " Outgoing protocolMsg: "; |
|
|
|
Logger.info(outString |
|
+ Integer.toHexString(msg.getProtocolMsg().opcode) |
|
+ '/' + msg.getProtocolMsg()); |
|
|
|
// Dump message contents using reflection |
|
tempPlayer = this.getPlayerCharacter(); |
|
outString = ""; |
|
outString += (tempPlayer == null) ? "PlayerUnknown" : tempPlayer.getFirstName() + ' ' |
|
+ msg.toString(); |
|
Logger.info(outString); |
|
} |
|
// Method logs detailed information about application |
|
// protocol traffic. Toggled at runtime via the |
|
// DevCmd netdebug on|off |
|
|
|
public void kickToLogin(int errCode, String message) { |
|
|
|
LoginErrorMsg lom = new LoginErrorMsg(errCode, message); |
|
|
|
if (!sendMsg(lom)) |
|
Logger.error("Failed to send message"); // TODO Do we just accept this failure to send Msg? |
|
|
|
DisconnectJob dj = new DisconnectJob(this); |
|
JobScheduler.getInstance().scheduleJob(dj, 250); |
|
|
|
} |
|
|
|
public final String getClientIpAddress() { |
|
return this.clientIpAddress; |
|
} |
|
|
|
// Enumeration of a message's origin for logging purposes |
|
private enum MessageSource { |
|
|
|
SOURCE_CLIENT, |
|
SOURCE_SERVER |
|
} |
|
}
|
|
|