Grasscutter.java 9.25 KB
Newer Older
Melledy's avatar
Melledy committed
1
2
package emu.grasscutter;

3
import java.io.*;
4
import java.util.Calendar;
Melledy's avatar
Melledy committed
5

6
7
import emu.grasscutter.auth.AuthenticationSystem;
import emu.grasscutter.auth.DefaultAuthentication;
Jaida Wu's avatar
Jaida Wu committed
8
import emu.grasscutter.command.CommandMap;
KingRainbow44's avatar
KingRainbow44 committed
9
import emu.grasscutter.plugin.PluginManager;
KingRainbow44's avatar
KingRainbow44 committed
10
import emu.grasscutter.plugin.api.ServerHook;
11
import emu.grasscutter.scripts.ScriptLoader;
12
13
import emu.grasscutter.server.http.HttpServer;
import emu.grasscutter.server.http.dispatch.DispatchHandler;
14
import emu.grasscutter.server.http.handlers.*;
15
import emu.grasscutter.server.http.dispatch.RegionHandler;
16
import emu.grasscutter.utils.ConfigContainer;
KingRainbow44's avatar
KingRainbow44 committed
17
import emu.grasscutter.utils.Utils;
18
19
20
21
22
23
import org.jline.reader.EndOfFileException;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.UserInterruptException;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
Melledy's avatar
Melledy committed
24
import org.reflections.Reflections;
Melledy's avatar
Melledy committed
25
26
27
28
29
30
31
32
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import ch.qos.logback.classic.Logger;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.database.DatabaseManager;
33
import emu.grasscutter.utils.Language;
Melledy's avatar
Melledy committed
34
35
36
37
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.Crypto;

38
39
import javax.annotation.Nullable;

40
import static emu.grasscutter.utils.Language.translate;
41
import static emu.grasscutter.Configuration.*;
42

