diff --git a/data/documentation/handbook.html b/data/documentation/handbook.html
new file mode 100644
index 000000000..6a77d3806
--- /dev/null
+++ b/data/documentation/handbook.html
@@ -0,0 +1,162 @@
+
+
+
+
+
+
+
+
+ GM Handbook
+
+
+
+
+
{{TITLE}}
+
+
{{TITLE_COMMANDS}}
+
+
+
+
+ {{HEADER_COMMAND}} |
+ {{HEADER_DESCRIPTION}} |
+
+
+ {{COMMANDS_TABLE}}
+
+
+
{{TITLE_AVATARS}}
+
+
+
+
+ {{HEADER_ID}} |
+ {{HEADER_AVATAR}} |
+
+
+ {{AVATARS_TABLE}}
+
+
+
{{TITLE_ITEMS}}
+
+
+
+
+ {{HEADER_ID}} |
+ {{HEADER_ITEM}} |
+
+
+ {{ITEMS_TABLE}}
+
+
+
+
{{TITLE_SCENES}}
+
+
+
+
+ {{HEADER_ID}} |
+ {{HEADER_SCENE}} |
+
+
+ {{SCENES_TABLE}}
+
+
+
{{TITLE_MONSTERS}}
+
+
+
+
+ {{HEADER_ID}} |
+ {{HEADER_MONSTER}} |
+
+
+ {{MONSTERS_TABLE}}
+
+
+
+
+
+
diff --git a/data/documentation/index.html b/data/documentation/index.html
new file mode 100644
index 000000000..f89dbe258
--- /dev/null
+++ b/data/documentation/index.html
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+ Documentation
+
+
+
+
+
+
diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java
index 316c2030e..3cf8363e7 100644
--- a/src/main/java/emu/grasscutter/Grasscutter.java
+++ b/src/main/java/emu/grasscutter/Grasscutter.java
@@ -14,6 +14,7 @@ import emu.grasscutter.server.http.HttpServer;
import emu.grasscutter.server.http.dispatch.DispatchHandler;
import emu.grasscutter.server.http.handlers.*;
import emu.grasscutter.server.http.dispatch.RegionHandler;
+import emu.grasscutter.server.http.documentation.DocumentationServerHandler;
import emu.grasscutter.utils.ConfigContainer;
import emu.grasscutter.utils.Utils;
import org.jline.reader.EndOfFileException;
@@ -129,6 +130,7 @@ public final class Grasscutter {
httpServer.addRouter(AnnouncementsHandler.class);
httpServer.addRouter(DispatchHandler.class);
httpServer.addRouter(GachaHandler.class);
+ httpServer.addRouter(DocumentationServerHandler.class);
// TODO: find a better place?
StaminaManager.initialize();
diff --git a/src/main/java/emu/grasscutter/server/http/documentation/DocumentationHandler.java b/src/main/java/emu/grasscutter/server/http/documentation/DocumentationHandler.java
new file mode 100644
index 000000000..de7f543a3
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/http/documentation/DocumentationHandler.java
@@ -0,0 +1,9 @@
+package emu.grasscutter.server.http.documentation;
+
+import express.http.Request;
+import express.http.Response;
+
+interface DocumentationHandler {
+
+ void handle(Request request, Response response);
+}
diff --git a/src/main/java/emu/grasscutter/server/http/documentation/DocumentationServerHandler.java b/src/main/java/emu/grasscutter/server/http/documentation/DocumentationServerHandler.java
new file mode 100644
index 000000000..24c8236de
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/http/documentation/DocumentationServerHandler.java
@@ -0,0 +1,19 @@
+package emu.grasscutter.server.http.documentation;
+
+import emu.grasscutter.server.http.Router;
+import express.Express;
+import io.javalin.Javalin;
+
+public final class DocumentationServerHandler implements Router {
+
+ @Override
+ public void applyRoutes(Express express, Javalin handle) {
+ final RootRequestHandler root = new RootRequestHandler();
+ final HandbookRequestHandler handbook = new HandbookRequestHandler();
+ final GachaMappingRequestHandler gachaMapping = new GachaMappingRequestHandler();
+
+ express.get("/documentation/handbook", handbook::handle);
+ express.get("/documentation/gachamapping", gachaMapping::handle);
+ express.get("/documentation", root::handle);
+ }
+}
diff --git a/src/main/java/emu/grasscutter/server/http/documentation/GachaMappingRequestHandler.java b/src/main/java/emu/grasscutter/server/http/documentation/GachaMappingRequestHandler.java
new file mode 100644
index 000000000..e405d9b54
--- /dev/null
+++ b/src/main/java/emu/grasscutter/server/http/documentation/GachaMappingRequestHandler.java
@@ -0,0 +1,155 @@
+package emu.grasscutter.server.http.documentation;
+
+import static emu.grasscutter.Configuration.RESOURCE;
+
+import com.google.gson.reflect.TypeToken;
+import emu.grasscutter.Grasscutter;
+import emu.grasscutter.data.GameData;
+import emu.grasscutter.data.ResourceLoader;
+import emu.grasscutter.data.def.AvatarData;
+import emu.grasscutter.data.def.ItemData;
+import emu.grasscutter.tools.Tools;
+import emu.grasscutter.utils.Utils;
+import express.http.Request;
+import express.http.Response;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+final class GachaMappingRequestHandler implements DocumentationHandler {
+
+ private Map map;
+
+ GachaMappingRequestHandler() {
+ ResourceLoader.loadResources();
+ final String textMapFile = "TextMap/TextMap" + Tools.getLanguageOption() + ".json";
+ try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(
+ Utils.toFilePath(RESOURCE(textMapFile))), StandardCharsets.UTF_8)) {
+ map = Grasscutter.getGsonFactory().fromJson(fileReader,
+ new TypeToken