Refactor existing code

This commit is contained in:
KingRainbow44 2022-04-18 01:11:27 -04:00
parent 7925d1cda3
commit 8b5f7dd06f
15 changed files with 311 additions and 264 deletions

View File

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: emu.grasscutter.Grasscutter

View File

@ -1,6 +1,6 @@
package emu.grasscutter; package emu.grasscutter;
public class Config { public final class Config {
public String DispatchServerIp = "127.0.0.1"; public String DispatchServerIp = "127.0.0.1";
public int DispatchServerPort = 443; public int DispatchServerPort = 443;
public String DispatchServerKeystorePath = "./keystore.p12"; public String DispatchServerKeystorePath = "./keystore.p12";
@ -31,14 +31,14 @@ public class Config {
return ServerOptions; return ServerOptions;
} }
public class GameRates { public static class GameRates {
public float ADVENTURE_EXP_RATE = 1.0f; public float ADVENTURE_EXP_RATE = 1.0f;
public float MORA_RATE = 1.0f; public float MORA_RATE = 1.0f;
public float DOMAIN_DROP_RATE = 1.0f; public float DOMAIN_DROP_RATE = 1.0f;
} }
public class ServerOptions { public static class ServerOptions {
public int MaxEntityLimit = 1000; // Max entity limit per world. TODO Unenforced for now public int MaxEntityLimit = 1000; // Max entity limit per world. // TODO: Enforce later.
public int[] WelcomeEmotes = {2007, 1002, 4010}; public int[] WelcomeEmotes = {2007, 1002, 4010};
public String WelcomeMotd = "Welcome to Grasscutter emu"; public String WelcomeMotd = "Welcome to Grasscutter emu";
} }

View File

@ -2,11 +2,10 @@ package emu.grasscutter;
import java.util.Arrays; import java.util.Arrays;
import emu.grasscutter.game.props.OpenState;
import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
public class GenshinConstants { public final class GenshinConstants {
public static String VERSION = "2.6.0"; public static String VERSION = "2.6.0";
public static final int MAX_TEAMS = 4; 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_FRIENDS = 45;
public static final int MAX_FRIEND_REQUESTS = 50; 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 = { public static final String[] DEFAULT_ABILITY_STRINGS = {
"Avatar_DefaultAbility_VisionReplaceDieInvincible", "Avatar_DefaultAbility_AvartarInShaderChange", "Avatar_SprintBS_Invincible", "Avatar_DefaultAbility_VisionReplaceDieInvincible", "Avatar_DefaultAbility_AvartarInShaderChange", "Avatar_SprintBS_Invincible",
"Avatar_Freeze_Duration_Reducer", "Avatar_Attack_ReviveEnergy", "Avatar_Component_Initializer", "Avatar_FallAnthem_Achievement_Listener" "Avatar_Freeze_Duration_Reducer", "Avatar_Attack_ReviveEnergy", "Avatar_Component_Initializer", "Avatar_FallAnthem_Achievement_Listener"

View File

@ -6,8 +6,8 @@ import java.io.FileReader;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Arrays;
import emu.grasscutter.utils.Utils;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.google.gson.Gson; import com.google.gson.Gson;
@ -22,19 +22,25 @@ import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.tools.Tools; import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.Crypto; import emu.grasscutter.utils.Crypto;
public class Grasscutter { public final class Grasscutter {
private static Logger log = (Logger) LoggerFactory.getLogger(Grasscutter.class); private static final Logger log = (Logger) LoggerFactory.getLogger(Grasscutter.class);
private static Config config; private static Config config;
private static Gson gson = new GsonBuilder().setPrettyPrinting().create(); private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
private static File configFile = new File("./config.json"); private static final File configFile = new File("./config.json");
public static RunMode MODE = RunMode.BOTH; public static RunMode MODE = RunMode.BOTH;
private static DispatchServer dispatchServer; private static DispatchServer dispatchServer;
private static GameServer gameServer; private static GameServer gameServer;
static {
// Load configuration.
Grasscutter.loadConfig();
// Check server structure.
Utils.startupCheck();
}
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
Grasscutter.loadConfig();
Crypto.loadKeys(); Crypto.loadKeys();
for (String arg : args) { for (String arg : args) {
@ -48,56 +54,34 @@ public class Grasscutter {
case "-handbook": case "-handbook":
Tools.createGmHandbook(); Tools.createGmHandbook();
return; return;
} }
} }
// Startup // Initialize server.
Grasscutter.getLogger().info("Grasscutter Emu"); Grasscutter.getLogger().info("Starting Grasscutter...");
// Load resource files // Load all resources.
ResourceLoader.loadAll(); ResourceLoader.loadAll();
// Database // Database
DatabaseManager.initialize(); DatabaseManager.initialize();
// Run servers // Start servers.
dispatchServer = new DispatchServer(); dispatchServer = new DispatchServer();
dispatchServer.start(); dispatchServer.start();
gameServer = new GameServer(new InetSocketAddress(getConfig().GameServerIp, getConfig().GameServerPort)); gameServer = new GameServer(new InetSocketAddress(getConfig().GameServerIp, getConfig().GameServerPort));
gameServer.start(); gameServer.start();
// Open console.
startConsole(); 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() { public static void loadConfig() {
try (FileReader file = new FileReader(configFile)) { try (FileReader file = new FileReader(configFile)) {
config = gson.fromJson(file, Config.class); config = gson.fromJson(file, Config.class);
} catch (Exception e) { } catch (Exception e) {
Grasscutter.config = new Config(); Grasscutter.config = new Config(); saveConfig();
} }
saveConfig();
} }
public static void saveConfig() { public static void saveConfig() {
@ -115,7 +99,7 @@ public class Grasscutter {
ServerCommands.handle(input); ServerCommands.handle(input);
} }
} catch (Exception e) { } catch (Exception e) {
Grasscutter.getLogger().error("Console error:", e); Grasscutter.getLogger().error("An error occurred.", e);
} }
} }
@ -124,4 +108,24 @@ public class Grasscutter {
AUTH, AUTH,
GAME 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;
}
} }

View File

@ -5,9 +5,9 @@ import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface Command { 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 "";
} }

