Grasscutter.java 10.9 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;
4Benj_'s avatar
4Benj_ committed
9
10
import emu.grasscutter.command.DefaultPermissionHandler;
import emu.grasscutter.command.PermissionHandler;
GanyusLeftHorn's avatar
GanyusLeftHorn committed
11
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
Melledy's avatar
Melledy committed
12
13
import emu.grasscutter.game.managers.energy.EnergyManager;
import emu.grasscutter.game.managers.stamina.StaminaManager;
KingRainbow44's avatar
KingRainbow44 committed
14
import emu.grasscutter.plugin.PluginManager;
KingRainbow44's avatar
KingRainbow44 committed
15
import emu.grasscutter.plugin.api.ServerHook;
16
import emu.grasscutter.scripts.ScriptLoader;
17
18
import emu.grasscutter.server.http.HttpServer;
import emu.grasscutter.server.http.dispatch.DispatchHandler;
19
import emu.grasscutter.server.http.handlers.*;
20
import emu.grasscutter.server.http.dispatch.RegionHandler;
2bllw8's avatar
2bllw8 committed
21
import emu.grasscutter.server.http.documentation.DocumentationServerHandler;
22
import emu.grasscutter.utils.ConfigContainer;
KingRainbow44's avatar
KingRainbow44 committed
23
import emu.grasscutter.utils.Utils;
24
25
26
27
28
29
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
30
import org.reflections.Reflections;
Melledy's avatar
Melledy committed
31
32
33
34
35
36
37
38
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;
39
import emu.grasscutter.utils.Language;
Melledy's avatar
Melledy committed
40
41
42
43
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.Crypto;

44
45
import javax.annotation.Nullable;

46
import static emu.grasscutter.utils.Language.translate;
47
import static emu.grasscutter.Configuration.*;
48

