mirror of
https://github.com/Melledy/Nebula.git
synced 2025-12-12 12:24:35 +01:00
259 lines
7.4 KiB
Java
259 lines
7.4 KiB
Java
package emu.nebula.net;
|
|
|
|
import java.security.MessageDigest;
|
|
import java.util.Base64;
|
|
|
|
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
|
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
|
|
|
import emu.nebula.Nebula;
|
|
import emu.nebula.game.account.Account;
|
|
import emu.nebula.game.account.AccountHelper;
|
|
import emu.nebula.game.player.Player;
|
|
import emu.nebula.proto.Public.MailState;
|
|
import emu.nebula.proto.Public.Nil;
|
|
import emu.nebula.util.AeadHelper;
|
|
import emu.nebula.util.Utils;
|
|
import lombok.Getter;
|
|
import lombok.SneakyThrows;
|
|
import us.hebi.quickbuf.ProtoMessage;
|
|
import us.hebi.quickbuf.RepeatedByte;
|
|
|
|
@Getter
|
|
public class GameSession {
|
|
private String token;
|
|
private Account account;
|
|
private Player player;
|
|
private String ipAddress;
|
|
|
|
// Crypto
|
|
private int encryptMethod; // 0 = gcm, 1 = chacha20
|
|
private byte[] clientPublicKey;
|
|
private byte[] serverPublicKey;
|
|
private byte[] serverPrivateKey;
|
|
private byte[] key;
|
|
|
|
// Session cleanup
|
|
private boolean remove;
|
|
private long lastActiveTime;
|
|
|
|
public GameSession() {
|
|
this.updateLastActiveTime();
|
|
}
|
|
|
|
public synchronized Player getPlayer() {
|
|
return this.player;
|
|
}
|
|
|
|
public synchronized void setPlayer(Player player) {
|
|
this.player = player;
|
|
this.player.setSession(this);
|
|
this.player.onLogin();
|
|
}
|
|
|
|
public synchronized void clearPlayer() {
|
|
// Sanity check
|
|
if (this.player == null) {
|
|
return;
|
|
}
|
|
|
|
// Clear player
|
|
var player = this.player;
|
|
this.player = null;
|
|
|
|
// Remove session reference from player ONLY if their session wasn't replaced yet
|
|
if (player.getSession() == this) {
|
|
player.setSession(null);
|
|
}
|
|
|
|
// Set session removal flag
|
|
this.remove = true;
|
|
}
|
|
|
|
public synchronized boolean hasPlayer() {
|
|
return this.player != null;
|
|
}
|
|
|
|
// Encryption
|
|
|
|
public void setClientKey(RepeatedByte key) {
|
|
this.clientPublicKey = key.toArray();
|
|
}
|
|
|
|
public void generateServerKey() {
|
|
var pair = AeadHelper.generateECDHKEyPair();
|
|
|
|
this.serverPrivateKey = ((ECPrivateKeyParameters) pair.getPrivate()).getD().toByteArray();
|
|
this.serverPublicKey = ((ECPublicKeyParameters) pair.getPublic()).getQ().getEncoded(false);
|
|
}
|
|
|
|
public void calculateKey() {
|
|
this.key = AeadHelper.generateKey(clientPublicKey, serverPublicKey, serverPrivateKey);
|
|
this.encryptMethod = Utils.randomRange(0, 1);
|
|
}
|
|
|
|
public String generateToken() {
|
|
String temp = System.currentTimeMillis() + ":" + AeadHelper.generateBytes(64).toString();
|
|
|
|
try {
|
|
MessageDigest md = MessageDigest.getInstance("SHA-512");
|
|
byte[] bytes = md.digest(temp.getBytes());
|
|
|
|
this.token = Base64.getEncoder().encodeToString(bytes);
|
|
} catch (Exception e) {
|
|
this.token = Base64.getEncoder().encodeToString(temp.getBytes());
|
|
}
|
|
|
|
return this.token;
|
|
}
|
|
|
|
// Login
|
|
|
|
public boolean login(String loginToken) {
|
|
// Sanity check
|
|
if (this.account != null) {
|
|
return false;
|
|
}
|
|
|
|
// Get account
|
|
this.account = AccountHelper.getAccountByLoginToken(loginToken);
|
|
|
|
if (account == null) {
|
|
return false;
|
|
}
|
|
|
|
// Note: We should cache players in case multiple sessions try to login to the same player at the time
|
|
// Get player by account
|
|
var player = Nebula.getGameContext().getPlayerModule().loadPlayer(account);
|
|
|
|
// Skip intro
|
|
if (player == null && Nebula.getConfig().getServerOptions().skipIntro) {
|
|
player = Nebula.getGameContext().getPlayerModule().createPlayer(this, "Player", false);
|
|
}
|
|
|
|
// Set player
|
|
if (player != null) {
|
|
this.setPlayer(player);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void updateLastActiveTime() {
|
|
this.lastActiveTime = System.currentTimeMillis();
|
|
}
|
|
|
|
public void updateIpAddress(String ip) {
|
|
this.ipAddress = ip;
|
|
}
|
|
|
|
// Packet encoding helper functions
|
|
|
|
@SneakyThrows
|
|
public byte[] encodeMsg(int msgId, ProtoMessage<?> proto) {
|
|
// Check if we have any packages to send to the client
|
|
if (this.getPlayer() != null) {
|
|
// Check if player should add any packages
|
|
this.checkPlayerStates();
|
|
|
|
// Chain next packages for player
|
|
if (this.getPlayer().hasNextPackages()) {
|
|
this.addNextPackages(proto);
|
|
}
|
|
}
|
|
|
|
// Encode to message like normal
|
|
return PacketHelper.encodeMsg(msgId, proto);
|
|
}
|
|
|
|
public byte[] encodeMsg(int msgId) {
|
|
// Check if we have any packages to send to the client
|
|
if (this.getPlayer() != null) {
|
|
// Check if player should add any packages
|
|
this.checkPlayerStates();
|
|
|
|
// Chain next packages for player
|
|
if (this.getPlayer().hasNextPackages()) {
|
|
// Create a proto so we can add next packages
|
|
var proto = Nil.newInstance();
|
|
|
|
// Encode proto with next packages
|
|
return this.encodeMsg(msgId, this.addNextPackages(proto));
|
|
}
|
|
}
|
|
|
|
// Encode simple message
|
|
return PacketHelper.encodeMsg(msgId);
|
|
}
|
|
|
|
private void checkPlayerStates() {
|
|
// Update mail state flag
|
|
if (this.getPlayer().getMailbox().isNewState()) {
|
|
// Clear
|
|
this.getPlayer().getMailbox().clearNewState();
|
|
|
|
// Send mail state notify
|
|
this.getPlayer().addNextPackage(
|
|
NetMsgId.mail_state_notify,
|
|
MailState.newInstance().setNew(true));
|
|
}
|
|
|
|
// Check handbook states
|
|
this.getPlayer().getCharacters().checkPlayerState();
|
|
}
|
|
|
|
private ProtoMessage<?> addNextPackages(ProtoMessage<?> proto) {
|
|
// Sanity check and make sure proto has a "nextPackage" field
|
|
if (!PacketHelper.hasNextPackageMethod(proto)) {
|
|
return proto;
|
|
}
|
|
|
|
// Set next package
|
|
if (this.getPlayer().getNextPackages().size() > 0) {
|
|
// Set current package
|
|
NetMsgPacket curPacket = null;
|
|
|
|
// Chain link next packages
|
|
while (getPlayer().getNextPackages().size() > 0) {
|
|
// Make sure the current packet has a nextPackage field
|
|
if (curPacket != null && !PacketHelper.hasNextPackageMethod(curPacket.getProto())) {
|
|
break;
|
|
}
|
|
|
|
// Get current package
|
|
var nextPacket = getPlayer().getNextPackages().pop();
|
|
|
|
// Set cur packet if its null
|
|
if (curPacket == null) {
|
|
curPacket = nextPacket;
|
|
continue;
|
|
}
|
|
|
|
// Set next package
|
|
PacketHelper.setNextPackage(nextPacket.getProto(), curPacket.toByteArray());
|
|
|
|
// Update next packet
|
|
curPacket = nextPacket;
|
|
}
|
|
|
|
// Set next package of current proto via reflection
|
|
if (curPacket != null) {
|
|
PacketHelper.setNextPackage(proto, curPacket.toByteArray());
|
|
}
|
|
}
|
|
|
|
return proto;
|
|
}
|
|
|
|
// Misc network
|
|
|
|
/**
|
|
* Called AFTER a response is sent to the client
|
|
*/
|
|
public void afterResponse() {
|
|
if (this.getPlayer() != null) {
|
|
this.getPlayer().afterResponse();
|
|
}
|
|
}
|
|
}
|