KingRainbow44's avatar
KingRainbow44 committed
43
44
public final class Grasscutter {
	private static final Logger log = (Logger) LoggerFactory.getLogger(Grasscutter.class);
45
	private static LineReader consoleLineReader = null;
46
	
方块君's avatar
方块君 committed
47
	private static Language language;
48

KingRainbow44's avatar
KingRainbow44 committed
49
	private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
50
	public static final File configFile = new File("./config.json");
Secretboy's avatar
Secretboy committed
51

52
	private static int day; // Current day of week.
Secretboy's avatar
Secretboy committed
53

54
	private static HttpServer httpServer;
Melledy's avatar
Melledy committed
55
	private static GameServer gameServer;
KingRainbow44's avatar
KingRainbow44 committed
56
	private static PluginManager pluginManager;
57
	private static AuthenticationSystem authenticationSystem;
Secretboy's avatar
Secretboy committed
58

KingRainbow44's avatar
KingRainbow44 committed
59
	public static final Reflections reflector = new Reflections("emu.grasscutter");
60
	public static ConfigContainer config;
Magix's avatar
Magix committed
61
  
KingRainbow44's avatar
KingRainbow44 committed
62
	static {
Melledy's avatar
Melledy committed
63
64
		// Declare logback configuration.
		System.setProperty("logback.configurationFile", "src/main/resources/logback.xml");
Secretboy's avatar
Secretboy committed
65

Melledy's avatar
Melledy committed
66
		// Load server configuration.
67
		Grasscutter.loadConfig();
68
		// Attempt to update configuration.
69
		ConfigContainer.updateConfig();
方块君's avatar
方块君 committed
70

71
		// Load translation files.
方块君's avatar
方块君 committed
72
		Grasscutter.loadLanguage();
Secretboy's avatar
Secretboy committed
73

KingRainbow44's avatar
KingRainbow44 committed
74
75
76
		// Check server structure.
		Utils.startupCheck();
	}
Secretboy's avatar
Secretboy committed
77

78
79
80
  	public static void main(String[] args) throws Exception {
		Crypto.loadKeys(); // Load keys from buffers.
	
81
		// Parse arguments.
KingRainbow44's avatar
KingRainbow44 committed
82
		boolean exitEarly = false;
Melledy's avatar
Melledy committed
83
84
		for (String arg : args) {
			switch (arg.toLowerCase()) {
KingRainbow44's avatar
KingRainbow44 committed
85
				case "-handbook" -> {
KingRainbow44's avatar
KingRainbow44 committed
86
					Tools.createGmHandbook(); exitEarly = true;
KingRainbow44's avatar
KingRainbow44 committed
87
				}
88
				case "-gachamap" -> {
89
					Tools.createGachaMapping(DATA("gacha_mappings.js")); exitEarly = true;
90
				}
Melledy's avatar
Melledy committed
91
			}
KingRainbow44's avatar
KingRainbow44 committed
92
93
94
95
		} 
		
		// Exit early if argument sets it.
		if(exitEarly) System.exit(0);
96
	
KingRainbow44's avatar
KingRainbow44 committed
97
		// Initialize server.
98
		Grasscutter.getLogger().info(translate("messages.status.starting"));
99
	
KingRainbow44's avatar
KingRainbow44 committed
100
		// Load all resources.
101
		Grasscutter.updateDayOfWeek();
Melledy's avatar
Melledy committed
102
		ResourceLoader.loadAll();
103
		ScriptLoader.init();
104
	
KingRainbow44's avatar
KingRainbow44 committed
105
		// Initialize database.
Melledy's avatar
Melledy committed
106
		DatabaseManager.initialize();
107
108
109
		
		// Initialize the default authentication system.
		authenticationSystem = new DefaultAuthentication();
110
	
KingRainbow44's avatar
KingRainbow44 committed
111
		// Create server instances.
112
		httpServer = new HttpServer();
113
		gameServer = new GameServer();
KingRainbow44's avatar
KingRainbow44 committed
114
		// Create a server hook instance with both servers.
115
		new ServerHook(gameServer, httpServer);
116
		
117
118
		// Create plugin manager instance.
		pluginManager = new PluginManager();
119
120
121
122
123
124
125
126
		// Add HTTP routes after loading plugins.
		httpServer.addRouter(HttpServer.UnhandledRequestRouter.class);
		httpServer.addRouter(HttpServer.DefaultRequestRouter.class);
		httpServer.addRouter(RegionHandler.class);
		httpServer.addRouter(LogHandler.class);
		httpServer.addRouter(GenericHandler.class);
		httpServer.addRouter(AnnouncementsHandler.class);
		httpServer.addRouter(DispatchHandler.class);
127
128
		httpServer.addRouter(LegacyAuthHandler.class);
		httpServer.addRouter(GachaHandler.class);
129
	
KingRainbow44's avatar
KingRainbow44 committed
130
		// Start servers.
131
132
		var runMode = SERVER.runMode;
		if (runMode == ServerRunMode.HYBRID) {
133
			httpServer.start();
134
			gameServer.start();
135
		} else if (runMode == ServerRunMode.DISPATCH_ONLY) {
136
			httpServer.start();
137
		} else if (runMode == ServerRunMode.GAME_ONLY) {
138
139
			gameServer.start();
		} else {
140
			getLogger().error(translate("messages.status.run_mode_error", runMode));
141
142
			getLogger().error(translate("messages.status.run_mode_help"));
			getLogger().error(translate("messages.status.shutdown"));
143
144
			System.exit(1);
		}
145
	
KingRainbow44's avatar
KingRainbow44 committed
146
147
		// Enable all plugins.
		pluginManager.enablePlugins();
148
	
KingRainbow44's avatar
KingRainbow44 committed
149
150
		// Hook into shutdown event.
		Runtime.getRuntime().addShutdownHook(new Thread(Grasscutter::onShutdown));
151
	
152
153
		// Open console.
		startConsole();
154
 	}
KingRainbow44's avatar
KingRainbow44 committed
155
156
157
158
159
160
161
162

	/**
	 * Server shutdown event.
	 */
	private static void onShutdown() {
		// Disable all plugins.
		pluginManager.disablePlugins();
	}
163

164
165
166
167
168
169
170
171
172
173
174
175
176
	/*
	 * Methods for the language system component.
	 */
	
	public static void loadLanguage() {
		var locale = config.language.language;
		language = Language.getLanguage(Utils.getLanguageCode(locale));
	}
	
	/*
	 * Methods for the configuration system component.
	 */

177
178
179
	/**
	 * Attempts to load the configuration from a file.
	 */
180
	public static void loadConfig() {
Melledy's avatar
Melledy committed
181
		try (FileReader file = new FileReader(configFile)) {
182
183
184
185
186
187
			config = gson.fromJson(file, ConfigContainer.class);
		} catch (Exception exception) {
			Grasscutter.saveConfig(null);
			config = new ConfigContainer();
		} catch (Error error) {
			// Occurred probably from an outdated config file.
188
			Grasscutter.saveConfig(null);
189
			config = new ConfigContainer();
Melledy's avatar
Melledy committed
190
191
		}
	}
方块君's avatar
方块君 committed
192

193
194
195
196
	/**
	 * Saves the provided server configuration.
	 * @param config The configuration to save, or null for a new one.
	 */
197
198
	public static void saveConfig(@Nullable ConfigContainer config) {
		if(config == null) config = new ConfigContainer();
199
		
Melledy's avatar
Melledy committed
200
201
		try (FileWriter file = new FileWriter(configFile)) {
			file.write(gson.toJson(config));
202
203
		} catch (IOException ignored) {
			Grasscutter.getLogger().error("Unable to write to config file.");
Melledy's avatar
Melledy committed
204
		} catch (Exception e) {
205
			Grasscutter.getLogger().error("Unable to save config file.", e);
Melledy's avatar
Melledy committed
206
207
		}
	}
Secretboy's avatar
Secretboy committed
208

209
210
211
212
	/*
	 * Getters for the various server components.
	 */
	
213
	public static ConfigContainer getConfig() {
KingRainbow44's avatar
KingRainbow44 committed
214
215
216
		return config;
	}

方块君's avatar
方块君 committed
217
218
219
220
	public static Language getLanguage() {
		return language;
	}

Secretboy's avatar
Secretboy committed
221
222
223
224
225
226
227
228
	public static void setLanguage(Language language) {
        Grasscutter.language = language;
	}

