This commit is contained in:
Melledy 2022-04-17 23:45:08 -07:00
commit be180179a1
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;
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";
}

View File

@ -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"

View File

@ -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;
}
}

View File

@ -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 "";
}

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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 = "<!doctype html><html lang=\"en\"><head><title>Gacha</title></head><body></body></html>";
//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 = "<!doctype html><html lang=\"en\"><head><title>Gacha</title></head><body></body></html>";
//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);

View File

@ -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);

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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];

View File

@ -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();
}

View File

@ -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);
}
}