KingRainbow44's avatar
KingRainbow44 committed
49
50
public final class Grasscutter {
	private static final Logger log = (Logger) LoggerFactory.getLogger(Grasscutter.class);
51
	private static LineReader consoleLineReader = null;
52
	
方块君's avatar
方块君 committed
53
	private static Language language;
54

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

58
	private static int day; // Current day of week.
Secretboy's avatar
Secretboy committed
59

60
	private static HttpServer httpServer;
Melledy's avatar
Melledy committed
61
	private static GameServer gameServer;
KingRainbow44's avatar
KingRainbow44 committed
62
	private static PluginManager pluginManager;
63
	private static AuthenticationSystem authenticationSystem;
4Benj_'s avatar
4Benj_ committed
64
	private static PermissionHandler permissionHandler;
Secretboy's avatar
Secretboy committed
65

KingRainbow44's avatar
KingRainbow44 committed
66
	public static final Reflections reflector = new Reflections("emu.grasscutter");
67
	public static ConfigContainer config;
Magix's avatar
Magix committed
68
  
KingRainbow44's avatar
KingRainbow44 committed
69
	static {
Melledy's avatar
Melledy committed
70
71
		// Declare logback configuration.
		System.setProperty("logback.configurationFile", "src/main/resources/logback.xml");
Secretboy's avatar
Secretboy committed
72

Melledy's avatar
Melledy committed
73
		// Load server configuration.
74
		Grasscutter.loadConfig();
75
		// Attempt to update configuration.
76
		ConfigContainer.updateConfig();
方块君's avatar
方块君 committed
77

78
		// Load translation files.
方块君's avatar
方块君 committed
79
		Grasscutter.loadLanguage();
Secretboy's avatar
Secretboy committed
80

KingRainbow44's avatar
KingRainbow44 committed
81
82
83
		// Check server structure.
		Utils.startupCheck();
	}
Secretboy's avatar
Secretboy committed
84

85
86
87
  	public static void main(String[] args) throws Exception {
		Crypto.loadKeys(); // Load keys from buffers.
	
88
		// Parse arguments.
KingRainbow44's avatar
KingRainbow44 committed
89
		boolean exitEarly = false;
Melledy's avatar
Melledy committed
90
91
		for (String arg : args) {
			switch (arg.toLowerCase()) {
KingRainbow44's avatar
KingRainbow44 committed
92
				case "-handbook" -> {
KingRainbow44's avatar
KingRainbow44 committed
93
					Tools.createGmHandbook(); exitEarly = true;
KingRainbow44's avatar
KingRainbow44 committed
94
				}
95
				case "-gachamap" -> {
96
					Tools.createGachaMapping(DATA("gacha_mappings.js")); exitEarly = true;
97
				}
98
				case "-version" -> {
mingjun97's avatar
mingjun97 committed
99
					System.out.println("Grasscutter version: " + BuildConfig.VERSION + "-" + BuildConfig.GIT_HASH); exitEarly = true;
100
				}
Melledy's avatar
Melledy committed
101
			}
KingRainbow44's avatar
KingRainbow44 committed
102
103
104
105
		} 
		
		// Exit early if argument sets it.
		if(exitEarly) System.exit(0);
106
	
KingRainbow44's avatar
KingRainbow44 committed
107
		// Initialize server.
108
		Grasscutter.getLogger().info(translate("messages.status.starting"));
109
110
		Grasscutter.getLogger().info(translate("messages.status.game_version", GameConstants.VERSION));
		Grasscutter.getLogger().info(translate("messages.status.version", BuildConfig.VERSION, BuildConfig.GIT_HASH));
111
	
KingRainbow44's avatar
KingRainbow44 committed
112
		// Load all resources.
113
		Grasscutter.updateDayOfWeek();
Melledy's avatar
Melledy committed
114
		ResourceLoader.loadAll();
115
		ScriptLoader.init();
116
		EnergyManager.initialize();
GanyusLeftHorn's avatar
GanyusLeftHorn committed
117
		DungeonChallenge.initialize();
118
	
KingRainbow44's avatar
KingRainbow44 committed
119
		// Initialize database.
Melledy's avatar
Melledy committed
120
		DatabaseManager.initialize();
121
		
4Benj_'s avatar
4Benj_ committed
122
		// Initialize the default systems.
123
		authenticationSystem = new DefaultAuthentication();
4Benj_'s avatar
4Benj_ committed
124
		permissionHandler = new DefaultPermissionHandler();
125
	
KingRainbow44's avatar
KingRainbow44 committed
126
		// Create server instances.
127
		httpServer = new HttpServer();
128
		gameServer = new GameServer();
KingRainbow44's avatar
KingRainbow44 committed
129
		// Create a server hook instance with both servers.
130
		new ServerHook(gameServer, httpServer);
131
		
132
133
		// Create plugin manager instance.
		pluginManager = new PluginManager();
134
135
136
137
138
139
140
141
		// 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);
142
		httpServer.addRouter(GachaHandler.class);
2bllw8's avatar
2bllw8 committed
143
		httpServer.addRouter(DocumentationServerHandler.class);
144
		
gentlespoon's avatar
gentlespoon committed
145
146
		// TODO: find a better place?
		StaminaManager.initialize();
147
	
KingRainbow44's avatar
KingRainbow44 committed
148
		// Start servers.
149
150
		var runMode = SERVER.runMode;
		if (runMode == ServerRunMode.HYBRID) {
151
			httpServer.start();
152
			gameServer.start();
153
		} else if (runMode == ServerRunMode.DISPATCH_ONLY) {
154
			httpServer.start();
155
		} else if (runMode == ServerRunMode.GAME_ONLY) {
156
157
			gameServer.start();
		} else {
158
			getLogger().error(translate("messages.status.run_mode_error", runMode));
159
160
			getLogger().error(translate("messages.status.run_mode_help"));
			getLogger().error(translate("messages.status.shutdown"));
161
162
			System.exit(1);
		}
163
	
KingRainbow44's avatar
KingRainbow44 committed
164
165
		// Enable all plugins.
		pluginManager.enablePlugins();
166
	
KingRainbow44's avatar
KingRainbow44 committed
167
168
		// Hook into shutdown event.
		Runtime.getRuntime().addShutdownHook(new Thread(Grasscutter::onShutdown));
169
	
170
171
		// Open console.
		startConsole();
172
 	}
KingRainbow44's avatar
KingRainbow44 committed
173
174
175
176
177
178
179
180

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

182
183
184
185
186
187
188
189
190
191
192
193
194
	/*
	 * 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.
	 */

195
196
197
	/**
	 * Attempts to load the configuration from a file.
	 */
198
	public static void loadConfig() {
199
200
201
202
203
204
205
206
207
		// Check if config.json exists. If not, we generate a new config.
		if (!configFile.exists()) {
			getLogger().info("config.json could not be found. Generating a default configuration ...");
			config = new ConfigContainer();
			Grasscutter.saveConfig(config);
			return;
		} 

		// If the file already exists, we attempt to load it.
Melledy's avatar
Melledy committed
208
		try (FileReader file = new FileReader(configFile)) {
209
210
			config = gson.fromJson(file, ConfigContainer.class);
		} catch (Exception exception) {
211
212
213
			getLogger().error("There was an error while trying to load the configuration from config.json. Please make sure that there are no syntax errors. If you want to start with a default configuration, delete your existing config.json.");
			System.exit(1);
		} 
Melledy's avatar
Melledy committed
214
	}
方块君's avatar
方块君 committed
215

216
217
218
219
	/**
	 * Saves the provided server configuration.
	 * @param config The configuration to save, or null for a new one.
	 */
220
221
	public static void saveConfig(@Nullable ConfigContainer config) {
		if(config == null) config = new ConfigContainer();
222
		
Melledy's avatar
Melledy committed
223
224
		try (FileWriter file = new FileWriter(configFile)) {
			file.write(gson.toJson(config));
225
226
		} catch (IOException ignored) {
			Grasscutter.getLogger().error("Unable to write to config file.");
Melledy's avatar
Melledy committed
227
		} catch (Exception e) {
228
			Grasscutter.getLogger().error("Unable to save config file.", e);
Melledy's avatar
Melledy committed
229
230
		}
	}
Secretboy's avatar
Secretboy committed
231

232
233
234
235
	/*
	 * Getters for the various server components.
	 */
	
236
	public static ConfigContainer getConfig() {
KingRainbow44's avatar
KingRainbow44 committed
237
238
239
		return config;
	}

方块君's avatar
方块君 committed
240
241
242
243
	public static Language getLanguage() {
		return language;
	}

Secretboy's avatar
Secretboy committed
244
245
246
247
248
249
250
251
	public static void setLanguage(Language language) {
        Grasscutter.language = language;
	}

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

KingRainbow44's avatar
KingRainbow44 committed
252
253
254
255
	public static Logger getLogger() {
		return log;
	}

256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
	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
276
277
278
279
	public static Gson getGsonFactory() {
		return gson;
	}

280
281
	public static HttpServer getHttpServer() {
		return httpServer;
KingRainbow44's avatar
KingRainbow44 committed
282
283
284
285
286
	}

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

KingRainbow44's avatar
KingRainbow44 committed
288
289
290
	public static PluginManager getPluginManager() {
		return pluginManager;
	}
291
292
293
294
	