	public static Language getLanguage(String langCode) {
        return Language.getLanguage(langCode);
	}

KingRainbow44's avatar
KingRainbow44 committed
229
230
231
232
	public static Logger getLogger() {
		return log;
	}

233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
	public static LineReader getConsole() {
		if (consoleLineReader == null) {
			Terminal terminal = null;
			try {
				terminal = TerminalBuilder.builder().jna(true).build();
			} catch (Exception e) {
				try {
					// Fallback to a dumb jline terminal.
					terminal = TerminalBuilder.builder().dumb(true).build();
				} catch (Exception ignored) {
					// When dumb is true, build() never throws.
				}
			}
			consoleLineReader = LineReaderBuilder.builder()
					.terminal(terminal)
					.build();
		}
		return consoleLineReader;
	}

KingRainbow44's avatar
KingRainbow44 committed
253
254
255
256
	public static Gson getGsonFactory() {
		return gson;
	}

257
258
	public static HttpServer getHttpServer() {
		return httpServer;
KingRainbow44's avatar
KingRainbow44 committed
259
260
261
262
263
	}

	public static GameServer getGameServer() {
		return gameServer;
	}
Secretboy's avatar
Secretboy committed
264

KingRainbow44's avatar
KingRainbow44 committed
265
266
267
	public static PluginManager getPluginManager() {
		return pluginManager;
	}
268
269
270
271
	
	public static AuthenticationSystem getAuthenticationSystem() {
		return authenticationSystem;
	}
Secretboy's avatar
Secretboy committed
272

273
274
275
276
277
278
279
280
	public static int getCurrentDayOfWeek() {
		return day;
	}
	
	/*
	 * Utility methods.
	 */
	
281
282
283
284
285
	public static void updateDayOfWeek() {
		Calendar calendar = Calendar.getInstance();
		day = calendar.get(Calendar.DAY_OF_WEEK); 
	}

286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
	public static void startConsole() {
		// Console should not start in dispatch only mode.
		if (SERVER.runMode == ServerRunMode.DISPATCH_ONLY) {
			getLogger().info(translate("messages.dispatch.no_commands_error"));
			return;
		}

		getLogger().info(translate("messages.status.done"));
		String input = null;
		boolean isLastInterrupted = false;
		while (true) {
			try {
				input = consoleLineReader.readLine("> ");
			} catch (UserInterruptException e) {
				if (!isLastInterrupted) {
					isLastInterrupted = true;
					Grasscutter.getLogger().info("Press Ctrl-C again to shutdown.");
					continue;
				} else {
					Runtime.getRuntime().exit(0);
				}
			} catch (EndOfFileException e) {
				Grasscutter.getLogger().info("EOF detected.");
				continue;
			} catch (IOError e) {
				Grasscutter.getLogger().error("An IO error occurred.", e);
				continue;
			}

			isLastInterrupted = false;
			try {
				CommandMap.getInstance().invoke(null, null, input);
			} catch (Exception e) {
				Grasscutter.getLogger().error(translate("messages.game.command_error"), e);
			}
		}
322
	}
Secretboy's avatar
Secretboy committed
323

324
325
326
327
328
329
330
331
332
333
334
335
	/**
	 * Sets the authentication system for the server.
	 * @param authenticationSystem The authentication system to use.
	 */
	public static void setAuthenticationSystem(AuthenticationSystem authenticationSystem) {
		Grasscutter.authenticationSystem = authenticationSystem;
	}

	/*
	 * Enums for the configuration.
	 */
	
336
337
338
	public enum ServerRunMode {
		HYBRID, DISPATCH_ONLY, GAME_ONLY
	}
Secretboy's avatar
Secretboy committed
339

340
341
342
	public enum ServerDebugMode {
		ALL, MISSING, NONE
	}
Melledy's avatar
Melledy committed
343
}