From 3adf0d448c3a55a7ec36de532461bfe6af4d3557 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Fri, 13 May 2022 23:22:30 -0400 Subject: [PATCH] Refactor dispatch (now called HTTP) server (pt. 2) --- .../java/emu/grasscutter/Grasscutter.java | 18 +- .../command/commands/ReloadCommand.java | 2 +- .../grasscutter/game/gacha/GachaBanner.java | 6 +- .../grasscutter/plugin/api/ServerHook.java | 43 +- .../scripts/serializer/LuaSerializer.java | 6 +- .../server/dispatch/AnnouncementHandler.java | 38 -- .../server/dispatch/ClientLogHandler.java | 19 - .../server/dispatch/DispatchServer.java | 532 ------------------ .../authentication/AuthenticationHandler.java | 16 - .../DefaultAuthenticationHandler.java | 80 --- .../dispatch/http/GachaRecordHandler.java | 54 -- .../dispatch/json/ComboTokenReqJson.java | 15 - .../dispatch/json/ComboTokenResJson.java | 17 - .../json/LoginAccountRequestJson.java | 7 - .../server/dispatch/json/LoginResultJson.java | 38 -- .../dispatch/json/LoginTokenRequestJson.java | 6 - .../grasscutter/server/http/HttpServer.java | 4 +- .../server/http/dispatch/RegionHandler.java | 14 +- .../http/handlers/AnnouncementsHandler.java | 8 +- .../server/http/handlers/GachaHandler.java | 71 +++ .../server/http/handlers/GenericHandler.java | 44 +- .../http/handlers/LegacyAuthHandler.java | 17 + .../server/http/handlers/LogHandler.java | 12 +- .../objects/HttpJsonResponse.java} | 6 +- .../packet/send/PacketPlayerLoginRsp.java | 4 +- 25 files changed, 195 insertions(+), 882 deletions(-) delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/AnnouncementHandler.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/ClientLogHandler.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/authentication/AuthenticationHandler.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenReqJson.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenResJson.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/json/LoginAccountRequestJson.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/json/LoginResultJson.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/json/LoginTokenRequestJson.java create mode 100644 src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java create mode 100644 src/main/java/emu/grasscutter/server/http/handlers/LegacyAuthHandler.java rename src/main/java/emu/grasscutter/server/{dispatch/DispatchHttpJsonHandler.java => http/objects/HttpJsonResponse.java} (90%) diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index bddfa9964..30768bcb5 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -11,9 +11,7 @@ import emu.grasscutter.plugin.api.ServerHook; import emu.grasscutter.scripts.ScriptLoader; import emu.grasscutter.server.http.HttpServer; import emu.grasscutter.server.http.dispatch.DispatchHandler; -import emu.grasscutter.server.http.handlers.AnnouncementsHandler; -import emu.grasscutter.server.http.handlers.GenericHandler; -import emu.grasscutter.server.http.handlers.LogHandler; +import emu.grasscutter.server.http.handlers.*; import emu.grasscutter.server.http.dispatch.RegionHandler; import emu.grasscutter.utils.ConfigContainer; import emu.grasscutter.utils.Utils; @@ -33,7 +31,6 @@ import ch.qos.logback.classic.Logger; import emu.grasscutter.data.ResourceLoader; import emu.grasscutter.database.DatabaseManager; import emu.grasscutter.utils.Language; -import emu.grasscutter.server.dispatch.DispatchServer; import emu.grasscutter.server.game.GameServer; import emu.grasscutter.tools.Tools; import emu.grasscutter.utils.Crypto; @@ -54,7 +51,6 @@ public final class Grasscutter { private static int day; // Current day of week. - private static DispatchServer dispatchServer; private static HttpServer httpServer; private static GameServer gameServer; private static PluginManager pluginManager; @@ -113,11 +109,10 @@ public final class Grasscutter { authenticationSystem = new DefaultAuthentication(); // Create server instances. - dispatchServer = new DispatchServer(); httpServer = new HttpServer(); gameServer = new GameServer(); // Create a server hook instance with both servers. - new ServerHook(gameServer, dispatchServer); + new ServerHook(gameServer, httpServer); // Create plugin manager instance. pluginManager = new PluginManager(); @@ -129,14 +124,15 @@ public final class Grasscutter { httpServer.addRouter(GenericHandler.class); httpServer.addRouter(AnnouncementsHandler.class); httpServer.addRouter(DispatchHandler.class); + httpServer.addRouter(LegacyAuthHandler.class); + httpServer.addRouter(GachaHandler.class); // Start servers. var runMode = SERVER.runMode; if (runMode == ServerRunMode.HYBRID) { - dispatchServer.start(); + httpServer.start(); gameServer.start(); } else if (runMode == ServerRunMode.DISPATCH_ONLY) { - dispatchServer.start(); httpServer.start(); } else if (runMode == ServerRunMode.GAME_ONLY) { gameServer.start(); @@ -258,8 +254,8 @@ public final class Grasscutter { return gson; } - public static DispatchServer getDispatchServer() { - return dispatchServer; + public static HttpServer getHttpServer() { + return httpServer; } public static GameServer getGameServer() { diff --git a/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java b/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java index 9414a89c4..984fd7d60 100644 --- a/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java @@ -21,7 +21,7 @@ public final class ReloadCommand implements CommandHandler { Grasscutter.getGameServer().getGachaManager().load(); Grasscutter.getGameServer().getDropManager().load(); Grasscutter.getGameServer().getShopManager().load(); - Grasscutter.getDispatchServer().loadQueries(); + // Grasscutter.getHttpServer().loadQueries(); // Is this practical? CommandHandler.sendMessage(sender, translate(sender, "commands.reload.reload_done")); } diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java index dce433fcf..7602f7c06 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java @@ -98,9 +98,9 @@ public class GachaBanner { } public GachaInfo toProto(String sessionKey) { - String record = "http" + (DISPATCH_INFO.encryption.useInRouting ? "s" : "") + "://" - + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" - + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) + String record = "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://" + + lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":" + + lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort) + "/gacha?s=" + sessionKey + "&gachaType=" + gachaType; // Grasscutter.getLogger().info("record = " + record); GachaInfo.Builder info = GachaInfo.newBuilder() diff --git a/src/main/java/emu/grasscutter/plugin/api/ServerHook.java b/src/main/java/emu/grasscutter/plugin/api/ServerHook.java index a37abfb62..ffa19110d 100644 --- a/src/main/java/emu/grasscutter/plugin/api/ServerHook.java +++ b/src/main/java/emu/grasscutter/plugin/api/ServerHook.java @@ -1,10 +1,13 @@ package emu.grasscutter.plugin.api; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.auth.AuthenticationSystem; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.game.player.Player; -import emu.grasscutter.server.dispatch.DispatchServer; import emu.grasscutter.server.game.GameServer; +import emu.grasscutter.server.http.HttpServer; +import emu.grasscutter.server.http.Router; import java.util.LinkedList; import java.util.List; @@ -15,7 +18,7 @@ import java.util.List; public final class ServerHook { private static ServerHook instance; private final GameServer gameServer; - private final DispatchServer dispatchServer; + private final HttpServer httpServer; /** * Gets the server hook instance. @@ -28,11 +31,11 @@ public final class ServerHook { /** * Hooks into a server. * @param gameServer The game server to hook into. - * @param dispatchServer The dispatch server to hook into. + * @param httpServer The HTTP server to hook into. */ - public ServerHook(GameServer gameServer, DispatchServer dispatchServer) { + public ServerHook(GameServer gameServer, HttpServer httpServer) { this.gameServer = gameServer; - this.dispatchServer = dispatchServer; + this.httpServer = httpServer; instance = this; } @@ -45,10 +48,10 @@ public final class ServerHook { } /** - * @return The dispatch server. + * @return The HTTP server. */ - public DispatchServer getDispatchServer() { - return this.dispatchServer; + public HttpServer getHttpServer() { + return this.httpServer; } /** @@ -70,4 +73,28 @@ public final class ServerHook { Command commandData = clazz.getAnnotation(Command.class); this.gameServer.getCommandMap().registerCommand(commandData.label(), handler); } + + /** + * Adds a router using an instance of a class. + * @param router A router instance. + */ + public void addRouter(Router router) { + this.addRouter(router.getClass()); + } + + /** + * Adds a router using a class. + * @param router The class of the router. + */ + public void addRouter(Class router) { + this.httpServer.addRouter(router); + } + + /** + * Sets the server's authentication system. + * @param authSystem An instance of the authentication system. + */ + public void setAuthSystem(AuthenticationSystem authSystem) { + Grasscutter.setAuthenticationSystem(authSystem); + } } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java index a63328b55..8924a33ae 100644 --- a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java +++ b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java @@ -70,16 +70,14 @@ public class LuaSerializer implements Serializer { } try { + //noinspection ConfusingArgumentToVarargsMethod object = type.getDeclaredConstructor().newInstance(null); LuaValue[] keys = table.keys(); for (LuaValue k : keys) { try { Field field = object.getClass().getDeclaredField(k.checkjstring()); - if (field == null) { - continue; - } - + field.setAccessible(true); LuaValue keyValue = table.get(k); diff --git a/src/main/java/emu/grasscutter/server/dispatch/AnnouncementHandler.java b/src/main/java/emu/grasscutter/server/dispatch/AnnouncementHandler.java deleted file mode 100644 index ab752c8e1..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/AnnouncementHandler.java +++ /dev/null @@ -1,38 +0,0 @@ -package emu.grasscutter.server.dispatch; - -import emu.grasscutter.Grasscutter; -import express.http.HttpContextHandler; -import express.http.Request; -import express.http.Response; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.Objects; - -import static emu.grasscutter.Configuration.*; - -public final class AnnouncementHandler implements HttpContextHandler { - @Override - public void handle(Request request, Response response) throws IOException {//event - if (Objects.equals(request.baseUrl(), "/common/hk4e_global/announcement/api/getAnnContent")) { - response.send("{\"retcode\":0,\"message\":\"OK\",\"data\":" + readToString(new File(DATA("GameAnnouncement.json"))) +"}"); - } else if (Objects.equals(request.baseUrl(), "/common/hk4e_global/announcement/api/getAnnList")) { - String data = readToString(new File(DATA("GameAnnouncementList.json"))).replace("System.currentTimeMillis()",String.valueOf(System.currentTimeMillis())); - response.send("{\"retcode\":0,\"message\":\"OK\",\"data\": "+data +"}"); - } - } - @SuppressWarnings("ResultOfMethodCallIgnored") - private static String readToString(File file) { - long length = file.length(); - byte[] content = new byte[(int) length]; - try { - FileInputStream in = new FileInputStream(file); - in.read(content); in.close(); - } catch (IOException ignored) { - Grasscutter.getLogger().warn("File not found: " + file.getAbsolutePath()); - } - - return new String(content); - } -} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/server/dispatch/ClientLogHandler.java b/src/main/java/emu/grasscutter/server/dispatch/ClientLogHandler.java deleted file mode 100644 index b3d48dbbb..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/ClientLogHandler.java +++ /dev/null @@ -1,19 +0,0 @@ -package emu.grasscutter.server.dispatch; - -import express.http.HttpContextHandler; -import express.http.Request; -import express.http.Response; - -import java.io.IOException; - -/** - * Used for processing crash dumps and logs generated by the game. - * Logs are in JSON, and are sent to the server for logging. - */ -public final class ClientLogHandler implements HttpContextHandler { - @Override - public void handle(Request request, Response response) throws IOException { - // TODO: Figure out how to dump request body and log to file. - response.send("{\"code\":0}"); - } -} diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java deleted file mode 100644 index 8b8a9a185..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ /dev/null @@ -1,532 +0,0 @@ -package emu.grasscutter.server.dispatch; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.protobuf.ByteString; - -import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; -import emu.grasscutter.Grasscutter.ServerDebugMode; -import emu.grasscutter.Grasscutter.ServerRunMode; -import emu.grasscutter.database.DatabaseHelper; -import emu.grasscutter.game.Account; -import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp; -import emu.grasscutter.net.proto.QueryRegionListHttpRspOuterClass.QueryRegionListHttpRsp; -import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo; -import emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo; -import emu.grasscutter.server.dispatch.authentication.AuthenticationHandler; -import emu.grasscutter.server.dispatch.authentication.DefaultAuthenticationHandler; -import emu.grasscutter.server.dispatch.http.GachaRecordHandler; -import emu.grasscutter.server.dispatch.json.*; -import emu.grasscutter.server.dispatch.json.ComboTokenReqJson.LoginTokenData; -import emu.grasscutter.server.event.dispatch.QueryAllRegionsEvent; -import emu.grasscutter.server.event.dispatch.QueryCurrentRegionEvent; -import emu.grasscutter.tools.Tools; -import emu.grasscutter.utils.FileUtils; -import emu.grasscutter.utils.Utils; -import express.Express; -import io.javalin.http.staticfiles.Location; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.util.ssl.SslContextFactory; - -import java.io.*; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.util.*; - -import static emu.grasscutter.utils.Language.translate; -import static emu.grasscutter.Configuration.*; - -public final class DispatchServer { - public static String query_region_list = ""; - public static String query_cur_region = ""; - - private final Gson gson; - private final String defaultServerName = "os_usa"; - - public String regionListBase64; - public Map regions; - private AuthenticationHandler authHandler; - private Express httpServer; - - public DispatchServer() { - this.regions = new HashMap<>(); - this.gson = new GsonBuilder().create(); - - this.loadQueries(); - this.initRegion(); - } - - public Express getServer() { - return httpServer; - } - - public void setHttpServer(Express httpServer) { - this.httpServer.stop(); - this.httpServer = httpServer; - this.httpServer.listen(DISPATCH_INFO.bindPort); - } - - public Gson getGsonFactory() { - return gson; - } - - public QueryCurrRegionHttpRsp getCurrRegion() { - // Needs to be fixed by having the game servers connect to the dispatch server. - if (SERVER.runMode == ServerRunMode.HYBRID) { - return regions.get(defaultServerName).parsedRegionQuery; - } - - Grasscutter.getLogger().warn("[Dispatch] Unsupported run mode for getCurrRegion()"); - return null; - } - - public void loadQueries() { - File file; - - file = new File(DATA("query_region_list.txt")); - if (file.exists()) { - query_region_list = new String(FileUtils.read(file)); - } else { - Grasscutter.getLogger().warn("[Dispatch] query_region_list not found! Using default region list."); - } - - file = new File(DATA("query_cur_region.txt")); - if (file.exists()) { - query_cur_region = new String(FileUtils.read(file)); - } else { - Grasscutter.getLogger().warn("[Dispatch] query_cur_region not found! Using default current region."); - } - } - - private void initRegion() { - try { - byte[] decoded = Base64.getDecoder().decode(query_region_list); - QueryRegionListHttpRsp rl = QueryRegionListHttpRsp.parseFrom(decoded); - - byte[] decoded2 = Base64.getDecoder().decode(query_cur_region); - QueryCurrRegionHttpRsp regionQuery = QueryCurrRegionHttpRsp.parseFrom(decoded2); - - List servers = new ArrayList<>(); - List usedNames = new ArrayList<>(); // List to check for potential naming conflicts. - if (SERVER.runMode == ServerRunMode.HYBRID) { // Automatically add the game server if in hybrid mode. - RegionSimpleInfo server = RegionSimpleInfo.newBuilder() - .setName("os_usa") - .setTitle(DISPATCH_INFO.defaultName) - .setType("DEV_PUBLIC") - .setDispatchUrl( - "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://" - + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" - + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) - + "/query_cur_region/" + defaultServerName) - .build(); - usedNames.add(defaultServerName); - servers.add(server); - - RegionInfo serverRegion = regionQuery.getRegionInfo().toBuilder() - .setGateserverIp(lr(GAME_INFO.accessAddress, GAME_INFO.bindAddress)) - .setGateserverPort(lr(GAME_INFO.accessPort, GAME_INFO.bindPort)) - .setSecretKey(ByteString.copyFrom(FileUtils.read(KEYS_FOLDER + "/dispatchSeed.bin"))) - .build(); - - QueryCurrRegionHttpRsp parsedRegionQuery = regionQuery.toBuilder().setRegionInfo(serverRegion).build(); - regions.put(defaultServerName, new RegionData(parsedRegionQuery, - Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray()))); - - } else if (DISPATCH_INFO.regions.length == 0) { - Grasscutter.getLogger().error("[Dispatch] There are no game servers available. Exiting due to unplayable state."); - System.exit(1); - } - - for (var regionInfo : DISPATCH_INFO.regions) { - if (usedNames.contains(regionInfo.Name)) { - Grasscutter.getLogger().error("Region name already in use."); - continue; - } - RegionSimpleInfo server = RegionSimpleInfo.newBuilder() - .setName(regionInfo.Name) - .setTitle(regionInfo.Title) - .setType("DEV_PUBLIC") - .setDispatchUrl( - "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://" - + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" - + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) - + "/query_cur_region/" + regionInfo.Name) - .build(); - usedNames.add(regionInfo.Name); - servers.add(server); - - RegionInfo serverRegion = regionQuery.getRegionInfo().toBuilder() - .setGateserverIp(regionInfo.Ip) - .setGateserverPort(regionInfo.Port) - .setSecretKey(ByteString - .copyFrom(FileUtils.read(KEYS_FOLDER + "/dispatchSeed.bin"))) - .build(); - - QueryCurrRegionHttpRsp parsedRegionQuery = regionQuery.toBuilder().setRegionInfo(serverRegion).build(); - regions.put(regionInfo.Name, new RegionData(parsedRegionQuery, - Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray()))); - } - - QueryRegionListHttpRsp regionList = QueryRegionListHttpRsp.newBuilder() - .addAllRegionList(servers) - .setClientSecretKey(rl.getClientSecretKey()) - .setClientCustomConfigEncrypted(rl.getClientCustomConfigEncrypted()) - .setEnableLoginPc(true) - .build(); - - this.regionListBase64 = Base64.getEncoder().encodeToString(regionList.toByteString().toByteArray()); - } catch (Exception exception) { - Grasscutter.getLogger().error("[Dispatch] Error while initializing region info!", exception); - } - } - - public void start() throws Exception { - httpServer = new Express(config -> { - config.server(() -> { - Server server = new Server(); - ServerConnector serverConnector; - - if(HTTP_ENCRYPTION.useEncryption) { - SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); - File keystoreFile = new File(HTTP_ENCRYPTION.keystore); - - if(keystoreFile.exists()) { - try { - sslContextFactory.setKeyStorePath(keystoreFile.getPath()); - sslContextFactory.setKeyStorePassword(HTTP_ENCRYPTION.keystorePassword); - } catch (Exception e) { - e.printStackTrace(); - Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.password_error")); - - try { - sslContextFactory.setKeyStorePath(keystoreFile.getPath()); - sslContextFactory.setKeyStorePassword("123456"); - Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.default_password")); - } catch (Exception e2) { - Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.general_error")); - e2.printStackTrace(); - } - } - - serverConnector = new ServerConnector(server, sslContextFactory); - } else { - Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.no_keystore_error")); - HTTP_ENCRYPTION.useEncryption = false; - - serverConnector = new ServerConnector(server); - } - } else { - serverConnector = new ServerConnector(server); - } - - serverConnector.setPort(DISPATCH_INFO.bindPort); - server.setConnectors(new Connector[]{serverConnector}); - return server; - }); - - config.enforceSsl = HTTP_ENCRYPTION.useEncryption; - if(SERVER.debugLevel == ServerDebugMode.ALL) { - config.enableDevLogging(); - } - - if (HTTP_POLICIES.cors.enabled) { - var corsPolicy = HTTP_POLICIES.cors; - if (corsPolicy.allowedOrigins.length > 0) - config.enableCorsForOrigin(corsPolicy.allowedOrigins); - else config.enableCorsForAllOrigins(); - } - }); - - httpServer.get("/", (req, res) -> res.send("" + translate("messages.status.welcome") + "")); - - httpServer.raw().error(404, ctx -> { - if(SERVER.debugLevel == ServerDebugMode.MISSING) { - Grasscutter.getLogger().info(translate("messages.dispatch.unhandled_request_error", ctx.method(), ctx.url())); - } - ctx.contentType("text/html"); - ctx.result(""); // I'm like 70% sure this won't break anything. - }); - - // Authentication Handler - // These routes are so that authentication routes are always the same no matter what auth system is used. - httpServer.get("/authentication/type", (req, res) -> { - res.send(this.getAuthHandler().getClass().getName()); - }); - - httpServer.post("/authentication/login", (req, res) -> this.getAuthHandler().handleLogin(req, res)); - httpServer.post("/authentication/register", (req, res) -> this.getAuthHandler().handleRegister(req, res)); - httpServer.post("/authentication/change_password", (req, res) -> this.getAuthHandler().handleChangePassword(req, res)); - - // Server Status - httpServer.get("/status/server", (req, res) -> { - - int playerCount = Grasscutter.getGameServer().getPlayers().size(); - String version = GameConstants.VERSION; - - res.send("{\"retcode\":0,\"status\":{\"playerCount\":" + playerCount + ",\"version\":\"" + version + "\"}}"); - }); - - // Dispatch - httpServer.get("/query_region_list", (req, res) -> { - // Log - Grasscutter.getLogger().info(String.format("[Dispatch] Client %s request: query_region_list", req.ip())); - - // Invoke event. - QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListBase64); event.call(); - // Respond with event result. - res.send(event.getRegionList()); - }); - - // /server/:id -> 2.6.5x - httpServer.get("/query_cur_region/:id", (req, res) -> { - String regionName = req.params("id"); - // Log - Grasscutter.getLogger().info( - String.format("Client %s request: query_cur_region/%s", req.ip(), regionName)); - // Create a response form the request query parameters - String response = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw=="; - if (req.query().values().size() > 0) { - response = regions.get(regionName).Base64; - } - - // Invoke event. - QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(response); event.call(); - // Respond with event result. - res.send(event.getRegionInfo()); - }); - - // Login - - httpServer.post("/hk4e_global/mdk/shield/api/login", (req, res) -> { - // Get post data - LoginAccountRequestJson requestData = null; - try { - String body = req.ctx().body(); - requestData = getGsonFactory().fromJson(body, LoginAccountRequestJson.class); - } catch (Exception ignored) { } - - // Create response json - if (requestData == null) { - return; - } - Grasscutter.getLogger().info(translate("messages.dispatch.account.login_attempt", req.ip())); - - res.send(this.getAuthHandler().handleGameLogin(req, requestData)); - }); - - // Login via token - httpServer.post("/hk4e_global/mdk/shield/api/verify", (req, res) -> { - // Get post data - LoginTokenRequestJson requestData = null; - try { - String body = req.ctx().body(); - requestData = getGsonFactory().fromJson(body, LoginTokenRequestJson.class); - } catch (Exception ignored) { - } - - // Create response json - if (requestData == null) { - return; - } - LoginResultJson responseData = new LoginResultJson(); - Grasscutter.getLogger().info(translate("messages.dispatch.account.login_token_attempt", req.ip())); - - // Login - Account account = DatabaseHelper.getAccountById(requestData.uid); - - // Test - if (account == null || !account.getSessionKey().equals(requestData.token)) { - responseData.retcode = -111; - responseData.message = translate("messages.dispatch.account.account_cache_error"); - - Grasscutter.getLogger().info(translate("messages.dispatch.account.login_token_error", req.ip())); - } else { - responseData.message = "OK"; - responseData.data.account.uid = requestData.uid; - responseData.data.account.token = requestData.token; - responseData.data.account.email = account.getEmail(); - - Grasscutter.getLogger().info(translate("messages.dispatch.account.login_token_success", req.ip(), requestData.uid)); - } - - res.send(responseData); - }); - - // Exchange for combo token - httpServer.post("/hk4e_global/combo/granter/login/v2/login", (req, res) -> { - // Get post data - ComboTokenReqJson requestData = null; - try { - String body = req.ctx().body(); - requestData = getGsonFactory().fromJson(body, ComboTokenReqJson.class); - } catch (Exception ignored) { - } - - // 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 = translate("messages.dispatch.account.session_key_error"); - - Grasscutter.getLogger().info(translate("messages.dispatch.account.combo_token_error", req.ip())); - } else { - responseData.message = "OK"; - responseData.data.open_id = loginData.uid; - responseData.data.combo_id = "157795300"; - responseData.data.combo_token = account.generateLoginToken(); - - Grasscutter.getLogger().info(translate("messages.dispatch.account.combo_token_success", req.ip())); - } - - res.send(responseData); - }); - - // TODO: There are some missing route request types here (You can tell if they are missing if they are .all and not anything else) - // When http requests for theses routes are found please remove it from the list in DispatchHttpJsonHandler and update the route request types here - - // Agreement and Protocol - // hk4e-sdk-os.hoyoverse.com - httpServer.get("/hk4e_global/mdk/agreement/api/getAgreementInfos", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"marketing_agreements\":[]}}")); - // hk4e-sdk-os.hoyoverse.com - // this could be either GET or POST based on the observation of different clients - httpServer.all("/hk4e_global/combo/granter/api/compareProtocolVersion", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"modified\":true,\"protocol\":{\"id\":0,\"app_id\":4,\"language\":\"en\",\"user_proto\":\"\",\"priv_proto\":\"\",\"major\":7,\"minimum\":0,\"create_time\":\"0\",\"teenager_proto\":\"\",\"third_proto\":\"\"}}}")); - - // Game data - // hk4e-api-os.hoyoverse.com - httpServer.all("/common/hk4e_global/announcement/api/getAlertPic", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}")); - // hk4e-api-os.hoyoverse.com - httpServer.all("/common/hk4e_global/announcement/api/getAlertAnn", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"alert\":false,\"alert_id\":0,\"remind\":true}}")); - // hk4e-api-os.hoyoverse.com - httpServer.all("/common/hk4e_global/announcement/api/getAnnList", new AnnouncementHandler()); - // hk4e-api-os-static.hoyoverse.com - httpServer.all("/common/hk4e_global/announcement/api/getAnnContent", new AnnouncementHandler()); - // hk4e-sdk-os.hoyoverse.com - httpServer.all("/hk4e_global/mdk/shopwindow/shopwindow/listPriceTier", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"suggest_currency\":\"USD\",\"tiers\":[]}}")); - - // Captcha - // api-account-os.hoyoverse.com - httpServer.post("/account/risky/api/check", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":\"none\",\"action\":\"ACTION_NONE\",\"geetest\":null}}")); - - // Config - // sdk-os-static.hoyoverse.com - httpServer.get("/combo/box/api/config/sdk/combo", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"vals\":{\"disable_email_bind_skip\":\"false\",\"email_bind_remind_interval\":\"7\",\"email_bind_remind\":\"true\"}}}")); - // hk4e-sdk-os-static.hoyoverse.com - httpServer.get("/hk4e_global/combo/granter/api/getConfig", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"protocol\":true,\"qr_enabled\":false,\"log_level\":\"INFO\",\"announce_url\":\"https://webstatic-sea.hoyoverse.com/hk4e/announcement/index.html?sdk_presentation_style=fullscreen\\u0026sdk_screen_transparent=true\\u0026game_biz=hk4e_global\\u0026auth_appid=announcement\\u0026game=hk4e#/\",\"push_alias_type\":2,\"disable_ysdk_guard\":false,\"enable_announce_pic_popup\":true}}")); - // hk4e-sdk-os-static.hoyoverse.com - httpServer.get("/hk4e_global/mdk/shield/api/loadConfig", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":6,\"game_key\":\"hk4e_global\",\"client\":\"PC\",\"identity\":\"I_IDENTITY\",\"guest\":false,\"ignore_versions\":\"\",\"scene\":\"S_NORMAL\",\"name\":\"原神海外\",\"disable_regist\":false,\"enable_email_captcha\":false,\"thirdparty\":[\"fb\",\"tw\"],\"disable_mmt\":false,\"server_guest\":false,\"thirdparty_ignore\":{\"tw\":\"\",\"fb\":\"\"},\"enable_ps_bind_account\":false,\"thirdparty_login_configs\":{\"tw\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800},\"fb\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800}}}}")); - // Test api? - // abtest-api-data-sg.hoyoverse.com - httpServer.post("/data_abtest_api/config/experiment/list", new DispatchHttpJsonHandler("{\"retcode\":0,\"success\":true,\"message\":\"\",\"data\":[{\"code\":1000,\"type\":2,\"config_id\":\"14\",\"period_id\":\"6036_99\",\"version\":\"1\",\"configs\":{\"cardType\":\"old\"}}]}")); - - // log-upload-os.mihoyo.com - httpServer.all("/log/sdk/upload", new DispatchHttpJsonHandler("{\"code\":0}")); - httpServer.all("/sdk/upload", new DispatchHttpJsonHandler("{\"code\":0}")); - httpServer.post("/sdk/dataUpload", new DispatchHttpJsonHandler("{\"code\":0}")); - // /perf/config/verify?device_id=xxx&platform=x&name=xxx - httpServer.all("/perf/config/verify", new DispatchHttpJsonHandler("{\"code\":0}")); - - // Logging servers - // overseauspider.yuanshen.com - httpServer.all("/log", new ClientLogHandler()); - // log-upload-os.mihoyo.com - httpServer.all("/crash/dataUpload", new ClientLogHandler()); - - // webstatic-sea.hoyoverse.com - httpServer.get("/admin/mi18n/plat_oversea/m202003048/m202003048-version.json", new DispatchHttpJsonHandler("{\"version\":51}")); - - // gacha record. - String gachaMappingsPath = Utils.toFilePath(DATA("/gacha_mappings.js")); - // TODO: Only serve the html page and have a subsequent request to fetch the gacha data. - httpServer.get("/gacha", new GachaRecordHandler()); - if(!(new File(gachaMappingsPath).exists())) { - Tools.createGachaMapping(gachaMappingsPath); - } - - httpServer.raw().config.addSinglePageRoot("/gacha/mappings", gachaMappingsPath, Location.EXTERNAL); - - // static file support for plugins - httpServer.raw().config.precompressStaticFiles = false; // If this isn't set to false, files such as images may appear corrupted when serving static files - - httpServer.listen(DISPATCH_INFO.bindPort); - Grasscutter.getLogger().info(translate("messages.dispatch.port_bind", Integer.toString(httpServer.raw().port()))); - } - - private Map parseQueryString(String qs) { - Map result = new HashMap<>(); - if (qs == null) { - return result; - } - - int last = 0, next, l = qs.length(); - while (last < l) { - next = qs.indexOf('&', last); - if (next == -1) { - next = l; - } - - if (next > last) { - int eqPos = qs.indexOf('=', last); - if (eqPos < 0 || eqPos > next) { - result.put(URLDecoder.decode(qs.substring(last, next), StandardCharsets.UTF_8), ""); - } else { - result.put(URLDecoder.decode(qs.substring(last, eqPos), StandardCharsets.UTF_8), - URLDecoder.decode(qs.substring(eqPos + 1, next), StandardCharsets.UTF_8)); - } - } - last = next + 1; - } - return result; - } - - public AuthenticationHandler getAuthHandler() { - if(authHandler == null) { - return new DefaultAuthenticationHandler(); - } - return authHandler; - } - - public boolean registerAuthHandler(AuthenticationHandler authHandler) { - if(this.authHandler != null) { - Grasscutter.getLogger().error(String.format("[Dispatch] Unable to register '%s' authentication handler. \n" + - "The '%s' authentication handler has already been registered", authHandler.getClass().getName(), this.authHandler.getClass().getName())); - return false; - } - this.authHandler = authHandler; - return true; - } - - public void resetAuthHandler() { - this.authHandler = null; - } - - public static class RegionData { - QueryCurrRegionHttpRsp parsedRegionQuery; - String Base64; - - public RegionData(QueryCurrRegionHttpRsp prq, String b64) { - this.parsedRegionQuery = prq; - this.Base64 = b64; - } - - public QueryCurrRegionHttpRsp getParsedRegionQuery() { - return parsedRegionQuery; - } - - public String getBase64() { - return Base64; - } - } -} diff --git a/src/main/java/emu/grasscutter/server/dispatch/authentication/AuthenticationHandler.java b/src/main/java/emu/grasscutter/server/dispatch/authentication/AuthenticationHandler.java deleted file mode 100644 index 92a2961ea..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/authentication/AuthenticationHandler.java +++ /dev/null @@ -1,16 +0,0 @@ -package emu.grasscutter.server.dispatch.authentication; - -import emu.grasscutter.server.dispatch.json.LoginAccountRequestJson; -import emu.grasscutter.server.dispatch.json.LoginResultJson; -import express.http.Request; -import express.http.Response; - -public interface AuthenticationHandler { - - // This is in case plugins also want some sort of authentication - void handleLogin(Request req, Response res); - void handleRegister(Request req, Response res); - void handleChangePassword(Request req, Response res); - - LoginResultJson handleGameLogin(Request req, LoginAccountRequestJson requestData); -} diff --git a/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java b/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java deleted file mode 100644 index 67b3d4023..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java +++ /dev/null @@ -1,80 +0,0 @@ -package emu.grasscutter.server.dispatch.authentication; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.database.DatabaseHelper; -import emu.grasscutter.game.Account; -import emu.grasscutter.server.dispatch.json.LoginAccountRequestJson; -import emu.grasscutter.server.dispatch.json.LoginResultJson; -import express.http.Request; -import express.http.Response; - -import static emu.grasscutter.utils.Language.translate; -import static emu.grasscutter.Configuration.*; - -public class DefaultAuthenticationHandler implements AuthenticationHandler { - - @Override - public void handleLogin(Request req, Response res) { - res.send("Authentication is not available with the default authentication method"); - } - - @Override - public void handleRegister(Request req, Response res) { - res.send("Authentication is not available with the default authentication method"); - } - - @Override - public void handleChangePassword(Request req, Response res) { - res.send("Authentication is not available with the default authentication method"); - } - - @Override - public LoginResultJson handleGameLogin(Request req, LoginAccountRequestJson requestData) { - LoginResultJson responseData = new LoginResultJson(); - - // Login - Account account = DatabaseHelper.getAccountByName(requestData.account); - - // Check if account exists, else create a new one. - if (account == null) { - // Account doesn't exist, so we can either auto create it if the config value is set. - if (ACCOUNT.autoCreate) { - // This account has been created AUTOMATICALLY. There will be no permissions added. - account = DatabaseHelper.createAccountWithId(requestData.account, 0); - - for (String permission : ACCOUNT.defaultPermissions) { - account.addPermission(permission); - } - - if (account != null) { - responseData.message = "OK"; - responseData.data.account.uid = account.getId(); - responseData.data.account.token = account.generateSessionKey(); - responseData.data.account.email = account.getEmail(); - - Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_success", req.ip(), responseData.data.account.uid)); - } else { - responseData.retcode = -201; - responseData.message = translate("messages.dispatch.account.username_create_error"); - - Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_error", req.ip())); - } - } else { - responseData.retcode = -201; - responseData.message = translate("messages.dispatch.account.username_error"); - - Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_exist_error", req.ip())); - } - } else { - // Account was found, log the player in - responseData.message = "OK"; - responseData.data.account.uid = account.getId(); - responseData.data.account.token = account.generateSessionKey(); - responseData.data.account.email = account.getEmail(); - - Grasscutter.getLogger().info(translate("messages.dispatch.account.login_success", req.ip(), responseData.data.account.uid)); - } - - return responseData; - } -} diff --git a/src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java b/src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java deleted file mode 100644 index b90510367..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java +++ /dev/null @@ -1,54 +0,0 @@ -package emu.grasscutter.server.dispatch.http; - -import java.io.File; -import java.io.IOException; - -import emu.grasscutter.database.DatabaseHelper; -import emu.grasscutter.game.Account; -import emu.grasscutter.utils.FileUtils; -import emu.grasscutter.utils.Utils; -import express.http.HttpContextHandler; -import express.http.Request; -import express.http.Response; - -import static emu.grasscutter.Configuration.*; - -public final class GachaRecordHandler implements HttpContextHandler { - String render_template; - public GachaRecordHandler() { - File template = new File(Utils.toFilePath(DATA("/gacha_records.html"))); - if (template.exists()) { - // Load from cache - render_template = new String(FileUtils.read(template)); - } else { - render_template = "{{REPLACE_RECORD}}"; - } - } - - @Override - public void handle(Request req, Response res) throws IOException { - // Grasscutter.getLogger().info( req.query().toString() ); - String sessionKey = req.query("s"); - int page = 0; - int gachaType = 0; - if (req.query("p") != null) { - page = Integer.parseInt(req.query("p")); - } - - if (req.query("gachaType") != null) { - gachaType = Integer.parseInt(req.query("gachaType")); - } - - Account account = DatabaseHelper.getAccountBySessionKey(sessionKey); - if (account != null) { - String records = DatabaseHelper.getGachaRecords(account.getPlayerUid(), page, gachaType).toString(); - // Grasscutter.getLogger().info(records); - String response = render_template.replace("{{REPLACE_RECORD}}", records) - .replace("{{REPLACE_MAXPAGE}}", String.valueOf(DatabaseHelper.getGachaRecordsMaxPage(account.getPlayerUid(), page, gachaType))); - - res.send(response); - } else { - res.send("No account found."); - } - } -} diff --git a/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenReqJson.java b/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenReqJson.java deleted file mode 100644 index b3497f8d4..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenReqJson.java +++ /dev/null @@ -1,15 +0,0 @@ -package emu.grasscutter.server.dispatch.json; - -public class ComboTokenReqJson { - public int app_id; - public int channel_id; - public String data; - public String device; - public String sign; - - public static class LoginTokenData { - public String uid; - public String token; - public boolean guest; - } -} diff --git a/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenResJson.java b/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenResJson.java deleted file mode 100644 index 7c49d1278..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenResJson.java +++ /dev/null @@ -1,17 +0,0 @@ -package emu.grasscutter.server.dispatch.json; - -public class ComboTokenResJson { - public String message; - public int retcode; - public LoginData data = new LoginData(); - - public static class LoginData { - public int account_type = 1; - public boolean heartbeat; - public String combo_id; - public String combo_token; - public String open_id; - public String data = "{\"guest\":false}"; - public String fatigue_remind = null; // ? - } -} diff --git a/src/main/java/emu/grasscutter/server/dispatch/json/LoginAccountRequestJson.java b/src/main/java/emu/grasscutter/server/dispatch/json/LoginAccountRequestJson.java deleted file mode 100644 index cb3aff349..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/json/LoginAccountRequestJson.java +++ /dev/null @@ -1,7 +0,0 @@ -package emu.grasscutter.server.dispatch.json; - -public class LoginAccountRequestJson { - public String account; - public String password; - public boolean is_crypto; -} diff --git a/src/main/java/emu/grasscutter/server/dispatch/json/LoginResultJson.java b/src/main/java/emu/grasscutter/server/dispatch/json/LoginResultJson.java deleted file mode 100644 index 1f4dcd4b4..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/json/LoginResultJson.java +++ /dev/null @@ -1,38 +0,0 @@ -package emu.grasscutter.server.dispatch.json; - -public class LoginResultJson { - public String message; - public int retcode; - public VerifyData data = new VerifyData(); - - public static class VerifyData { - public VerifyAccountData account = new VerifyAccountData(); - public boolean device_grant_required = false; - public String realname_operation = "NONE"; - public boolean realperson_required = false; - public boolean safe_mobile_required = false; - } - - public static class VerifyAccountData { - public String uid; - public String name = ""; - public String email = ""; - public String mobile = ""; - public String is_email_verify = "0"; - public String realname = ""; - public String identity_card = ""; - public String token; - public String safe_mobile = ""; - public String facebook_name = ""; - public String twitter_name = ""; - public String game_center_name = ""; - public String google_name = ""; - public String apple_name = ""; - public String sony_name = ""; - public String tap_name = ""; - public String country = "US"; - public String reactivate_ticket = ""; - public String area_code = "**"; - public String device_grant_ticket = ""; - } -} diff --git a/src/main/java/emu/grasscutter/server/dispatch/json/LoginTokenRequestJson.java b/src/main/java/emu/grasscutter/server/dispatch/json/LoginTokenRequestJson.java deleted file mode 100644 index 12fed8f09..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/json/LoginTokenRequestJson.java +++ /dev/null @@ -1,6 +0,0 @@ -package emu.grasscutter.server.dispatch.json; - -public class LoginTokenRequestJson { - public String uid; - public String token; -} diff --git a/src/main/java/emu/grasscutter/server/http/HttpServer.java b/src/main/java/emu/grasscutter/server/http/HttpServer.java index dc0d396a6..5227d9793 100644 --- a/src/main/java/emu/grasscutter/server/http/HttpServer.java +++ b/src/main/java/emu/grasscutter/server/http/HttpServer.java @@ -162,11 +162,11 @@ public final class HttpServer { - + - + """); diff --git a/src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java b/src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java index e720a4b15..8b5dbeec7 100644 --- a/src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java +++ b/src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java @@ -151,8 +151,10 @@ public final class RegionHandler implements Router { // Get region data. String regionData = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw=="; - if (request.query().values().size() > 0) - regionData = regions.get(regionName).getBase64(); + if (request.query().values().size() > 0) { + var region = regions.get(regionName); + if(region != null) regionData = region.getBase64(); + } // Invoke event. QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(regionData); event.call(); @@ -183,4 +185,12 @@ public final class RegionHandler implements Router { return this.base64; } } + + /** + * Gets the current region query. + * @return A {@link QueryCurrRegionHttpRsp} object. + */ + public static QueryCurrRegionHttpRsp getCurrentRegion() { + return SERVER.runMode == ServerRunMode.HYBRID ? regions.get("os_usa").getRegionQuery() : null; + } } diff --git a/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java index a64e0552a..794b88ed4 100644 --- a/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java +++ b/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java @@ -1,7 +1,7 @@ package emu.grasscutter.server.http.handlers; import emu.grasscutter.Grasscutter; -import emu.grasscutter.server.dispatch.DispatchHttpJsonHandler; +import emu.grasscutter.server.http.objects.HttpJsonResponse; import emu.grasscutter.server.http.Router; import express.Express; import express.http.Request; @@ -21,15 +21,15 @@ import static emu.grasscutter.Configuration.DATA; public final class AnnouncementsHandler implements Router { @Override public void applyRoutes(Express express, Javalin handle) { // hk4e-api-os.hoyoverse.com - express.all("/common/hk4e_global/announcement/api/getAlertPic", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}")); + express.all("/common/hk4e_global/announcement/api/getAlertPic", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}")); // hk4e-api-os.hoyoverse.com - express.all("/common/hk4e_global/announcement/api/getAlertAnn", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"alert\":false,\"alert_id\":0,\"remind\":true}}")); + express.all("/common/hk4e_global/announcement/api/getAlertAnn", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"alert\":false,\"alert_id\":0,\"remind\":true}}")); // hk4e-api-os.hoyoverse.com express.all("/common/hk4e_global/announcement/api/getAnnList", AnnouncementsHandler::getAnnouncement); // hk4e-api-os-static.hoyoverse.com express.all("/common/hk4e_global/announcement/api/getAnnContent", AnnouncementsHandler::getAnnouncement); // hk4e-sdk-os.hoyoverse.com - express.all("/hk4e_global/mdk/shopwindow/shopwindow/listPriceTier", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"suggest_currency\":\"USD\",\"tiers\":[]}}")); + express.all("/hk4e_global/mdk/shopwindow/shopwindow/listPriceTier", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"suggest_currency\":\"USD\",\"tiers\":[]}}")); } private static void getAnnouncement(Request request, Response response) { diff --git a/src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java new file mode 100644 index 000000000..6cb27d90d --- /dev/null +++ b/src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java @@ -0,0 +1,71 @@ +package emu.grasscutter.server.http.handlers; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.server.http.Router; +import emu.grasscutter.tools.Tools; +import emu.grasscutter.utils.FileUtils; +import emu.grasscutter.utils.Utils; +import express.Express; +import express.http.Request; +import express.http.Response; +import io.javalin.Javalin; +import io.javalin.http.staticfiles.Location; + +import java.io.File; + +import static emu.grasscutter.Configuration.DATA; + +/** + * Handles all gacha-related HTTP requests. + */ +public final class GachaHandler implements Router { + private final String gachaMappings; + + private static String frontendTemplate = "{{REPLACE_RECORD}}"; + + public GachaHandler() { + this.gachaMappings = Utils.toFilePath(DATA("/gacha_mappings.js")); + if(!(new File(this.gachaMappings).exists())) { + try { + Tools.createGachaMapping(this.gachaMappings); + } catch (Exception exception) { + Grasscutter.getLogger().warn("Failed to create gacha mappings.", exception); + } + } + + var templateFile = new File(DATA("/gacha_records.html")); + if(templateFile.exists()) + frontendTemplate = new String(FileUtils.read(templateFile)); + } + + @Override public void applyRoutes(Express express, Javalin handle) { + express.get("/gacha", GachaHandler::gachaRecords); + + express.useStaticFallback("/gacha/mappings", this.gachaMappings, Location.EXTERNAL); + } + + private static void gachaRecords(Request request, Response response) { + var sessionKey = request.query("s"); + + int page = 0, gachaType = 0; + if(request.query("p") != null) + page = Integer.parseInt(request.query("p")); + if(request.query("gachaType") != null) + gachaType = Integer.parseInt(request.query("gachaType")); + + // Get account from session key. + var account = DatabaseHelper.getAccountBySessionKey(sessionKey); + + if(account == null) // Send response. + response.status(404).send("Unable to find account."); + else { + String records = DatabaseHelper.getGachaRecords(account.getPlayerUid(), gachaType, page).toString(); + long maxPage = DatabaseHelper.getGachaRecordsMaxPage(account.getPlayerUid(), page, gachaType); + + response.send(frontendTemplate + .replace("{{REPLACE_RECORD}}", records) + .replace("{{REPLACE_MAXPAGE}}", String.valueOf(maxPage))); + } + } +} diff --git a/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java index bb0bc8eea..2de8969d7 100644 --- a/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java +++ b/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java @@ -1,8 +1,12 @@ package emu.grasscutter.server.http.handlers; -import emu.grasscutter.server.dispatch.DispatchHttpJsonHandler; +import emu.grasscutter.GameConstants; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.server.http.objects.HttpJsonResponse; import emu.grasscutter.server.http.Router; import express.Express; +import express.http.Request; +import express.http.Response; import io.javalin.Javalin; /** @@ -11,37 +15,41 @@ import io.javalin.Javalin; public final class GenericHandler implements Router { @Override public void applyRoutes(Express express, Javalin handle) { // hk4e-sdk-os.hoyoverse.com - express.get("/hk4e_global/mdk/agreement/api/getAgreementInfos", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"marketing_agreements\":[]}}")); + express.get("/hk4e_global/mdk/agreement/api/getAgreementInfos", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"marketing_agreements\":[]}}")); // hk4e-sdk-os.hoyoverse.com // this could be either GET or POST based on the observation of different clients - express.all("/hk4e_global/combo/granter/api/compareProtocolVersion", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"modified\":true,\"protocol\":{\"id\":0,\"app_id\":4,\"language\":\"en\",\"user_proto\":\"\",\"priv_proto\":\"\",\"major\":7,\"minimum\":0,\"create_time\":\"0\",\"teenager_proto\":\"\",\"third_proto\":\"\"}}}")); + express.all("/hk4e_global/combo/granter/api/compareProtocolVersion", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"modified\":true,\"protocol\":{\"id\":0,\"app_id\":4,\"language\":\"en\",\"user_proto\":\"\",\"priv_proto\":\"\",\"major\":7,\"minimum\":0,\"create_time\":\"0\",\"teenager_proto\":\"\",\"third_proto\":\"\"}}}")); // api-account-os.hoyoverse.com - express.post("/account/risky/api/check", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":\"none\",\"action\":\"ACTION_NONE\",\"geetest\":null}}")); + express.post("/account/risky/api/check", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":\"none\",\"action\":\"ACTION_NONE\",\"geetest\":null}}")); // sdk-os-static.hoyoverse.com - express.get("/combo/box/api/config/sdk/combo", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"vals\":{\"disable_email_bind_skip\":\"false\",\"email_bind_remind_interval\":\"7\",\"email_bind_remind\":\"true\"}}}")); + express.get("/combo/box/api/config/sdk/combo", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"vals\":{\"disable_email_bind_skip\":\"false\",\"email_bind_remind_interval\":\"7\",\"email_bind_remind\":\"true\"}}}")); // hk4e-sdk-os-static.hoyoverse.com - express.get("/hk4e_global/combo/granter/api/getConfig", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"protocol\":true,\"qr_enabled\":false,\"log_level\":\"INFO\",\"announce_url\":\"https://webstatic-sea.hoyoverse.com/hk4e/announcement/index.html?sdk_presentation_style=fullscreen\\u0026sdk_screen_transparent=true\\u0026game_biz=hk4e_global\\u0026auth_appid=announcement\\u0026game=hk4e#/\",\"push_alias_type\":2,\"disable_ysdk_guard\":false,\"enable_announce_pic_popup\":true}}")); + express.get("/hk4e_global/combo/granter/api/getConfig", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"protocol\":true,\"qr_enabled\":false,\"log_level\":\"INFO\",\"announce_url\":\"https://webstatic-sea.hoyoverse.com/hk4e/announcement/index.html?sdk_presentation_style=fullscreen\\u0026sdk_screen_transparent=true\\u0026game_biz=hk4e_global\\u0026auth_appid=announcement\\u0026game=hk4e#/\",\"push_alias_type\":2,\"disable_ysdk_guard\":false,\"enable_announce_pic_popup\":true}}")); // hk4e-sdk-os-static.hoyoverse.com - express.get("/hk4e_global/mdk/shield/api/loadConfig", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":6,\"game_key\":\"hk4e_global\",\"client\":\"PC\",\"identity\":\"I_IDENTITY\",\"guest\":false,\"ignore_versions\":\"\",\"scene\":\"S_NORMAL\",\"name\":\"原神海外\",\"disable_regist\":false,\"enable_email_captcha\":false,\"thirdparty\":[\"fb\",\"tw\"],\"disable_mmt\":false,\"server_guest\":false,\"thirdparty_ignore\":{\"tw\":\"\",\"fb\":\"\"},\"enable_ps_bind_account\":false,\"thirdparty_login_configs\":{\"tw\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800},\"fb\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800}}}}")); + express.get("/hk4e_global/mdk/shield/api/loadConfig", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":6,\"game_key\":\"hk4e_global\",\"client\":\"PC\",\"identity\":\"I_IDENTITY\",\"guest\":false,\"ignore_versions\":\"\",\"scene\":\"S_NORMAL\",\"name\":\"原神海外\",\"disable_regist\":false,\"enable_email_captcha\":false,\"thirdparty\":[\"fb\",\"tw\"],\"disable_mmt\":false,\"server_guest\":false,\"thirdparty_ignore\":{\"tw\":\"\",\"fb\":\"\"},\"enable_ps_bind_account\":false,\"thirdparty_login_configs\":{\"tw\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800},\"fb\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800}}}}")); // Test api? // abtest-api-data-sg.hoyoverse.com - express.post("/data_abtest_api/config/experiment/list", new DispatchHttpJsonHandler("{\"retcode\":0,\"success\":true,\"message\":\"\",\"data\":[{\"code\":1000,\"type\":2,\"config_id\":\"14\",\"period_id\":\"6036_99\",\"version\":\"1\",\"configs\":{\"cardType\":\"old\"}}]}")); - - // hk4e-api-os.hoyoverse.com - express.all("/common/hk4e_global/announcement/api/getAlertPic", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}")); - // hk4e-api-os.hoyoverse.com - express.all("/common/hk4e_global/announcement/api/getAlertAnn", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"alert\":false,\"alert_id\":0,\"remind\":true}}")); + express.post("/data_abtest_api/config/experiment/list", new HttpJsonResponse("{\"retcode\":0,\"success\":true,\"message\":\"\",\"data\":[{\"code\":1000,\"type\":2,\"config_id\":\"14\",\"period_id\":\"6036_99\",\"version\":\"1\",\"configs\":{\"cardType\":\"old\"}}]}")); // log-upload-os.mihoyo.com - express.all("/log/sdk/upload", new DispatchHttpJsonHandler("{\"code\":0}")); - express.all("/sdk/upload", new DispatchHttpJsonHandler("{\"code\":0}")); - express.post("/sdk/dataUpload", new DispatchHttpJsonHandler("{\"code\":0}")); + express.all("/log/sdk/upload", new HttpJsonResponse("{\"code\":0}")); + express.all("/sdk/upload", new HttpJsonResponse("{\"code\":0}")); + express.post("/sdk/dataUpload", new HttpJsonResponse("{\"code\":0}")); // /perf/config/verify?device_id=xxx&platform=x&name=xxx - express.all("/perf/config/verify", new DispatchHttpJsonHandler("{\"code\":0}")); + express.all("/perf/config/verify", new HttpJsonResponse("{\"code\":0}")); // webstatic-sea.hoyoverse.com - express.get("/admin/mi18n/plat_oversea/m202003048/m202003048-version.json", new DispatchHttpJsonHandler("{\"version\":51}")); + express.get("/admin/mi18n/plat_oversea/m202003048/m202003048-version.json", new HttpJsonResponse("{\"version\":51}")); + + express.get("/status/server", GenericHandler::serverStatus); + } + + private static void serverStatus(Request request, Response response) { + int playerCount = Grasscutter.getGameServer().getPlayers().size(); + String version = GameConstants.VERSION; + + response.send("{\"retcode\":0,\"status\":{\"playerCount\":" + playerCount + ",\"version\":\"" + version + "\"}}"); } } diff --git a/src/main/java/emu/grasscutter/server/http/handlers/LegacyAuthHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/LegacyAuthHandler.java new file mode 100644 index 000000000..943a56d7e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/http/handlers/LegacyAuthHandler.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.http.handlers; + +import emu.grasscutter.server.http.Router; +import express.Express; +import io.javalin.Javalin; + +/** + * Handles the legacy authentication system routes. + */ +public final class LegacyAuthHandler implements Router { + @Override public void applyRoutes(Express express, Javalin handle) { + express.get("/authentication/type", (request, response) -> response.status(500).send("{\"notice\":\"This API is deprecated.\"}")); + express.post("/authentication/login", (request, response) -> response.status(500).send("{\"notice\":\"This API is deprecated.\"}")); + express.post("/authentication/register", (request, response) -> response.status(500).send("{\"notice\":\"This API is deprecated.\"}")); + express.post("/authentication/change_password", (request, response) -> response.status(500).send("{\"notice\":\"This API is deprecated.\"}")); + } +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java index 4f52c0826..08025d365 100644 --- a/src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java +++ b/src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java @@ -1,8 +1,9 @@ package emu.grasscutter.server.http.handlers; -import emu.grasscutter.server.dispatch.ClientLogHandler; import emu.grasscutter.server.http.Router; import express.Express; +import express.http.Request; +import express.http.Response; import io.javalin.Javalin; /** @@ -11,8 +12,13 @@ import io.javalin.Javalin; public final class LogHandler implements Router { @Override public void applyRoutes(Express express, Javalin handle) { // overseauspider.yuanshen.com - express.post("/log", new ClientLogHandler()); + express.post("/log", LogHandler::log); // log-upload-os.mihoyo.com - express.post("/crash/dataUpload", new ClientLogHandler()); + express.post("/crash/dataUpload", LogHandler::log); + } + + private static void log(Request request, Response response) { + // TODO: Figure out how to dump request body and log to file. + response.send("{\"code\":0}"); } } diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchHttpJsonHandler.java b/src/main/java/emu/grasscutter/server/http/objects/HttpJsonResponse.java similarity index 90% rename from src/main/java/emu/grasscutter/server/dispatch/DispatchHttpJsonHandler.java rename to src/main/java/emu/grasscutter/server/http/objects/HttpJsonResponse.java index 8d1164e8d..35ca9b006 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchHttpJsonHandler.java +++ b/src/main/java/emu/grasscutter/server/http/objects/HttpJsonResponse.java @@ -1,4 +1,4 @@ -package emu.grasscutter.server.dispatch; +package emu.grasscutter.server.http.objects; import java.io.IOException; import java.util.Arrays; @@ -13,7 +13,7 @@ import express.http.Response; import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.Configuration.*; -public final class DispatchHttpJsonHandler implements HttpContextHandler { +public final class HttpJsonResponse implements HttpContextHandler { private final String response; private final String[] missingRoutes = { // TODO: When http requests for theses routes are found please remove it from this list and update the route request type in the DispatchServer "/common/hk4e_global/announcement/api/getAlertPic", @@ -28,7 +28,7 @@ public final class DispatchHttpJsonHandler implements HttpContextHandler { "/crash/dataUpload" }; - public DispatchHttpJsonHandler(String response) { + public HttpJsonResponse(String response) { this.response = response; } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java index 9a21e4143..362493755 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java @@ -9,10 +9,12 @@ import emu.grasscutter.net.proto.PlayerLoginRspOuterClass.PlayerLoginRsp; import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass; import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo; import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.http.dispatch.RegionHandler; import emu.grasscutter.utils.FileUtils; import java.io.File; import java.util.Base64; +import java.util.Objects; import static emu.grasscutter.Configuration.*; @@ -55,7 +57,7 @@ public class PacketPlayerLoginRsp extends BasePacket { info = regionCache.getRegionInfo(); } else { - info = Grasscutter.getDispatchServer().getCurrRegion().getRegionInfo(); + info = Objects.requireNonNull(RegionHandler.getCurrentRegion()).getRegionInfo(); } PlayerLoginRsp p = PlayerLoginRsp.newBuilder()