GameSession.java 7.82 KB
Newer Older
Melledy's avatar
Melledy committed
1
2
3
4
package emu.grasscutter.server.game;

import java.io.File;
import java.net.InetSocketAddress;
5
import java.util.Set;
Melledy's avatar
Melledy committed
6
import emu.grasscutter.Grasscutter;
7
import emu.grasscutter.Grasscutter.ServerDebugMode;
Melledy's avatar
Melledy committed
8
import emu.grasscutter.game.Account;
Melledy's avatar
Melledy committed
9
import emu.grasscutter.game.player.Player;
10
import emu.grasscutter.net.packet.BasePacket;
11
import emu.grasscutter.net.packet.PacketOpcodes;
Melledy's avatar
Melledy committed
12
import emu.grasscutter.net.packet.PacketOpcodesUtil;
13
import emu.grasscutter.server.event.game.SendPacketEvent;
Melledy's avatar
Melledy committed
14
15
16
17
18
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;
Akka's avatar
Akka committed
19

20
import static emu.grasscutter.config.Configuration.*;
21
import static emu.grasscutter.utils.Language.translate;
22

23
public class GameSession implements GameSessionManager.KcpChannel {
24
	private final GameServer server;
25
26
	private GameSessionManager.KcpTunnel tunnel;

Melledy's avatar
Melledy committed
27
	private Account account;
28
	private Player player;
29

Melledy's avatar
Melledy committed
30
31
	private boolean useSecretKey;
	private SessionState state;
32

Melledy's avatar
Melledy committed
33
34
35
	private int clientTime;
	private long lastPingTime;
	private int lastClientSeq = 10;
36

Melledy's avatar
Melledy committed
37
38
39
40
41
	public GameSession(GameServer server) {
		this.server = server;
		this.state = SessionState.WAITING_FOR_TOKEN;
		this.lastPingTime = System.currentTimeMillis();
	}
42

Melledy's avatar
Melledy committed
43
44
45
	public GameServer getServer() {
		return server;
	}
46

Melledy's avatar
Melledy committed
47
	public InetSocketAddress getAddress() {
48
49
50
		try{
			return tunnel.getAddress();
		}catch (Throwable ignore){
Melledy's avatar
Melledy committed
51
52
53
54
55
56
57
			return null;
		}
	}

	public boolean useSecretKey() {
		return useSecretKey;
	}
58

Melledy's avatar
Melledy committed
59
60
61
62
63
64
65
	public Account getAccount() {
		return account;
	}

	public void setAccount(Account account) {
		this.account = account;
	}
66

Melledy's avatar
Melledy committed
67
68
69
70
	public String getAccountId() {
		return this.getAccount().getId();
	}

71
	public Player getPlayer() {
Melledy's avatar
Melledy committed
72
73
74
		return player;
	}

75
	public synchronized void setPlayer(Player player) {
Melledy's avatar
Melledy committed
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
		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;
	}
96

Melledy's avatar
Melledy committed
97
98
99
100
101
102
103
	public int getClientTime() {
		return this.clientTime;
	}

	public long getLastPingTime() {
		return lastPingTime;
	}
104

Melledy's avatar
Melledy committed
105
106
107
108
	public void updateLastPingTime(int clientTime) {
		this.clientTime = clientTime;
		this.lastPingTime = System.currentTimeMillis();
	}
109

Melledy's avatar
Melledy committed
110
111
112
	public int getNextClientSequence() {
		return ++lastClientSeq;
	}
113

Melledy's avatar
Melledy committed
114
    public void replayPacket(int opcode, String name) {
115
    	String filePath = PACKET(name);
Melledy's avatar
Melledy committed
116
		File p = new File(filePath);
117

Melledy's avatar
Melledy committed
118
119
120
		if (!p.exists()) return;

		byte[] packet = FileUtils.read(p);
121

122
123
		BasePacket basePacket = new BasePacket(opcode);
		basePacket.setData(packet);
124

125
		send(basePacket);
Melledy's avatar
Melledy committed
126
    }
127
128
129
130
131