View File

@ -1,8 +1,5 @@
package emu.grasscutter.database; package emu.grasscutter.database;
import java.sql.Connection;
import java.sql.SQLException;
import com.mongodb.MongoClient; import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI; import com.mongodb.MongoClientURI;
import com.mongodb.MongoCommandException; import com.mongodb.MongoCommandException;
@ -18,12 +15,11 @@ import emu.grasscutter.game.avatar.GenshinAvatar;
import emu.grasscutter.game.friends.Friendship; import emu.grasscutter.game.friends.Friendship;
import emu.grasscutter.game.inventory.GenshinItem; import emu.grasscutter.game.inventory.GenshinItem;
public class DatabaseManager { public final class DatabaseManager {
private static MongoClient mongoClient; private static MongoClient mongoClient;
private static Morphia morphia;
private static Datastore datastore; 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 DatabaseCounter.class, Account.class, GenshinPlayer.class, GenshinAvatar.class, GenshinItem.class, Friendship.class
}; };
@ -38,15 +34,11 @@ public class DatabaseManager {
public static MongoDatabase getDatabase() { public static MongoDatabase getDatabase() {
return getDatastore().getDatabase(); return getDatastore().getDatabase();
} }
public static Connection getConnection() throws SQLException {
return null;
}
public static void initialize() { public static void initialize() {
// Initialize // Initialize
mongoClient = new MongoClient(new MongoClientURI(Grasscutter.getConfig().DatabaseUrl)); mongoClient = new MongoClient(new MongoClientURI(Grasscutter.getConfig().DatabaseUrl));
morphia = new Morphia(); Morphia morphia = new Morphia();
// TODO Update when migrating to Morphia 2.0 // TODO Update when migrating to Morphia 2.0
morphia.getMapper().getOptions().setStoreEmpties(true); morphia.getMapper().getOptions().setStoreEmpties(true);

View File

@ -7,7 +7,7 @@ import java.util.Collections;
import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpHandler;
public class DispatchHttpJsonHandler implements HttpHandler { public final class DispatchHttpJsonHandler implements HttpHandler {
private final String response; private final String response;
public DispatchHttpJsonHandler(String response) { public DispatchHttpJsonHandler(String response) {
@ -24,5 +24,4 @@ public class DispatchHttpJsonHandler implements HttpHandler {
os.write(response.getBytes()); os.write(response.getBytes());
os.close(); os.close();
} }
} }

View File

@ -43,8 +43,7 @@ import emu.grasscutter.utils.Utils;
import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpServer;
public class DispatchServer { public final class DispatchServer {
private HttpsServer server;
private final InetSocketAddress address; private final InetSocketAddress address;
private final Gson gson; private final Gson gson;
private QueryCurrRegionHttpRsp currRegion; private QueryCurrRegionHttpRsp currRegion;
@ -135,12 +134,12 @@ public class DispatchServer {
this.regionCurrentBase64 = Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray()); this.regionCurrentBase64 = Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray());
this.currRegion = parsedRegionQuery; this.currRegion = parsedRegionQuery;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); Grasscutter.getLogger().error("Error while initializing region info!", e);
} }
} }
public void start() throws Exception { public void start() throws Exception {
server = HttpsServer.create(getAddress(), 0); HttpsServer server = HttpsServer.create(getAddress(), 0);
SSLContext sslContext = SSLContext.getInstance("TLS"); SSLContext sslContext = SSLContext.getInstance("TLS");
try (FileInputStream fis = new FileInputStream(Grasscutter.getConfig().DispatchServerKeystorePath)) { try (FileInputStream fis = new FileInputStream(Grasscutter.getConfig().DispatchServerKeystorePath)) {
@ -158,187 +157,169 @@ public class DispatchServer {
return; return;
} }
server.createContext("/", new HttpHandler() { server.createContext("/", t -> {
@Override //Create a response form the request query parameters
public void handle(HttpExchange t) throws IOException { String response = "Hello";
//Create a response form the request query parameters //Set the response header status and length
String response = "Hello"; t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8"));
//Set the response header status and length t.sendResponseHeaders(200, response.getBytes().length);
t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8")); //Write the response string
t.sendResponseHeaders(200, response.getBytes().length); OutputStream os = t.getResponseBody();
//Write the response string os.write(response.getBytes());
OutputStream os = t.getResponseBody(); os.close();
os.write(response.getBytes());
os.close();
}
}); });
// Dispatch // Dispatch
server.createContext("/query_region_list", new HttpHandler() { server.createContext("/query_region_list", t -> {
@Override // Log
public void handle(HttpExchange t) throws IOException { Grasscutter.getLogger().info("Client request: query_region_list");
// Log // Create a response form the request query parameters
Grasscutter.getLogger().info("Client request: query_region_list"); String response = regionListBase64;
// Create a response form the request query parameters // Set the response header status and length
String response = regionListBase64; t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8"));
// Set the response header status and length t.sendResponseHeaders(200, response.getBytes().length);
t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8")); // Write the response string
t.sendResponseHeaders(200, response.getBytes().length); OutputStream os = t.getResponseBody();
// Write the response string os.write(response.getBytes());
OutputStream os = t.getResponseBody(); os.close();
os.write(response.getBytes());
os.close();
}
}); });
server.createContext("/query_cur_region", new HttpHandler() { server.createContext("/query_cur_region", t -> {
@Override // Log
public void handle(HttpExchange t) throws IOException { Grasscutter.getLogger().info("Client request: query_cur_region");
// Log // Create a response form the request query parameters
Grasscutter.getLogger().info("Client request: query_cur_region"); URI uri = t.getRequestURI();
// Create a response form the request query parameters String response = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw==";
URI uri = t.getRequestURI(); if (uri.getQuery() != null && uri.getQuery().length() > 0) {
String response = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw=="; response = regionCurrentBase64;
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();
} }
// 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 // Login via account
server.createContext("/hk4e_global/mdk/shield/api/login", new HttpHandler() { server.createContext("/hk4e_global/mdk/shield/api/login", t -> {
@Override // Get post data
public void handle(HttpExchange t) throws IOException { LoginAccountRequestJson requestData = null;
// Get post data try {
LoginAccountRequestJson requestData = null; String body = Utils.toString(t.getRequestBody());
try { requestData = getGsonFactory().fromJson(body, LoginAccountRequestJson.class);
String body = Utils.toString(t.getRequestBody()); } catch (Exception e) {
requestData = getGsonFactory().fromJson(body, LoginAccountRequestJson.class);
} catch (Exception e) {
}
// 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();
} }
// 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 // Login via token
server.createContext("/hk4e_global/mdk/shield/api/verify", new HttpHandler() { server.createContext("/hk4e_global/mdk/shield/api/verify", t -> {
@Override // Get post data
public void handle(HttpExchange t) throws IOException { LoginTokenRequestJson requestData = null;
// Get post data try {
LoginTokenRequestJson requestData = null; String body = Utils.toString(t.getRequestBody());
try { requestData = getGsonFactory().fromJson(body, LoginTokenRequestJson.class);
String body = Utils.toString(t.getRequestBody()); } catch (Exception e) {
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);
// 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 // Exchange for combo token
server.createContext("/hk4e_global/combo/granter/login/v2/login", new HttpHandler() { server.createContext("/hk4e_global/combo/granter/login/v2/login", t -> {
@Override // Get post data
public void handle(HttpExchange t) throws IOException { ComboTokenReqJson requestData = null;
// Get post data try {
ComboTokenReqJson requestData = null; String body = Utils.toString(t.getRequestBody());
try { requestData = getGsonFactory().fromJson(body, ComboTokenReqJson.class);
String body = Utils.toString(t.getRequestBody()); } catch (Exception e) {
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);
// 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 // Agreement and Protocol
server.createContext( // hk4e-sdk-os.hoyoverse.com server.createContext( // hk4e-sdk-os.hoyoverse.com
@ -420,19 +401,16 @@ public class DispatchServer {
"/crash/dataUpload", "/crash/dataUpload",
new DispatchHttpJsonHandler("{\"code\":0}") new DispatchHttpJsonHandler("{\"code\":0}")
); );
uploadLogServer.createContext("/gacha", new HttpHandler() { uploadLogServer.createContext("/gacha", t -> {
@Override //Create a response form the request query parameters
public void handle(HttpExchange t) throws IOException { String response = "<!doctype html><html lang=\"en\"><head><title>Gacha</title></head><body></body></html>";
//Create a response form the request query parameters //Set the response header status and length
String response = "<!doctype html><html lang=\"en\"><head><title>Gacha</title></head><body></body></html>"; t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8"));
//Set the response header status and length t.sendResponseHeaders(200, response.getBytes().length);
t.getResponseHeaders().put("Content-Type", Collections.singletonList("text/html; charset=UTF-8")); //Write the response string
t.sendResponseHeaders(200, response.getBytes().length); OutputStream os = t.getResponseBody();
//Write the response string os.write(response.getBytes());
OutputStream os = t.getResponseBody(); os.close();
os.write(response.getBytes());
os.close();
}
}); });
uploadLogServer.start(); uploadLogServer.start();
Grasscutter.getLogger().info("Log server (log-upload-os) started on port " + 80); Grasscutter.getLogger().info("Log server (log-upload-os) started on port " + 80);

View File

@ -8,10 +8,8 @@ import emu.grasscutter.game.props.OpenState;
import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp; import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp;
import emu.grasscutter.net.proto.GetShopRspOuterClass.GetShopRsp; import emu.grasscutter.net.proto.GetShopRspOuterClass.GetShopRsp;
import emu.grasscutter.net.proto.OpenStateUpdateNotifyOuterClass.OpenStateUpdateNotify; 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 { public static void extractBanner(byte[] data) throws Exception {
GetGachaInfoRsp proto = GetGachaInfoRsp.parseFrom(data); GetGachaInfoRsp proto = GetGachaInfoRsp.parseFrom(data);
System.out.println(proto); System.out.println(proto);

View File

@ -5,6 +5,7 @@ import java.io.FileWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; 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.ItemData;
import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.data.def.MonsterData;
public class Tools { public final class Tools {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public static void createGmHandbook() throws Exception { public static void createGmHandbook() throws Exception {
@ -40,7 +41,7 @@ public class Tools {
writer.println("// Genshin Impact " + GenshinConstants.VERSION + " GM Handbook"); writer.println("// Genshin Impact " + GenshinConstants.VERSION + " GM Handbook");
writer.println("// Created " + dtf.format(now) + System.lineSeparator() + System.lineSeparator()); 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); Collections.sort(list);
writer.println("// Avatars"); writer.println("// Avatars");
@ -51,7 +52,7 @@ public class Tools {
writer.println(); writer.println();
list = GenshinData.getItemDataMap().keySet().stream().collect(Collectors.toList()); list = new ArrayList<>(GenshinData.getItemDataMap().keySet());
Collections.sort(list); Collections.sort(list);
writer.println("// Items"); writer.println("// Items");
@ -63,7 +64,7 @@ public class Tools {
writer.println(); writer.println();
writer.println("// Monsters"); writer.println("// Monsters");
list = GenshinData.getMonsterDataMap().keySet().stream().collect(Collectors.toList()); list = new ArrayList<>(GenshinData.getMonsterDataMap().keySet());
Collections.sort(list); Collections.sort(list);
for (Integer id : list) { for (Integer id : list) {

View File

@ -7,8 +7,8 @@ import emu.grasscutter.Grasscutter;
import emu.grasscutter.net.proto.GetPlayerTokenRspOuterClass.GetPlayerTokenRsp; import emu.grasscutter.net.proto.GetPlayerTokenRspOuterClass.GetPlayerTokenRsp;
import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp; import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp;
public class Crypto { public final class Crypto {
private static SecureRandom secureRandom = new SecureRandom(); private static final SecureRandom secureRandom = new SecureRandom();
public static final long ENCRYPT_SEED = Long.parseUnsignedLong("11468049314633205968"); public static final long ENCRYPT_SEED = Long.parseUnsignedLong("11468049314633205968");
public static byte[] ENCRYPT_SEED_BUFFER = new byte[0]; 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()); FileUtils.write(Grasscutter.getConfig().KEY_FOLDER + "secretKeyBuffer.bin", p.getSecretKeyBuffer().toByteArray());
Grasscutter.getLogger().info("Secret Key: " + p.getSecretKey()); Grasscutter.getLogger().info("Secret Key: " + p.getSecretKey());
} catch (Exception e) { } catch (Exception e) {
// TODO Auto-generated catch block Grasscutter.getLogger().error("Crypto error.", e);
e.printStackTrace();
} }
} }
@ -47,7 +46,7 @@ public class Crypto {
QueryCurrRegionHttpRsp p = QueryCurrRegionHttpRsp.parseFrom(Base64.getDecoder().decode(data)); QueryCurrRegionHttpRsp p = QueryCurrRegionHttpRsp.parseFrom(Base64.getDecoder().decode(data));
FileUtils.write(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin", p.getRegionInfo().getSecretKey().toByteArray()); FileUtils.write(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin", p.getRegionInfo().getSecretKey().toByteArray());
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); Grasscutter.getLogger().error("Crypto error.", e);
} }
} }

View File

@ -1,21 +1,21 @@
package emu.grasscutter.utils; package emu.grasscutter.utils;
import emu.grasscutter.Grasscutter;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
public class FileUtils { public final class FileUtils {
public static void write(String dest, byte[] bytes) { public static void write(String dest, byte[] bytes) {
Path path = Paths.get(dest); Path path = Paths.get(dest);
try { try {
Files.write(path, bytes); Files.write(path, bytes);
} catch (IOException e) { } catch (IOException e) {
// TODO Auto-generated catch block Grasscutter.getLogger().warn("Failed to write file: " + dest);
e.printStackTrace();
} }
} }
@ -27,8 +27,7 @@ public class FileUtils {
try { try {
return Files.readAllBytes(path); return Files.readAllBytes(path);
} catch (IOException e) { } catch (IOException e) {
// TODO Auto-generated catch block Grasscutter.getLogger().warn("Failed to read file: " + path);
e.printStackTrace();
} }
return new byte[0]; return new byte[0];

View File

@ -3,7 +3,7 @@ package emu.grasscutter.utils;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.proto.PropValueOuterClass.PropValue; import emu.grasscutter.net.proto.PropValueOuterClass.PropValue;
public class ProtoHelper { public final class ProtoHelper {
public static PropValue newPropValue(PlayerProperty key, int value) { public static PropValue newPropValue(PlayerProperty key, int value) {
return PropValue.newBuilder().setType(key.getId()).setIval(value).setVal(value).build(); return PropValue.newBuilder().setType(key.getId()).setIval(value).setVal(value).build();
} }

View File

@ -1,17 +1,19 @@
package emu.grasscutter.utils; package emu.grasscutter.utils;
import java.io.BufferedInputStream; import java.io.*;
import java.io.ByteArrayOutputStream; import java.nio.file.Files;
import java.io.IOException; import java.nio.file.StandardCopyOption;
import java.io.InputStream;
import java.util.Random; import java.util.Random;
import emu.grasscutter.Config;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled; 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 final Random random = new Random();
public static int randomRange(int min, int max) { public static int randomRange(int min, int max) {
@ -76,4 +78,77 @@ public class Utils {
} }
return v7; 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);
}
} }