diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 000000000..1af1b1c0d --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: emu.grasscutter.Grasscutter + diff --git a/src/main/java/emu/grasscutter/Config.java b/src/main/java/emu/grasscutter/Config.java index 1f5e6fd2e..945ff006b 100644 --- a/src/main/java/emu/grasscutter/Config.java +++ b/src/main/java/emu/grasscutter/Config.java @@ -1,6 +1,6 @@ package emu.grasscutter; -public class Config { +public final class Config { public String DispatchServerIp = "127.0.0.1"; public int DispatchServerPort = 443; public String DispatchServerKeystorePath = "./keystore.p12"; @@ -31,14 +31,14 @@ public class Config { return ServerOptions; } - public class GameRates { + public static class GameRates { public float ADVENTURE_EXP_RATE = 1.0f; public float MORA_RATE = 1.0f; public float DOMAIN_DROP_RATE = 1.0f; } - public class ServerOptions { - public int MaxEntityLimit = 1000; // Max entity limit per world. TODO Unenforced for now + public static class ServerOptions { + public int MaxEntityLimit = 1000; // Max entity limit per world. // TODO: Enforce later. public int[] WelcomeEmotes = {2007, 1002, 4010}; public String WelcomeMotd = "Welcome to Grasscutter emu"; } diff --git a/src/main/java/emu/grasscutter/GenshinConstants.java b/src/main/java/emu/grasscutter/GenshinConstants.java index ff46eddb9..bb59953e2 100644 --- a/src/main/java/emu/grasscutter/GenshinConstants.java +++ b/src/main/java/emu/grasscutter/GenshinConstants.java @@ -2,11 +2,10 @@ package emu.grasscutter; import java.util.Arrays; -import emu.grasscutter.game.props.OpenState; import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Utils; -public class GenshinConstants { +public final class GenshinConstants { public static String VERSION = "2.6.0"; public static final int MAX_TEAMS = 4; @@ -25,9 +24,9 @@ public class GenshinConstants { public static final int MAX_FRIENDS = 45; public static final int MAX_FRIEND_REQUESTS = 50; - public static final int SERVER_CONSOLE_UID = 99; // uid of the fake player used for commands + public static final int SERVER_CONSOLE_UID = 99; // The UID of the server console's "player". - // Default entity ability hashes + // Default entity ability hashes. public static final String[] DEFAULT_ABILITY_STRINGS = { "Avatar_DefaultAbility_VisionReplaceDieInvincible", "Avatar_DefaultAbility_AvartarInShaderChange", "Avatar_SprintBS_Invincible", "Avatar_Freeze_Duration_Reducer", "Avatar_Attack_ReviveEnergy", "Avatar_Component_Initializer", "Avatar_FallAnthem_Achievement_Listener" diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index 9102ba52f..8ad8fe3e3 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -6,8 +6,8 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.InputStreamReader; import java.net.InetSocketAddress; -import java.util.Arrays; +import emu.grasscutter.utils.Utils; import org.slf4j.LoggerFactory; import com.google.gson.Gson; @@ -22,19 +22,25 @@ import emu.grasscutter.server.game.GameServer; import emu.grasscutter.tools.Tools; import emu.grasscutter.utils.Crypto; -public class Grasscutter { - private static Logger log = (Logger) LoggerFactory.getLogger(Grasscutter.class); +public final class Grasscutter { + private static final Logger log = (Logger) LoggerFactory.getLogger(Grasscutter.class); private static Config config; - private static Gson gson = new GsonBuilder().setPrettyPrinting().create(); - private static File configFile = new File("./config.json"); + private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + private static final File configFile = new File("./config.json"); public static RunMode MODE = RunMode.BOTH; private static DispatchServer dispatchServer; private static GameServer gameServer; + static { + // Load configuration. + Grasscutter.loadConfig(); + // Check server structure. + Utils.startupCheck(); + } + public static void main(String[] args) throws Exception { - Grasscutter.loadConfig(); Crypto.loadKeys(); for (String arg : args) { @@ -48,56 +54,34 @@ public class Grasscutter { case "-handbook": Tools.createGmHandbook(); return; - } } - // Startup - Grasscutter.getLogger().info("Grasscutter Emu"); + // Initialize server. + Grasscutter.getLogger().info("Starting Grasscutter..."); - // Load resource files + // Load all resources. ResourceLoader.loadAll(); - // Database DatabaseManager.initialize(); - // Run servers + // Start servers. dispatchServer = new DispatchServer(); dispatchServer.start(); gameServer = new GameServer(new InetSocketAddress(getConfig().GameServerIp, getConfig().GameServerPort)); gameServer.start(); + // Open console. startConsole(); } - - public static Config getConfig() { - return config; - } - - public static Logger getLogger() { - return log; - } - - public static Gson getGsonFactory() { - return gson; - } - - public static DispatchServer getDispatchServer() { - return dispatchServer; - } - - public static GameServer getGameServer() { - return gameServer; - } public static void loadConfig() { try (FileReader file = new FileReader(configFile)) { config = gson.fromJson(file, Config.class); } catch (Exception e) { - Grasscutter.config = new Config(); + Grasscutter.config = new Config(); saveConfig(); } - saveConfig(); } public static void saveConfig() { @@ -115,7 +99,7 @@ public class Grasscutter { ServerCommands.handle(input); } } catch (Exception e) { - Grasscutter.getLogger().error("Console error:", e); + Grasscutter.getLogger().error("An error occurred.", e); } } @@ -124,4 +108,24 @@ public class Grasscutter { AUTH, GAME } + + public static Config getConfig() { + return config; + } + + public static Logger getLogger() { + return log; + } + + public static Gson getGsonFactory() { + return gson; + } + + public static DispatchServer getDispatchServer() { + return dispatchServer; + } + + public static GameServer getGameServer() { + return gameServer; + } } diff --git a/src/main/java/emu/grasscutter/commands/Command.java b/src/main/java/emu/grasscutter/commands/Command.java index 77eb92e11..aa88826dd 100644 --- a/src/main/java/emu/grasscutter/commands/Command.java +++ b/src/main/java/emu/grasscutter/commands/Command.java @@ -5,9 +5,9 @@ import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface Command { - public String[] aliases() default ""; + String[] aliases() default ""; - public int gmLevel() default 1; + int gmLevel() default 1; - public String helpText() default ""; + String helpText() default ""; } diff --git a/src/main/java/emu/grasscutter/database/DatabaseManager.java b/src/main/java/emu/grasscutter/database/DatabaseManager.java index 5098db3cc..97e27a81a 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseManager.java +++ b/src/main/java/emu/grasscutter/database/DatabaseManager.java @@ -1,8 +1,5 @@ package emu.grasscutter.database; -import java.sql.Connection; -import java.sql.SQLException; - import com.mongodb.MongoClient; import com.mongodb.MongoClientURI; import com.mongodb.MongoCommandException; @@ -18,12 +15,11 @@ import emu.grasscutter.game.avatar.GenshinAvatar; import emu.grasscutter.game.friends.Friendship; import emu.grasscutter.game.inventory.GenshinItem; -public class DatabaseManager { +public final class DatabaseManager { private static MongoClient mongoClient; - private static Morphia morphia; private static Datastore datastore; - private static Class>[] mappedClasses = new Class>[] { + private static final Class>[] mappedClasses = new Class>[] { DatabaseCounter.class, Account.class, GenshinPlayer.class, GenshinAvatar.class, GenshinItem.class, Friendship.class }; @@ -38,15 +34,11 @@ public class DatabaseManager { public static MongoDatabase getDatabase() { return getDatastore().getDatabase(); } - - public static Connection getConnection() throws SQLException { - return null; - } public static void initialize() { // Initialize mongoClient = new MongoClient(new MongoClientURI(Grasscutter.getConfig().DatabaseUrl)); - morphia = new Morphia(); + Morphia morphia = new Morphia(); // TODO Update when migrating to Morphia 2.0 morphia.getMapper().getOptions().setStoreEmpties(true); diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchHttpJsonHandler.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchHttpJsonHandler.java index b4ef13229..3a42f7791 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchHttpJsonHandler.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchHttpJsonHandler.java @@ -7,7 +7,7 @@ import java.util.Collections; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; -public class DispatchHttpJsonHandler implements HttpHandler { +public final class DispatchHttpJsonHandler implements HttpHandler { private final String response; public DispatchHttpJsonHandler(String response) { @@ -24,5 +24,4 @@ public class DispatchHttpJsonHandler implements HttpHandler { os.write(response.getBytes()); os.close(); } - } diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java index e33e1de2a..84b520fa8 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java @@ -43,8 +43,7 @@ import emu.grasscutter.utils.Utils; import com.sun.net.httpserver.HttpServer; -public class DispatchServer { - private HttpsServer server; +public final class DispatchServer { private final InetSocketAddress address; private final Gson gson; private QueryCurrRegionHttpRsp currRegion; @@ -135,12 +134,12 @@ public class DispatchServer { this.regionCurrentBase64 = Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray()); this.currRegion = parsedRegionQuery; } catch (Exception e) { - e.printStackTrace(); + Grasscutter.getLogger().error("Error while initializing region info!", e); } } public void start() throws Exception { - server = HttpsServer.create(getAddress(), 0); + HttpsServer server = HttpsServer.create(getAddress(), 0); SSLContext sslContext = SSLContext.getInstance("TLS"); try (FileInputStream fis = new FileInputStream(Grasscutter.getConfig().DispatchServerKeystorePath)) { @@ -158,187 +157,169 @@ public class DispatchServer { return; } - server.createContext("/", new HttpHandler() { - @Override - public void handle(HttpExchange t) throws IOException { - //Create a response form the request query parameters - String response = "Hello"; - //Set the response header status and length - t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8")); - t.sendResponseHeaders(200, response.getBytes().length); - //Write the response string - OutputStream os = t.getResponseBody(); - os.write(response.getBytes()); - os.close(); - } + server.createContext("/", t -> { + //Create a response form the request query parameters + String response = "Hello"; + //Set the response header status and length + t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8")); + t.sendResponseHeaders(200, response.getBytes().length); + //Write the response string + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); }); // Dispatch - server.createContext("/query_region_list", new HttpHandler() { - @Override - public void handle(HttpExchange t) throws IOException { - // Log - Grasscutter.getLogger().info("Client request: query_region_list"); - // Create a response form the request query parameters - String response = regionListBase64; - // Set the response header status and length - t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8")); - t.sendResponseHeaders(200, response.getBytes().length); - // Write the response string - OutputStream os = t.getResponseBody(); - os.write(response.getBytes()); - os.close(); - } + server.createContext("/query_region_list", t -> { + // Log + Grasscutter.getLogger().info("Client request: query_region_list"); + // Create a response form the request query parameters + String response = regionListBase64; + // Set the response header status and length + t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8")); + t.sendResponseHeaders(200, response.getBytes().length); + // Write the response string + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); }); - server.createContext("/query_cur_region", new HttpHandler() { - @Override - public void handle(HttpExchange t) throws IOException { - // Log - Grasscutter.getLogger().info("Client request: query_cur_region"); - // Create a response form the request query parameters - URI uri = t.getRequestURI(); - String response = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw=="; - if (uri.getQuery() != null && uri.getQuery().length() > 0) { - response = regionCurrentBase64; - } - // Set the response header status and length - t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8")); - t.sendResponseHeaders(200, response.getBytes().length); - // Write the response string - OutputStream os = t.getResponseBody(); - os.write(response.getBytes()); - os.close(); + server.createContext("/query_cur_region", t -> { + // Log + Grasscutter.getLogger().info("Client request: query_cur_region"); + // Create a response form the request query parameters + URI uri = t.getRequestURI(); + String response = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw=="; + if (uri.getQuery() != null && uri.getQuery().length() > 0) { + response = regionCurrentBase64; } + // Set the response header status and length + t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8")); + t.sendResponseHeaders(200, response.getBytes().length); + // Write the response string + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); }); // Login via account - server.createContext("/hk4e_global/mdk/shield/api/login", new HttpHandler() { - @Override - public void handle(HttpExchange t) throws IOException { - // Get post data - LoginAccountRequestJson requestData = null; - try { - String body = Utils.toString(t.getRequestBody()); - requestData = getGsonFactory().fromJson(body, LoginAccountRequestJson.class); - } catch (Exception e) { - - } - // Create response json - if (requestData == null) { - return; - } - LoginResultJson responseData = new LoginResultJson(); + server.createContext("/hk4e_global/mdk/shield/api/login", t -> { + // Get post data + LoginAccountRequestJson requestData = null; + try { + String body = Utils.toString(t.getRequestBody()); + requestData = getGsonFactory().fromJson(body, LoginAccountRequestJson.class); + } catch (Exception e) { - // Login - Account account = DatabaseHelper.getAccountByName(requestData.account); - - // Test - if (account == null) { - responseData.retcode = -201; - responseData.message = "Username not found."; - } else { - responseData.message = "OK"; - responseData.data.account.uid = account.getId(); - responseData.data.account.token = account.generateSessionKey(); - responseData.data.account.email = account.getEmail(); - } - - // Create a response - String response = getGsonFactory().toJson(responseData); - // Set the response header status and length - t.getResponseHeaders().put("Content-Type", Collections.singletonList("application/json")); - t.sendResponseHeaders(200, response.getBytes().length); - // Write the response string - OutputStream os = t.getResponseBody(); - os.write(response.getBytes()); - os.close(); } + // Create response json + if (requestData == null) { + return; + } + LoginResultJson responseData = new LoginResultJson(); + + // Login + Account account = DatabaseHelper.getAccountByName(requestData.account); + + // Test + if (account == null) { + responseData.retcode = -201; + responseData.message = "Username not found."; + } else { + responseData.message = "OK"; + responseData.data.account.uid = account.getId(); + responseData.data.account.token = account.generateSessionKey(); + responseData.data.account.email = account.getEmail(); + } + + // Create a response + String response = getGsonFactory().toJson(responseData); + // Set the response header status and length + t.getResponseHeaders().put("Content-Type", Collections.singletonList("application/json")); + t.sendResponseHeaders(200, response.getBytes().length); + // Write the response string + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); }); // Login via token - server.createContext("/hk4e_global/mdk/shield/api/verify", new HttpHandler() { - @Override - public void handle(HttpExchange t) throws IOException { - // Get post data - LoginTokenRequestJson requestData = null; - try { - String body = Utils.toString(t.getRequestBody()); - requestData = getGsonFactory().fromJson(body, LoginTokenRequestJson.class); - } catch (Exception e) { - - } - // Create response json - if (requestData == null) { - return; - } - LoginResultJson responseData = new LoginResultJson(); - - // Login - Account account = DatabaseHelper.getAccountById(requestData.uid); + server.createContext("/hk4e_global/mdk/shield/api/verify", t -> { + // Get post data + LoginTokenRequestJson requestData = null; + try { + String body = Utils.toString(t.getRequestBody()); + requestData = getGsonFactory().fromJson(body, LoginTokenRequestJson.class); + } catch (Exception e) { - // Test - if (account == null || !account.getSessionKey().equals(requestData.token)) { - responseData.retcode = -111; - responseData.message = "Game account cache information error"; - } else { - responseData.message = "OK"; - responseData.data.account.uid = requestData.uid; - responseData.data.account.token = requestData.token; - responseData.data.account.email = account.getEmail(); - } - - // Create a response - String response = getGsonFactory().toJson(responseData); - // Set the response header status and length - t.getResponseHeaders().put("Content-Type", Collections.singletonList("application/json")); - t.sendResponseHeaders(200, response.getBytes().length); - // Write the response string - OutputStream os = t.getResponseBody(); - os.write(response.getBytes()); - os.close(); } + // Create response json + if (requestData == null) { + return; + } + LoginResultJson responseData = new LoginResultJson(); + + // Login + Account account = DatabaseHelper.getAccountById(requestData.uid); + + // Test + if (account == null || !account.getSessionKey().equals(requestData.token)) { + responseData.retcode = -111; + responseData.message = "Game account cache information error"; + } else { + responseData.message = "OK"; + responseData.data.account.uid = requestData.uid; + responseData.data.account.token = requestData.token; + responseData.data.account.email = account.getEmail(); + } + + // Create a response + String response = getGsonFactory().toJson(responseData); + // Set the response header status and length + t.getResponseHeaders().put("Content-Type", Collections.singletonList("application/json")); + t.sendResponseHeaders(200, response.getBytes().length); + // Write the response string + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); }); // Exchange for combo token - server.createContext("/hk4e_global/combo/granter/login/v2/login", new HttpHandler() { - @Override - public void handle(HttpExchange t) throws IOException { - // Get post data - ComboTokenReqJson requestData = null; - try { - String body = Utils.toString(t.getRequestBody()); - requestData = getGsonFactory().fromJson(body, ComboTokenReqJson.class); - } catch (Exception e) { - - } - // Create response json - if (requestData == null || requestData.data == null) { - return; - } - LoginTokenData loginData = getGsonFactory().fromJson(requestData.data, LoginTokenData.class); // Get login data - ComboTokenResJson responseData = new ComboTokenResJson(); - - // Login - Account account = DatabaseHelper.getAccountById(loginData.uid); + server.createContext("/hk4e_global/combo/granter/login/v2/login", t -> { + // Get post data + ComboTokenReqJson requestData = null; + try { + String body = Utils.toString(t.getRequestBody()); + requestData = getGsonFactory().fromJson(body, ComboTokenReqJson.class); + } catch (Exception e) { - // Test - if (account == null || !account.getSessionKey().equals(loginData.token)) { - responseData.retcode = -201; - responseData.message = "Wrong session key."; - } else { - responseData.message = "OK"; - responseData.data.open_id = loginData.uid; - responseData.data.combo_id = "157795300"; - responseData.data.combo_token = account.generateLoginToken(); - } - - // Create a response - String response = getGsonFactory().toJson(responseData); - // Set the response header status and length - t.getResponseHeaders().put("Content-Type", Collections.singletonList("application/json")); - t.sendResponseHeaders(200, response.getBytes().length); - // Write the response string - OutputStream os = t.getResponseBody(); - os.write(response.getBytes()); - os.close(); } + // Create response json + if (requestData == null || requestData.data == null) { + return; + } + LoginTokenData loginData = getGsonFactory().fromJson(requestData.data, LoginTokenData.class); // Get login data + ComboTokenResJson responseData = new ComboTokenResJson(); + + // Login + Account account = DatabaseHelper.getAccountById(loginData.uid); + + // Test + if (account == null || !account.getSessionKey().equals(loginData.token)) { + responseData.retcode = -201; + responseData.message = "Wrong session key."; + } else { + responseData.message = "OK"; + responseData.data.open_id = loginData.uid; + responseData.data.combo_id = "157795300"; + responseData.data.combo_token = account.generateLoginToken(); + } + + // Create a response + String response = getGsonFactory().toJson(responseData); + // Set the response header status and length + t.getResponseHeaders().put("Content-Type", Collections.singletonList("application/json")); + t.sendResponseHeaders(200, response.getBytes().length); + // Write the response string + OutputStream os = t.getResponseBody(); + os.write(response.getBytes()); + os.close(); }); // Agreement and Protocol server.createContext( // hk4e-sdk-os.hoyoverse.com @@ -420,19 +401,16 @@ public class DispatchServer { "/crash/dataUpload", new DispatchHttpJsonHandler("{\"code\":0}") ); - uploadLogServer.createContext("/gacha", new HttpHandler() { - @Override - public void handle(HttpExchange t) throws IOException { - //Create a response form the request query parameters - String response = "