	public static AuthenticationSystem getAuthenticationSystem() {
		return authenticationSystem;
	}
Secretboy's avatar
Secretboy committed
295

4Benj_'s avatar
4Benj_ committed
296
297
298
299
	public static PermissionHandler getPermissionHandler() {
		return permissionHandler;
	}

300
301
302
303
304
305
306
307
	public static int getCurrentDayOfWeek() {
		return day;
	}
	
	/*
	 * Utility methods.
	 */
	
308
309
310
311
312
	public static void updateDayOfWeek() {
		Calendar calendar = Calendar.getInstance();
		day = calendar.get(Calendar.DAY_OF_WEEK); 
	}

313
314
315
316
317
318
319
320
321
322
	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;
323
		while (config.server.game.enableConsole) {
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
			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);
			}
		}
349
	}
Secretboy's avatar
Secretboy committed
350

351
352
353
354
355
356
357
358
	/**
	 * Sets the authentication system for the server.
	 * @param authenticationSystem The authentication system to use.
	 */
	public static void setAuthenticationSystem(AuthenticationSystem authenticationSystem) {
		Grasscutter.authenticationSystem = authenticationSystem;
	}

4Benj_'s avatar
4Benj_ committed
359
360
361
362
363
364
365
366
	/**
	 * Sets the permission handler for the server.
	 * @param permissionHandler The permission handler to use.
	 */
	public static void setPermissionHandler(PermissionHandler permissionHandler) {
		Grasscutter.permissionHandler = permissionHandler;
	}

367
368
369
370
	/*
	 * Enums for the configuration.
	 */
	
371
372
373
	public enum ServerRunMode {
		HYBRID, DISPATCH_ONLY, GAME_ONLY
	}
Secretboy's avatar
Secretboy committed
374

375
376
377
	public enum ServerDebugMode {
		ALL, MISSING, NONE
	}
Melledy's avatar
Melledy committed
378
}