mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-12-17 17:34:39 +01:00
292 lines
8.9 KiB
Java
292 lines
8.9 KiB
Java
package emu.grasscutter.server.game;
|
|
|
|
import java.io.File;
|
|
import java.net.InetSocketAddress;
|
|
import java.util.Set;
|
|
import emu.grasscutter.Grasscutter;
|
|
import emu.grasscutter.Grasscutter.ServerDebugMode;
|
|
import emu.grasscutter.game.Account;
|
|
import emu.grasscutter.game.player.Player;
|
|
import emu.grasscutter.net.packet.BasePacket;
|
|
import emu.grasscutter.net.packet.PacketOpcodes;
|
|
import emu.grasscutter.net.packet.PacketOpcodesUtils;
|
|
import emu.grasscutter.server.event.game.SendPacketEvent;
|
|
import emu.grasscutter.utils.Crypto;
|
|
import emu.grasscutter.utils.FileUtils;
|
|
import emu.grasscutter.utils.Utils;
|
|
import io.netty.buffer.ByteBuf;
|
|
import io.netty.buffer.Unpooled;
|
|
|
|
import static emu.grasscutter.config.Configuration.*;
|
|
import static emu.grasscutter.utils.Language.translate;
|
|
|
|
public class GameSession implements GameSessionManager.KcpChannel {
|
|
private final GameServer server;
|
|
private GameSessionManager.KcpTunnel tunnel;
|
|
|
|
private Account account;
|
|
private Player player;
|
|
|
|
private boolean useSecretKey;
|
|
private SessionState state;
|
|
|
|
private int clientTime;
|
|
private long lastPingTime;
|
|
private int lastClientSeq = 10;
|
|
|
|
public GameSession(GameServer server) {
|
|
this.server = server;
|
|
this.state = SessionState.WAITING_FOR_TOKEN;
|
|
this.lastPingTime = System.currentTimeMillis();
|
|
}
|
|
|
|
public GameServer getServer() {
|
|
return server;
|
|
}
|
|
|
|
public InetSocketAddress getAddress() {
|
|
try {
|
|
return tunnel.getAddress();
|
|
}catch (Throwable ignore) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public boolean useSecretKey() {
|
|
return useSecretKey;
|
|
}
|
|
|
|
public Account getAccount() {
|
|
return account;
|
|
}
|
|
|
|
public void setAccount(Account account) {
|
|
this.account = account;
|
|
}
|
|
|
|
public String getAccountId() {
|
|
return this.getAccount().getId();
|
|
}
|
|
|
|
public Player getPlayer() {
|
|
return player;
|
|
}
|
|
|
|
public synchronized void setPlayer(Player player) {
|
|
this.player = player;
|
|
this.player.setSession(this);
|
|
this.player.setAccount(this.getAccount());
|
|
}
|
|
|
|
public SessionState getState() {
|
|
return state;
|
|
}
|
|
|
|
public void setState(SessionState state) {
|
|
this.state = state;
|
|
}
|
|
|
|
public boolean isLoggedIn() {
|
|
return this.getPlayer() != null;
|
|
}
|
|
|
|
public void setUseSecretKey(boolean useSecretKey) {
|
|
this.useSecretKey = useSecretKey;
|
|
}
|
|
|
|
public int getClientTime() {
|
|
return this.clientTime;
|
|
}
|
|
|
|
public long getLastPingTime() {
|
|
return lastPingTime;
|
|
}
|
|
|
|
public void updateLastPingTime(int clientTime) {
|
|
this.clientTime = clientTime;
|
|
this.lastPingTime = System.currentTimeMillis();
|
|
}
|
|
|
|
public int getNextClientSequence() {
|
|
return ++lastClientSeq;
|
|
}
|
|
|
|
public void replayPacket(int opcode, String name) {
|
|
String filePath = PACKET(name);
|
|
File p = new File(filePath);
|
|
|
|
if (!p.exists()) return;
|
|
|
|
byte[] packet = FileUtils.read(p);
|
|
|
|
BasePacket basePacket = new BasePacket(opcode);
|
|
basePacket.setData(packet);
|
|
|
|
send(basePacket);
|
|
}
|
|
|
|
public void logPacket( String sendOrRecv, int opcode, byte[] payload) {
|
|
Grasscutter.getLogger().info(sendOrRecv + ": " + PacketOpcodesUtils.getOpcodeName(opcode) + " (" + opcode + ")");
|
|
System.out.println(Utils.bytesToHex(payload));
|
|
}
|
|
public void send(BasePacket packet) {
|
|
// Test
|
|
if (packet.getOpcode() <= 0) {
|
|
Grasscutter.getLogger().warn("Tried to send packet with missing cmd id!");
|
|
return;
|
|
}
|
|
|
|
// DO NOT REMOVE (unless we find a way to validate code before sending to client which I don't think we can)
|
|
// Stop WindSeedClientNotify from being sent for security purposes.
|
|
if (PacketOpcodesUtils.BANNED_PACKETS.contains(packet.getOpcode())) {
|
|
return;
|
|
}
|
|
|
|
// Header
|
|
if (packet.shouldBuildHeader()) {
|
|
packet.buildHeader(this.getNextClientSequence());
|
|
}
|
|
|
|
// Log
|
|
switch (GAME_INFO.logPackets) {
|
|
case ALL -> {
|
|
if (!PacketOpcodesUtils.LOOP_PACKETS.contains(packet.getOpcode())) {
|
|
logPacket("SEND", packet.getOpcode(), packet.getData());
|
|
}
|
|
}
|
|
case WHITELIST-> {
|
|
if (SERVER.debugWhitelist.contains(packet.getOpcode())) {
|
|
logPacket("SEND", packet.getOpcode(), packet.getData());
|
|
}
|
|
}
|
|
case BLACKLIST-> {
|
|
if (!SERVER.debugBlacklist.contains(packet.getOpcode())) {
|
|
logPacket("SEND", packet.getOpcode(), packet.getData());
|
|
}
|
|
}
|
|
default -> {}
|
|
}
|
|
|
|
// Invoke event.
|
|
SendPacketEvent event = new SendPacketEvent(this, packet); event.call();
|
|
if (!event.isCanceled()) { // If event is not cancelled, continue.
|
|
tunnel.writeData(event.getPacket().build());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onConnected(GameSessionManager.KcpTunnel tunnel) {
|
|
this.tunnel = tunnel;
|
|
Grasscutter.getLogger().info(translate("messages.game.connect", this.getAddress().toString()));
|
|
}
|
|
|
|
|
|
@Override
|
|
public void handleReceive(byte[] bytes) {
|
|
// Decrypt and turn back into a packet
|
|
Crypto.xor(bytes, useSecretKey() ? Crypto.ENCRYPT_KEY : Crypto.DISPATCH_KEY);
|
|
ByteBuf packet = Unpooled.wrappedBuffer(bytes);
|
|
|
|
// Log
|
|
//logPacket(packet);
|
|
// Handle
|
|
try {
|
|
boolean allDebug = GAME_INFO.logPackets == ServerDebugMode.ALL;
|
|
while (packet.readableBytes() > 0) {
|
|
// Length
|
|
if (packet.readableBytes() < 12) {
|
|
return;
|
|
}
|
|
// Packet sanity check
|
|
int const1 = packet.readShort();
|
|
if (const1 != 17767) {
|
|
if (allDebug) {
|
|
Grasscutter.getLogger().error("Bad Data Package Received: got {} ,expect 17767",const1);
|
|
}
|
|
return; // Bad packet
|
|
}
|
|
// Data
|
|
int opcode = packet.readShort();
|
|
int headerLength = packet.readShort();
|
|
int payloadLength = packet.readInt();
|
|
byte[] header = new byte[headerLength];
|
|
byte[] payload = new byte[payloadLength];
|
|
|
|
packet.readBytes(header);
|
|
packet.readBytes(payload);
|
|
// Sanity check #2
|
|
int const2 = packet.readShort();
|
|
if (const2 != -30293) {
|
|
if (allDebug) {
|
|
Grasscutter.getLogger().error("Bad Data Package Received: got {} ,expect -30293",const2);
|
|
}
|
|
return; // Bad packet
|
|
}
|
|
|
|
// Log packet
|
|
switch (GAME_INFO.logPackets) {
|
|
case ALL -> {
|
|
if (!PacketOpcodesUtils.LOOP_PACKETS.contains(opcode)) {
|
|
logPacket("RECV",opcode, payload);
|
|
}
|
|
}
|
|
case WHITELIST-> {
|
|
if (SERVER.debugWhitelist.contains(opcode)) {
|
|
logPacket("RECV",opcode, payload);
|
|
}
|
|
}
|
|
case BLACKLIST-> {
|
|
if (!(SERVER.debugBlacklist.contains(opcode))) {
|
|
logPacket("RECV",opcode, payload);
|
|
}
|
|
}
|
|
default -> {}
|
|
}
|
|
|
|
// Handle
|
|
getServer().getPacketHandler().handle(this, opcode, header, payload);
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
} finally {
|
|
//byteBuf.release(); //Needn't
|
|
packet.release();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void handleClose() {
|
|
setState(SessionState.INACTIVE);
|
|
//send disconnection pack in case of reconnection
|
|
Grasscutter.getLogger().info(translate("messages.game.disconnect", this.getAddress().toString()));
|
|
// Save after disconnecting
|
|
if (this.isLoggedIn()) {
|
|
Player player = getPlayer();
|
|
// Call logout event.
|
|
player.onLogout();
|
|
}
|
|
try {
|
|
send(new BasePacket(PacketOpcodes.ServerDisconnectClientNotify));
|
|
}catch (Throwable ignore) {
|
|
Grasscutter.getLogger().warn("closing {} error",getAddress().getAddress().getHostAddress());
|
|
}
|
|
tunnel = null;
|
|
}
|
|
|
|
public void close() {
|
|
tunnel.close();
|
|
}
|
|
|
|
public boolean isActive() {
|
|
return getState() == SessionState.ACTIVE;
|
|
}
|
|
|
|
public enum SessionState {
|
|
INACTIVE,
|
|
WAITING_FOR_TOKEN,
|
|
WAITING_FOR_LOGIN,
|
|
PICKING_CHARACTER,
|
|
ACTIVE
|
|
}
|
|
}
|