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 = "Gacha"; - //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(); - } + uploadLogServer.createContext("/gacha", t -> { + //Create a response form the request query parameters + String response = "Gacha"; + //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(); }); uploadLogServer.start(); Grasscutter.getLogger().info("Log server (log-upload-os) started on port " + 80); diff --git a/src/main/java/emu/grasscutter/tools/Dumpers.java b/src/main/java/emu/grasscutter/tools/Dumpers.java index e417009a4..da7f51b0a 100644 --- a/src/main/java/emu/grasscutter/tools/Dumpers.java +++ b/src/main/java/emu/grasscutter/tools/Dumpers.java @@ -8,10 +8,8 @@ import emu.grasscutter.game.props.OpenState; import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp; import emu.grasscutter.net.proto.GetShopRspOuterClass.GetShopRsp; import emu.grasscutter.net.proto.OpenStateUpdateNotifyOuterClass.OpenStateUpdateNotify; -import emu.grasscutter.utils.FileUtils; -public class Dumpers { - +public final class Dumpers { public static void extractBanner(byte[] data) throws Exception { GetGachaInfoRsp proto = GetGachaInfoRsp.parseFrom(data); System.out.println(proto); diff --git a/src/main/java/emu/grasscutter/tools/Tools.java b/src/main/java/emu/grasscutter/tools/Tools.java index fede40866..b7c565672 100644 --- a/src/main/java/emu/grasscutter/tools/Tools.java +++ b/src/main/java/emu/grasscutter/tools/Tools.java @@ -5,6 +5,7 @@ import java.io.FileWriter; import java.io.PrintWriter; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -20,7 +21,7 @@ import emu.grasscutter.data.def.AvatarData; import emu.grasscutter.data.def.ItemData; import emu.grasscutter.data.def.MonsterData; -public class Tools { +public final class Tools { @SuppressWarnings("deprecation") public static void createGmHandbook() throws Exception { @@ -40,7 +41,7 @@ public class Tools { writer.println("// Genshin Impact " + GenshinConstants.VERSION + " GM Handbook"); writer.println("// Created " + dtf.format(now) + System.lineSeparator() + System.lineSeparator()); - list = GenshinData.getAvatarDataMap().keySet().stream().collect(Collectors.toList()); + list = new ArrayList<>(GenshinData.getAvatarDataMap().keySet()); Collections.sort(list); writer.println("// Avatars"); @@ -51,7 +52,7 @@ public class Tools { writer.println(); - list = GenshinData.getItemDataMap().keySet().stream().collect(Collectors.toList()); + list = new ArrayList<>(GenshinData.getItemDataMap().keySet()); Collections.sort(list); writer.println("// Items"); @@ -63,7 +64,7 @@ public class Tools { writer.println(); writer.println("// Monsters"); - list = GenshinData.getMonsterDataMap().keySet().stream().collect(Collectors.toList()); + list = new ArrayList<>(GenshinData.getMonsterDataMap().keySet()); Collections.sort(list); for (Integer id : list) { diff --git a/src/main/java/emu/grasscutter/utils/Crypto.java b/src/main/java/emu/grasscutter/utils/Crypto.java index a44cbfb98..2d3e96c16 100644 --- a/src/main/java/emu/grasscutter/utils/Crypto.java +++ b/src/main/java/emu/grasscutter/utils/Crypto.java @@ -7,8 +7,8 @@ import emu.grasscutter.Grasscutter; import emu.grasscutter.net.proto.GetPlayerTokenRspOuterClass.GetPlayerTokenRsp; import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp; -public class Crypto { - private static SecureRandom secureRandom = new SecureRandom(); +public final class Crypto { + private static final SecureRandom secureRandom = new SecureRandom(); public static final long ENCRYPT_SEED = Long.parseUnsignedLong("11468049314633205968"); public static byte[] ENCRYPT_SEED_BUFFER = new byte[0]; @@ -37,8 +37,7 @@ public class Crypto { FileUtils.write(Grasscutter.getConfig().KEY_FOLDER + "secretKeyBuffer.bin", p.getSecretKeyBuffer().toByteArray()); Grasscutter.getLogger().info("Secret Key: " + p.getSecretKey()); } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); + Grasscutter.getLogger().error("Crypto error.", e); } } @@ -47,7 +46,7 @@ public class Crypto { QueryCurrRegionHttpRsp p = QueryCurrRegionHttpRsp.parseFrom(Base64.getDecoder().decode(data)); FileUtils.write(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin", p.getRegionInfo().getSecretKey().toByteArray()); } catch (Exception e) { - e.printStackTrace(); + Grasscutter.getLogger().error("Crypto error.", e); } } diff --git a/src/main/java/emu/grasscutter/utils/FileUtils.java b/src/main/java/emu/grasscutter/utils/FileUtils.java index 5398ee65d..06e6087b3 100644 --- a/src/main/java/emu/grasscutter/utils/FileUtils.java +++ b/src/main/java/emu/grasscutter/utils/FileUtils.java @@ -1,21 +1,21 @@ package emu.grasscutter.utils; +import emu.grasscutter.Grasscutter; + import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -public class FileUtils { - +public final class FileUtils { public static void write(String dest, byte[] bytes) { Path path = Paths.get(dest); try { Files.write(path, bytes); } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + Grasscutter.getLogger().warn("Failed to write file: " + dest); } } @@ -27,8 +27,7 @@ public class FileUtils { try { return Files.readAllBytes(path); } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + Grasscutter.getLogger().warn("Failed to read file: " + path); } return new byte[0]; diff --git a/src/main/java/emu/grasscutter/utils/ProtoHelper.java b/src/main/java/emu/grasscutter/utils/ProtoHelper.java index 70aba3e3f..a6b59dc85 100644 --- a/src/main/java/emu/grasscutter/utils/ProtoHelper.java +++ b/src/main/java/emu/grasscutter/utils/ProtoHelper.java @@ -3,7 +3,7 @@ package emu.grasscutter.utils; import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.net.proto.PropValueOuterClass.PropValue; -public class ProtoHelper { +public final class ProtoHelper { public static PropValue newPropValue(PlayerProperty key, int value) { return PropValue.newBuilder().setType(key.getId()).setIval(value).setVal(value).build(); } diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index cbfcb7ec9..4293193d1 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -1,17 +1,19 @@ package emu.grasscutter.utils; -import java.io.BufferedInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.util.Random; +import emu.grasscutter.Config; import emu.grasscutter.Grasscutter; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; +import org.slf4j.Logger; -public class Utils { +@SuppressWarnings({"UnusedReturnValue", "BooleanMethodIsAlwaysInverted"}) +public final class Utils { public static final Random random = new Random(); public static int randomRange(int min, int max) { @@ -76,4 +78,77 @@ public class Utils { } return v7; } + + /** + * Checks if a file exists on the file system. + * @param path The path to the file. + * @return True if the file exists, false otherwise. + */ + public static boolean fileExists(String path) { + return new File(path).exists(); + } + + /** + * Creates a folder on the file system. + * @param path The path to the folder. + * @return True if the folder was created, false otherwise. + */ + public static boolean createFolder(String path) { + return new File(path).mkdirs(); + } + + /** + * Copies a file from the archive's resources to the file system. + * @param resource The path to the resource. + * @param destination The path to copy the resource to. + * @return True if the file was copied, false otherwise. + */ + public static boolean copyFromResources(String resource, String destination) { + try (InputStream stream = Grasscutter.class.getResourceAsStream(resource)) { + if(stream == null) { + Grasscutter.getLogger().warn("Could not find resource: " + resource); + return false; + } + + Files.copy(stream, new File(destination).toPath(), StandardCopyOption.REPLACE_EXISTING); + return true; + } catch (Exception e) { + Grasscutter.getLogger().warn("Unable to copy resource " + resource + " to " + destination, e); + return false; + } + } + + /** + * Checks for required files and folders before startup. + */ + public static void startupCheck() { + Config config = Grasscutter.getConfig(); + Logger logger = Grasscutter.getLogger(); + boolean exit = false; + + String resourcesFolder = config.RESOURCE_FOLDER; + String dataFolder = config.DATA_FOLDER; + + // Check for resources folder. + if(!fileExists(resourcesFolder)) { + logger.info("Creating resources folder..."); + logger.info("Place a copy of 'GenshinData' in the resources folder."); + createFolder(resourcesFolder); exit = true; + } + + // Check for GenshinData. + if(!fileExists(resourcesFolder + "BinOutput") || + !fileExists(resourcesFolder + "ExcelBinOutput")) { + logger.info("Place a copy of 'GenshinData' in the resources folder."); + exit = true; + } + + // Check for game data. + if(!fileExists(dataFolder)) + createFolder(dataFolder); + if(!fileExists(dataFolder + "AbilityEmbryos.json")) + copyFromResources("data/AbilityEmbryos.json", dataFolder); + + if(exit) System.exit(1); + } } diff --git a/src/main/java/logback.xml b/src/main/resources/logback.xml similarity index 100% rename from src/main/java/logback.xml rename to src/main/resources/logback.xml