    public void logPacket( String sendOrRecv, int opcode, byte[] payload) {
        Grasscutter.getLogger().info(sendOrRecv + ": " + PacketOpcodesUtil.getOpcodeName(opcode) + " (" + opcode + ")");
        System.out.println(Utils.bytesToHex(payload));
    }
132
    public void send(BasePacket packet) {
Melledy's avatar
Melledy committed
133
    	// Test
134
    	if (packet.getOpcode() <= 0) {
Melledy's avatar
Melledy committed
135
136
137
    		Grasscutter.getLogger().warn("Tried to send packet with missing cmd id!");
    		return;
    	}
138
139
140
141
142
143

		// 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(PacketOpcodes.BANNED_PACKETS.contains(packet.getOpcode())) {
			return;
		}
144

Melledy's avatar
Melledy committed
145
    	// Header
146
147
    	if (packet.shouldBuildHeader()) {
    		packet.buildHeader(this.getNextClientSequence());
Melledy's avatar
Melledy committed
148
    	}
149

Melledy's avatar
Melledy committed
150
    	// Log
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
    	switch (GAME_INFO.logPackets) {
    	    case ALL -> {
    	        if (!loopPacket.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 -> {}
168
169
    	}

170
		// Invoke event.
171
		SendPacketEvent event = new SendPacketEvent(this, packet); event.call();
172
173
174
    	if(!event.isCanceled()) { // If event is not cancelled, continue.
			tunnel.writeData(event.getPacket().build());
		}
Melledy's avatar
Melledy committed
175
    }
176

177
178
179
180
181
182
183
184
	private static final Set<Integer> loopPacket = Set.of(
			PacketOpcodes.PingReq,
			PacketOpcodes.PingRsp,
			PacketOpcodes.WorldPlayerRTTNotify,
			PacketOpcodes.UnionCmdNotify,
			PacketOpcodes.QueryPathReq
	);

185
186
187
188
189
190
	@Override
	public void onConnected(GameSessionManager.KcpTunnel tunnel) {
		this.tunnel = tunnel;
		Grasscutter.getLogger().info(translate("messages.game.connect", this.getAddress().toString()));
	}

Melledy's avatar
Melledy committed
191
192

	@Override
193
	public void handleReceive(byte[] bytes) {
Melledy's avatar
Melledy committed
194
		// Decrypt and turn back into a packet
195
196
197
		Crypto.xor(bytes, useSecretKey() ? Crypto.ENCRYPT_KEY : Crypto.DISPATCH_KEY);
		ByteBuf packet = Unpooled.wrappedBuffer(bytes);

Melledy's avatar
Melledy committed
198
199
200
201
		// Log
		//logPacket(packet);
		// Handle
		try {
202
			boolean allDebug = GAME_INFO.logPackets == ServerDebugMode.ALL;
Melledy's avatar
Melledy committed
203
204
205
206
207
208
209
210
			while (packet.readableBytes() > 0) {
				// Length
				if (packet.readableBytes() < 12) {
					return;
				}
				// Packet sanity check
				int const1 = packet.readShort();
				if (const1 != 17767) {
211
212
213
					if(allDebug){
						Grasscutter.getLogger().error("Bad Data Package Received: got {} ,expect 17767",const1);
					}
Melledy's avatar
Melledy committed
214
215
216
217
218
219
220
221
					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];
222

Melledy's avatar
Melledy committed
223
224
225
226
227
				packet.readBytes(header);
				packet.readBytes(payload);
				// Sanity check #2
				int const2 = packet.readShort();
				if (const2 != -30293) {
228
229
230
					if(allDebug){
						Grasscutter.getLogger().error("Bad Data Package Received: got {} ,expect -30293",const2);
					}
Melledy's avatar
Melledy committed
231
232
					return; // Bad packet
				}
233
				
Melledy's avatar
Melledy committed
234
				// Log packet
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
				switch (GAME_INFO.logPackets) {
    	            case ALL -> {
    	                if (!loopPacket.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 -> {}
    	        }
253

Melledy's avatar
Melledy committed
254
255
256
257
258
259
				// Handle
				getServer().getPacketHandler().handle(this, opcode, header, payload);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
260
			//byteBuf.release(); //Needn't
Melledy's avatar
Melledy committed
261
262
263
			packet.release();
		}
	}
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291

	@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;
	}

Melledy's avatar
Melledy committed
292
293
294
295
296
	public enum SessionState {
		INACTIVE,
		WAITING_FOR_TOKEN,
		WAITING_FOR_LOGIN,
		PICKING_CHARACTER,
297
		ACTIVE
Melledy's avatar
Melledy committed
298
299
	}
}