From dd6e1bb8a32a41f939daa7754ae07ca5297d90e2 Mon Sep 17 00:00:00 2001 From: Luke H-W Date: Fri, 7 Oct 2022 23:01:08 +1030 Subject: [PATCH] Move Data, Plugin, Script, Packet access from Strings to Paths (#1839) * Move Data, Plugin, Script, Packet access from Strings to Paths - No longer dump default Data files to folder on launch - Allow Scripts to be loaded from Resources zip - Lay groundwork for Plugins to be loaded from zip --- .../grasscutter/config/ConfigContainer.java | 2 +- .../emu/grasscutter/config/Configuration.java | 59 ++------- .../java/emu/grasscutter/data/DataLoader.java | 60 ++++------ .../emu/grasscutter/data/ResourceLoader.java | 8 +- .../grasscutter/game/gacha/GachaSystem.java | 4 +- .../grasscutter/game/quest/GameMainQuest.java | 7 +- .../java/emu/grasscutter/plugin/Plugin.java | 6 +- .../emu/grasscutter/plugin/PluginManager.java | 5 +- .../emu/grasscutter/scripts/ScriptLoader.java | 57 +++++---- .../grasscutter/scripts/data/SceneBlock.java | 5 +- .../grasscutter/scripts/data/SceneGroup.java | 5 +- .../grasscutter/scripts/data/SceneMeta.java | 5 +- .../grasscutter/server/game/GameSession.java | 53 ++------ .../documentation/HandbookRequestHandler.java | 24 ++-- .../documentation/RootRequestHandler.java | 19 ++- .../http/handlers/AnnouncementsHandler.java | 3 +- .../server/http/handlers/GachaHandler.java | 30 +++-- .../java/emu/grasscutter/tools/Tools.java | 18 +-- .../java/emu/grasscutter/utils/FileUtils.java | 113 ++++++++++++++++-- .../java/emu/grasscutter/utils/Language.java | 1 + .../java/emu/grasscutter/utils/Utils.java | 2 +- 21 files changed, 242 insertions(+), 244 deletions(-) diff --git a/src/main/java/emu/grasscutter/config/ConfigContainer.java b/src/main/java/emu/grasscutter/config/ConfigContainer.java index f8af1dae8..2e36dbaae 100644 --- a/src/main/java/emu/grasscutter/config/ConfigContainer.java +++ b/src/main/java/emu/grasscutter/config/ConfigContainer.java @@ -84,7 +84,7 @@ public class ConfigContainer { public String resources = "./resources/"; public String data = "./data/"; public String packets = "./packets/"; - public String scripts = "./resources/Scripts/"; + public String scripts = "resources:Scripts/"; public String plugins = "./plugins/"; // UNUSED (potentially added later?) diff --git a/src/main/java/emu/grasscutter/config/Configuration.java b/src/main/java/emu/grasscutter/config/Configuration.java index bf008a41f..98041badc 100644 --- a/src/main/java/emu/grasscutter/config/Configuration.java +++ b/src/main/java/emu/grasscutter/config/Configuration.java @@ -1,16 +1,9 @@ package emu.grasscutter.config; -import java.util.Locale; -import java.util.stream.Stream; +import emu.grasscutter.utils.FileUtils; -import emu.grasscutter.Grasscutter; - -import java.io.IOException; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; +import java.util.Locale; import static emu.grasscutter.Grasscutter.config; @@ -34,46 +27,9 @@ public final class Configuration extends ConfigContainer { public static final Locale FALLBACK_LANGUAGE = config.language.fallback; public static final String DOCUMENT_LANGUAGE = config.language.document; private static final String DATA_FOLDER = config.folderStructure.data; - private static final String RESOURCES_FOLDER = config.folderStructure.resources; private static final String PLUGINS_FOLDER = config.folderStructure.plugins; private static final String SCRIPTS_FOLDER = config.folderStructure.scripts; private static final String PACKETS_FOLDER = config.folderStructure.packets; - private static final FileSystem RESOURCES_FILE_SYSTEM; // Not sure about lifetime rules on this one, might be safe to remove - private static final Path RESOURCES_PATH; - static { - FileSystem fs = null; - Path path = Path.of(RESOURCES_FOLDER); - if (RESOURCES_FOLDER.endsWith(".zip")) { // Would be nice to support .tar.gz too at some point, but it doesn't come for free in Java - try { - fs = FileSystems.newFileSystem(path); - } catch (IOException e) { - Grasscutter.getLogger().error("Failed to load resources zip \"" + RESOURCES_FOLDER + "\""); - } - } - - if (fs != null) { - var root = fs.getPath(""); - try (Stream pathStream = java.nio.file.Files.find(root, 3, (p, a) -> { - var filename = p.getFileName(); - if (filename == null) return false; - return filename.toString().equals("ExcelBinOutput"); - })) { - var excelBinOutput = pathStream.findFirst(); - if (excelBinOutput.isPresent()) { - path = excelBinOutput.get().getParent(); - if (path == null) - path = root; - Grasscutter.getLogger().debug("Resources will be loaded from \"" + RESOURCES_FOLDER + "/" + path.toString() + "\""); - } else { - Grasscutter.getLogger().error("Failed to find ExcelBinOutput in resources zip \"" + RESOURCES_FOLDER + "\""); - } - } catch (IOException e) { - Grasscutter.getLogger().error("Failed to scan resources zip \"" + RESOURCES_FOLDER + "\""); - } - } - RESOURCES_FILE_SYSTEM = fs; - RESOURCES_PATH = path; - }; public static final Server SERVER = config.server; public static final Database DATABASE = config.databaseInfo; @@ -93,22 +49,27 @@ public final class Configuration extends ConfigContainer { /* * Utilities */ + @Deprecated(forRemoval = true) public static String DATA() { return DATA_FOLDER; } + @Deprecated(forRemoval = true) public static String DATA(String path) { return Path.of(DATA_FOLDER, path).toString(); } + @Deprecated(forRemoval = true) public static Path getResourcePath(String path) { - return RESOURCES_PATH.resolve(path); + return FileUtils.getResourcePath(path); } + @Deprecated(forRemoval = true) public static String RESOURCE(String path) { - return getResourcePath(path).toString(); + return FileUtils.getResourcePath(path).toString(); } + @Deprecated(forRemoval = true) public static String PLUGIN() { return PLUGINS_FOLDER; } @@ -117,10 +78,12 @@ public final class Configuration extends ConfigContainer { return Path.of(PLUGINS_FOLDER, path).toString(); } + @Deprecated(forRemoval = true) public static String SCRIPT(String path) { return Path.of(SCRIPTS_FOLDER, path).toString(); } + @Deprecated(forRemoval = true) public static String PACKET(String path) { return Path.of(PACKETS_FOLDER, path).toString(); } diff --git a/src/main/java/emu/grasscutter/data/DataLoader.java b/src/main/java/emu/grasscutter/data/DataLoader.java index 869081651..9b19336bf 100644 --- a/src/main/java/emu/grasscutter/data/DataLoader.java +++ b/src/main/java/emu/grasscutter/data/DataLoader.java @@ -5,15 +5,12 @@ import emu.grasscutter.server.http.handlers.GachaHandler; import emu.grasscutter.tools.Tools; import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.JsonUtils; -import emu.grasscutter.utils.Utils; -import static emu.grasscutter.config.Configuration.DATA; - -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Map; @@ -59,15 +56,17 @@ public class DataLoader { * @throws FileNotFoundException */ public static InputStream load(String resourcePath, boolean useFallback) throws FileNotFoundException { - if (Utils.fileExists(DATA(resourcePath))) { + Path path = useFallback + ? FileUtils.getDataPath(resourcePath) + : FileUtils.getDataUserPath(resourcePath); + if (Files.exists(path)) { // Data is in the resource directory - return new FileInputStream(DATA(resourcePath)); - } else { - if (useFallback) { - return FileUtils.readResourceAsStream("/defaults/data/" + resourcePath); + try { + return Files.newInputStream(path); + } catch (IOException e) { + throw new FileNotFoundException(e.getMessage()); // This is evil but so is changing the function signature at this point } } - return null; } @@ -95,11 +94,11 @@ public class DataLoader { if (filenames == null) { Grasscutter.getLogger().error("We were unable to locate your default data files."); - } else for (Path file : filenames) { - String relativePath = String.valueOf(file).split("defaults[\\\\\\/]data[\\\\\\/]")[1]; + } //else for (Path file : filenames) { + // String relativePath = String.valueOf(file).split("defaults[\\\\\\/]data[\\\\\\/]")[1]; - checkAndCopyData(relativePath); - } + // checkAndCopyData(relativePath); + // } } catch (Exception e) { Grasscutter.getLogger().error("An error occurred while trying to check the data folder.", e); } @@ -108,36 +107,25 @@ public class DataLoader { } private static void checkAndCopyData(String name) { - String filePath = Utils.toFilePath(DATA(name)); + // TODO: Revisit this if default dumping is ever reintroduced + Path filePath = FileUtils.getDataPath(name); - if (!Utils.fileExists(filePath)) { - // Check if file is in subdirectory - if (name.contains("/")) { - String[] path = name.split("/"); - - String folder = ""; - for (int i = 0; i < (path.length - 1); i++) { - folder += path[i] + "/"; - - // Make sure the current folder exists - String folderToCreate = Utils.toFilePath(DATA(folder)); - if (!Utils.fileExists(folderToCreate)) { - Grasscutter.getLogger().info("Creating data folder '" + folder + "'"); - Utils.createFolder(folderToCreate); - } - } - } + if (!Files.exists(filePath)) { + var root = filePath.getParent(); + if (root.toFile().mkdirs()) + Grasscutter.getLogger().info("Created data folder '" + root + "'"); Grasscutter.getLogger().info("Creating default '" + name + "' data"); - FileUtils.copyResource("/defaults/data/" + name, filePath); + FileUtils.copyResource("/defaults/data/" + name, filePath.toString()); } } private static void generateGachaMappings() { - if (!Utils.fileExists(GachaHandler.gachaMappings)) { + var path = GachaHandler.getGachaMappingsPath(); + if (!Files.exists(path)) { try { - Grasscutter.getLogger().info("Creating default '" + GachaHandler.gachaMappings + "' data"); - Tools.createGachaMapping(GachaHandler.gachaMappings); + Grasscutter.getLogger().info("Creating default '" + path.toString() + "' data"); + Tools.createGachaMappings(path); } catch (Exception exception) { Grasscutter.getLogger().warn("Failed to create gacha mappings. \n" + exception); } diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index d71558160..788e01f8e 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -26,8 +26,8 @@ import java.util.*; import java.util.regex.Pattern; import java.util.stream.Stream; -import static emu.grasscutter.config.Configuration.DATA; -import static emu.grasscutter.config.Configuration.getResourcePath; +import static emu.grasscutter.utils.FileUtils.getDataPath; +import static emu.grasscutter.utils.FileUtils.getResourcePath; import static emu.grasscutter.utils.Language.translate; public class ResourceLoader { @@ -174,7 +174,7 @@ public class ResourceLoader { // Read from cached file if exists try { - embryoList = JsonUtils.loadToList(DATA("AbilityEmbryos.json"), AbilityEmbryoEntry.class); + embryoList = JsonUtils.loadToList(getDataPath("AbilityEmbryos.json"), AbilityEmbryoEntry.class); } catch (Exception ignored) {} if (embryoList == null) { @@ -318,7 +318,7 @@ public class ResourceLoader { List list = null; try { - list = JsonUtils.loadToList(DATA("OpenConfig.json"), OpenConfigEntry.class); + list = JsonUtils.loadToList(getDataPath("OpenConfig.json"), OpenConfigEntry.class); } catch (Exception ignored) {} if (list == null) { diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaSystem.java b/src/main/java/emu/grasscutter/game/gacha/GachaSystem.java index afe1bb21e..6ffd6a9dc 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaSystem.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaSystem.java @@ -33,6 +33,7 @@ import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServerTickEvent; import emu.grasscutter.server.packet.send.PacketDoGachaRsp; import emu.grasscutter.server.packet.send.PacketGachaWishRsp; +import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.Utils; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -383,8 +384,7 @@ public class GachaSystem extends BaseGameSystem { if (this.watchService == null) { try { this.watchService = FileSystems.getDefault().newWatchService(); - Path path = new File(DATA()).toPath(); - path.register(watchService, new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_MODIFY}, SensitivityWatchEventModifier.HIGH); + FileUtils.getDataUserPath("").register(watchService, new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_MODIFY}, SensitivityWatchEventModifier.HIGH); } catch (Exception e) { Grasscutter.getLogger().error("Unable to load the Gacha Manager Watch Service. If ServerOptions.watchGacha is true it will not auto-reload"); e.printStackTrace(); diff --git a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java index 00be8df18..f40ff03c0 100644 --- a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java +++ b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java @@ -190,13 +190,12 @@ public class GameMainQuest { } public void addRewindPoints() { Bindings bindings = ScriptLoader.getEngine().createBindings(); - - CompiledScript cs = ScriptLoader.getScriptByPath( - SCRIPT("Quest/Share/Q" + getParentQuestId() + "ShareConfig." + ScriptLoader.getScriptType())); + String script = "Quest/Share/Q" + getParentQuestId() + "ShareConfig.lua"; + CompiledScript cs = ScriptLoader.getScript(script); //mainQuest 303 doesn't have a ShareConfig if (cs == null) { - Grasscutter.getLogger().debug("Couldn't find Q" + getParentQuestId() + "ShareConfig." + ScriptLoader.getScriptType()); + Grasscutter.getLogger().debug("Couldn't find " + script); return; } diff --git a/src/main/java/emu/grasscutter/plugin/Plugin.java b/src/main/java/emu/grasscutter/plugin/Plugin.java index fdc27514a..eff60130f 100644 --- a/src/main/java/emu/grasscutter/plugin/Plugin.java +++ b/src/main/java/emu/grasscutter/plugin/Plugin.java @@ -3,11 +3,11 @@ package emu.grasscutter.plugin; import emu.grasscutter.Grasscutter; import emu.grasscutter.plugin.api.ServerHook; import emu.grasscutter.server.game.GameServer; +import emu.grasscutter.utils.FileUtils; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static emu.grasscutter.config.Configuration.*; - import java.io.File; import java.io.InputStream; import java.net.URLClassLoader; @@ -37,7 +37,7 @@ public abstract class Plugin { this.identifier = identifier; this.classLoader = classLoader; - this.dataFolder = new File(PLUGIN(), identifier.name); + this.dataFolder = FileUtils.getPluginPath(identifier.name).toFile(); this.logger = LoggerFactory.getLogger(identifier.name); if (!this.dataFolder.exists() && !this.dataFolder.mkdirs()) { diff --git a/src/main/java/emu/grasscutter/plugin/PluginManager.java b/src/main/java/emu/grasscutter/plugin/PluginManager.java index 5d1b5f396..478f8dc3f 100644 --- a/src/main/java/emu/grasscutter/plugin/PluginManager.java +++ b/src/main/java/emu/grasscutter/plugin/PluginManager.java @@ -2,13 +2,12 @@ package emu.grasscutter.plugin; import emu.grasscutter.Grasscutter; import emu.grasscutter.server.event.*; +import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.JsonUtils; -import emu.grasscutter.utils.Utils; import lombok.*; import javax.annotation.Nullable; -import static emu.grasscutter.config.Configuration.PLUGIN; import static emu.grasscutter.utils.Language.translate; import java.io.*; @@ -43,7 +42,7 @@ public final class PluginManager { * Loads plugins from the config-specified directory. */ private void loadPlugins() { - File pluginsDir = new File(Utils.toFilePath(PLUGIN())); + File pluginsDir = FileUtils.getPluginPath("").toFile(); if (!pluginsDir.exists() && !pluginsDir.mkdirs()) { Grasscutter.getLogger().error(translate("plugin.directory_failed", pluginsDir.getAbsolutePath())); return; diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLoader.java b/src/main/java/emu/grasscutter/scripts/ScriptLoader.java index 3cb0031cf..16353e707 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLoader.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLoader.java @@ -9,6 +9,9 @@ import emu.grasscutter.scripts.constants.ScriptRegionShape; import emu.grasscutter.scripts.data.SceneMeta; import emu.grasscutter.scripts.serializer.LuaSerializer; import emu.grasscutter.scripts.serializer.Serializer; +import emu.grasscutter.utils.FileUtils; +import lombok.Getter; + import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaValue; import org.luaj.vm2.lib.OneArgFunction; @@ -19,6 +22,8 @@ import javax.script.*; import java.io.File; import java.io.FileReader; import java.lang.ref.SoftReference; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.Map; import java.util.Optional; @@ -26,12 +31,11 @@ import java.util.concurrent.ConcurrentHashMap; public class ScriptLoader { private static ScriptEngineManager sm; - private static ScriptEngine engine; + @Getter private static ScriptEngine engine; private static ScriptEngineFactory factory; - private static String fileType; - private static Serializer serializer; - private static ScriptLib scriptLib; - private static LuaValue scriptLibLua; + @Getter private static Serializer serializer; + @Getter private static ScriptLib scriptLib; + @Getter private static LuaValue scriptLibLua; /** * suggest GC to remove it if the memory is less */ @@ -52,7 +56,6 @@ public class ScriptLoader { factory = getEngine().getFactory(); // Lua stuff - fileType = "lua"; serializer = new LuaSerializer(); // Set engine to replace require as a temporary fix to missing scripts @@ -81,26 +84,6 @@ public class ScriptLoader { ctx.globals.set("ScriptLib", scriptLibLua); } - public static ScriptEngine getEngine() { - return engine; - } - - public static String getScriptType() { - return fileType; - } - - public static Serializer getSerializer() { - return serializer; - } - - public static ScriptLib getScriptLib() { - return scriptLib; - } - - public static LuaValue getScriptLibLua() { - return scriptLibLua; - } - public static Optional tryGet(SoftReference softReference){ try{ return Optional.ofNullable(softReference.get()); @@ -108,6 +91,8 @@ public class ScriptLoader { return Optional.empty(); } } + + @Deprecated(forRemoval = true) public static CompiledScript getScriptByPath(String path) { var sc = tryGet(scriptsCache.get(path)); if (sc.isPresent()) { @@ -131,6 +116,26 @@ public class ScriptLoader { } + public static CompiledScript getScript(String path) { + var sc = tryGet(scriptsCache.get(path)); + if (sc.isPresent()) { + return sc.get(); + } + + Grasscutter.getLogger().debug("Loading script " + path); + final Path scriptPath = FileUtils.getScriptPath(path); + if (!Files.exists(scriptPath)) return null; + + try { + var script = ((Compilable) getEngine()).compile(Files.newBufferedReader(scriptPath)); + scriptsCache.put(path, new SoftReference<>(script)); + return script; + } catch (Exception e) { + Grasscutter.getLogger().error("Loading script {} failed!", path, e); + return null; + } + } + public static SceneMeta getSceneMeta(int sceneId) { return tryGet(sceneMetaCache.get(sceneId)).orElseGet(() -> { var instance = SceneMeta.of(sceneId); diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java index d8f969e04..0f88c01c7 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneBlock.java @@ -14,8 +14,6 @@ import javax.script.Bindings; import javax.script.CompiledScript; import javax.script.ScriptException; -import static emu.grasscutter.config.Configuration.SCRIPT; - import java.util.Map; import java.util.stream.Collectors; @@ -52,8 +50,7 @@ public class SceneBlock { this.sceneId = sceneId; this.setLoaded(true); - CompiledScript cs = ScriptLoader.getScriptByPath( - SCRIPT("Scene/" + sceneId + "/scene" + sceneId + "_block" + this.id + "." + ScriptLoader.getScriptType())); + CompiledScript cs = ScriptLoader.getScript("Scene/" + sceneId + "/scene" + sceneId + "_block" + this.id + ".lua"); if (cs == null) { return null; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java index 163ce94c8..635e3698a 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -11,8 +11,6 @@ import javax.script.Bindings; import javax.script.CompiledScript; import javax.script.ScriptException; -import static emu.grasscutter.config.Configuration.SCRIPT; - import java.util.List; import java.util.Map; import java.util.Optional; @@ -84,8 +82,7 @@ public class SceneGroup { this.bindings = ScriptLoader.getEngine().createBindings(); - CompiledScript cs = ScriptLoader.getScriptByPath( - SCRIPT("Scene/" + sceneId + "/scene" + sceneId + "_group" + this.id + "." + ScriptLoader.getScriptType())); + CompiledScript cs = ScriptLoader.getScript("Scene/" + sceneId + "/scene" + sceneId + "_group" + this.id + ".lua"); if (cs == null) { return this; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java b/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java index 5d8a14dfd..885519325 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneMeta.java @@ -12,8 +12,6 @@ import javax.script.Bindings; import javax.script.CompiledScript; import javax.script.ScriptException; -import static emu.grasscutter.config.Configuration.SCRIPT; - import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -35,8 +33,7 @@ public class SceneMeta { public SceneMeta load(int sceneId) { // Get compiled script if cached - CompiledScript cs = ScriptLoader.getScriptByPath( - SCRIPT("Scene/" + sceneId + "/scene" + sceneId + "." + ScriptLoader.getScriptType())); + CompiledScript cs = ScriptLoader.getScript("Scene/" + sceneId + "/scene" + sceneId + ".lua"); if (cs == null) { Grasscutter.getLogger().warn("No script found for scene " + sceneId); diff --git a/src/main/java/emu/grasscutter/server/game/GameSession.java b/src/main/java/emu/grasscutter/server/game/GameSession.java index 9d43b0324..bbb9c625b 100644 --- a/src/main/java/emu/grasscutter/server/game/GameSession.java +++ b/src/main/java/emu/grasscutter/server/game/GameSession.java @@ -2,7 +2,8 @@ package emu.grasscutter.server.game; import java.io.File; import java.net.InetSocketAddress; -import java.util.Set; +import java.nio.file.Path; + import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter.ServerDebugMode; import emu.grasscutter.game.Account; @@ -16,6 +17,8 @@ import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.Utils; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import lombok.Getter; +import lombok.Setter; import static emu.grasscutter.config.Configuration.*; import static emu.grasscutter.utils.Language.translate; @@ -24,14 +27,14 @@ public class GameSession implements GameSessionManager.KcpChannel { private final GameServer server; private GameSessionManager.KcpTunnel tunnel; - private Account account; - private Player player; + @Getter @Setter private Account account; + @Getter private Player player; - private boolean useSecretKey; - private SessionState state; + @Setter private boolean useSecretKey; + @Getter @Setter private SessionState state; - private int clientTime; - private long lastPingTime; + @Getter private int clientTime; + @Getter private long lastPingTime; private int lastClientSeq = 10; public GameSession(GameServer server) { @@ -56,52 +59,20 @@ public class GameSession implements GameSessionManager.KcpChannel { return useSecretKey; } - public Account getAccount() { - return account; - } - - public void setAccount(Account account) { - this.account = account; - } - public String getAccountId() { return this.getAccount().getId(); } - public Player getPlayer() { - return player; - } - public synchronized void setPlayer(Player player) { this.player = player; this.player.setSession(this); this.player.setAccount(this.getAccount()); } - public SessionState getState() { - return state; - } - - public void setState(SessionState state) { - this.state = state; - } - public boolean isLoggedIn() { return this.getPlayer() != null; } - public void setUseSecretKey(boolean useSecretKey) { - this.useSecretKey = useSecretKey; - } - - public int getClientTime() { - return this.clientTime; - } - - public long getLastPingTime() { - return lastPingTime; - } - public void updateLastPingTime(int clientTime) { this.clientTime = clientTime; this.lastPingTime = System.currentTimeMillis(); @@ -112,8 +83,8 @@ public class GameSession implements GameSessionManager.KcpChannel { } public void replayPacket(int opcode, String name) { - String filePath = PACKET(name); - File p = new File(filePath); + Path filePath = FileUtils.getPluginPath(name); + File p = filePath.toFile(); if (!p.exists()) return; diff --git a/src/main/java/emu/grasscutter/server/http/documentation/HandbookRequestHandler.java b/src/main/java/emu/grasscutter/server/http/documentation/HandbookRequestHandler.java index 97acc4f12..04fa1b929 100644 --- a/src/main/java/emu/grasscutter/server/http/documentation/HandbookRequestHandler.java +++ b/src/main/java/emu/grasscutter/server/http/documentation/HandbookRequestHandler.java @@ -11,42 +11,38 @@ import emu.grasscutter.data.excels.MonsterData; import emu.grasscutter.data.excels.SceneData; import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.Language; -import emu.grasscutter.utils.Utils; import io.javalin.http.ContentType; import io.javalin.http.Context; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import java.io.File; -import java.nio.charset.StandardCharsets; +import java.io.IOException; +import java.nio.file.Files; import java.util.ArrayList; import java.util.List; final class HandbookRequestHandler implements DocumentationHandler { private List handbookHtmls; - private final String template; public HandbookRequestHandler() { - final File templateFile = new File(Utils.toFilePath(DATA("documentation/handbook.html"))); - if (templateFile.exists()) { - this.template = new String(FileUtils.read(templateFile), StandardCharsets.UTF_8); - this.handbookHtmls = generateHandbookHtmls(); - } else { - Grasscutter.getLogger().warn("File does not exist: " + templateFile); - this.template = null; + var templatePath = FileUtils.getDataPath("documentation/handbook.html"); + try { + this.handbookHtmls = generateHandbookHtmls(Files.readString(templatePath)); + } catch (IOException ignored) { + Grasscutter.getLogger().warn("File does not exist: " + templatePath); } } @Override public void handle(Context ctx) { final int langIdx = Language.TextStrings.MAP_LANGUAGES.getOrDefault(DOCUMENT_LANGUAGE, 0); // TODO: This should really be based off the client language somehow - if (template == null) { + if (this.handbookHtmls == null) { ctx.status(500); } else { ctx.contentType(ContentType.TEXT_HTML); - ctx.result(handbookHtmls.get(langIdx)); + ctx.result(this.handbookHtmls.get(langIdx)); } } - private List generateHandbookHtmls() { + private List generateHandbookHtmls(String template) { final int NUM_LANGUAGES = Language.TextStrings.NUM_LANGUAGES; final List output = new ArrayList<>(NUM_LANGUAGES); final List languages = Language.TextStrings.getLanguages(); diff --git a/src/main/java/emu/grasscutter/server/http/documentation/RootRequestHandler.java b/src/main/java/emu/grasscutter/server/http/documentation/RootRequestHandler.java index 5df75287c..f184ac952 100644 --- a/src/main/java/emu/grasscutter/server/http/documentation/RootRequestHandler.java +++ b/src/main/java/emu/grasscutter/server/http/documentation/RootRequestHandler.java @@ -1,29 +1,28 @@ package emu.grasscutter.server.http.documentation; -import static emu.grasscutter.config.Configuration.DATA; import static emu.grasscutter.utils.Language.translate; import emu.grasscutter.Grasscutter; import emu.grasscutter.utils.FileUtils; -import emu.grasscutter.utils.Utils; import io.javalin.http.ContentType; import io.javalin.http.Context; -import java.io.File; -import java.nio.charset.StandardCharsets; +import java.io.IOException; +import java.nio.file.Files; final class RootRequestHandler implements DocumentationHandler { private final String template; public RootRequestHandler() { - final File templateFile = new File(Utils.toFilePath(DATA("documentation/index.html"))); - if (templateFile.exists()) { - template = new String(FileUtils.read(templateFile), StandardCharsets.UTF_8); - } else { - Grasscutter.getLogger().warn("File does not exist: " + templateFile); - template = null; + var templatePath = FileUtils.getDataPath("documentation/index.html"); + String t = null; + try { + t = Files.readString(templatePath); + } catch (IOException ignored) { + Grasscutter.getLogger().warn("File does not exist: " + templatePath); } + this.template = t; } @Override 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 f49f4b513..2c24b7407 100644 --- a/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java +++ b/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java @@ -5,7 +5,6 @@ import emu.grasscutter.data.DataLoader; import emu.grasscutter.server.http.objects.HttpJsonResponse; import emu.grasscutter.server.http.Router; import emu.grasscutter.utils.FileUtils; -import emu.grasscutter.utils.Utils; import io.javalin.Javalin; import io.javalin.http.ContentType; import io.javalin.http.Context; @@ -74,7 +73,7 @@ public final class AnnouncementsHandler implements Router { private static void getPageResources(Context ctx) { try (InputStream filestream = DataLoader.load(ctx.path())) { - String possibleFilename = Utils.toFilePath(DATA(ctx.path())); + String possibleFilename = ctx.path(); ContentType fromExtension = ContentType.getContentTypeByExtension(possibleFilename.substring(possibleFilename.lastIndexOf(".") + 1)); ctx.contentType(fromExtension != null ? fromExtension : ContentType.APPLICATION_OCTET_STREAM); diff --git a/src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java index 59afe02c3..aa23f0138 100644 --- a/src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java +++ b/src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java @@ -13,31 +13,36 @@ import io.javalin.Javalin; import io.javalin.http.ContentType; import io.javalin.http.Context; import io.javalin.http.staticfiles.Location; +import lombok.Getter; import java.io.File; +import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.Set; -import static emu.grasscutter.config.Configuration.DATA; import static emu.grasscutter.utils.Language.translate; /** * Handles all gacha-related HTTP requests. */ public final class GachaHandler implements Router { - public static final String gachaMappings = DATA(Utils.toFilePath("gacha/mappings.js")); + @Getter private static final Path gachaMappingsPath = FileUtils.getDataUserPath("gacha/mappings.js"); + @Deprecated(forRemoval = true) + public static final String gachaMappings = gachaMappingsPath.toString(); @Override public void applyRoutes(Javalin javalin) { javalin.get("/gacha", GachaHandler::gachaRecords); javalin.get("/gacha/details", GachaHandler::gachaDetails); - javalin._conf.addSinglePageRoot("/gacha/mappings", gachaMappings, Location.EXTERNAL); + javalin._conf.addSinglePageRoot("/gacha/mappings", gachaMappingsPath.toString(), Location.EXTERNAL); // TODO: This ***must*** be changed to take the Path not a String. This might involve upgrading Javalin. } private static void gachaRecords(Context ctx) { - File recordsTemplate = new File(Utils.toFilePath(DATA("gacha/records.html"))); + File recordsTemplate = FileUtils.getDataPath("gacha/records.html").toFile(); if (!recordsTemplate.exists()) { Grasscutter.getLogger().warn("File does not exist: " + recordsTemplate); ctx.status(500); @@ -77,13 +82,7 @@ public final class GachaHandler implements Router { } private static void gachaDetails(Context ctx) { - File detailsTemplate = new File(Utils.toFilePath(DATA("gacha/details.html"))); - if (!detailsTemplate.exists()) { - Grasscutter.getLogger().warn("File does not exist: " + detailsTemplate); - ctx.status(500); - return; - } - + Path detailsTemplate = FileUtils.getDataPath("gacha/details.html"); String sessionKey = ctx.queryParam("s"); Account account = DatabaseHelper.getAccountBySessionKey(sessionKey); if (account == null) { @@ -96,7 +95,14 @@ public final class GachaHandler implements Router { return; } - String template = new String(FileUtils.read(detailsTemplate), StandardCharsets.UTF_8); + String template; + try { + template = Files.readString(detailsTemplate); + } catch (IOException e) { + Grasscutter.getLogger().warn("Failed to read data/gacha/details.html"); + ctx.status(500); + return; + } // Add translated title etc. to the page. template = template.replace("{{TITLE}}", translate(player, "gacha.details.title")) diff --git a/src/main/java/emu/grasscutter/tools/Tools.java b/src/main/java/emu/grasscutter/tools/Tools.java index 0823afaf9..546d36d7f 100644 --- a/src/main/java/emu/grasscutter/tools/Tools.java +++ b/src/main/java/emu/grasscutter/tools/Tools.java @@ -7,6 +7,7 @@ import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; @@ -28,6 +29,7 @@ import it.unimi.dsi.fastutil.ints.Int2IntRBTreeMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import static emu.grasscutter.config.Configuration.*; +import static emu.grasscutter.utils.FileUtils.getResourcePath; public final class Tools { public static void createGmHandbooks() throws Exception { @@ -109,10 +111,6 @@ public final class Tools { Grasscutter.getLogger().info("GM Handbooks generated!"); } - public static void createGachaMapping(String location) throws Exception { - createGachaMappings(location); - } - public static List createGachaMappingJsons() { final int NUM_LANGUAGES = Language.TextStrings.NUM_LANGUAGES; final Language.TextStrings CHARACTER = Language.getTextMapKey(4233146695L); // "Character" in EN @@ -196,7 +194,7 @@ public final class Tools { return sbs.stream().map(StringBuilder::toString).toList(); } - public static void createGachaMappings(String location) throws Exception { + public static void createGachaMappings(Path location) throws IOException { ResourceLoader.loadResources(); List jsons = createGachaMappingJsons(); StringBuilder sb = new StringBuilder("mappings = {\n"); @@ -207,13 +205,9 @@ public final class Tools { sb.setLength(sb.length() - 2); // Delete trailing ",\n" sb.append("\n}"); - try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(location), StandardCharsets.UTF_8), false)) { - // if the user made choices for language, I assume it's okay to assign his/her selected language to "en-us" - // since it's the fallback language and there will be no difference in the gacha record page. - // The end-user can still modify the `gacha/mappings.js` directly to enable multilingual for the gacha record system. - writer.println(sb); - Grasscutter.getLogger().info("Mappings generated to " + location + " !"); - } + Files.createDirectories(location.getParent()); + Files.writeString(location, sb); + Grasscutter.getLogger().info("Mappings generated to " + location); } public static List getAvailableLanguage() { diff --git a/src/main/java/emu/grasscutter/utils/FileUtils.java b/src/main/java/emu/grasscutter/utils/FileUtils.java index 6ef4f7763..1f4848241 100644 --- a/src/main/java/emu/grasscutter/utils/FileUtils.java +++ b/src/main/java/emu/grasscutter/utils/FileUtils.java @@ -10,11 +10,107 @@ import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; public final class FileUtils { + private static final FileSystem JAR_FILE_SYSTEM; + private static final Path DATA_DEFAULT_PATH; + private static final Path DATA_USER_PATH = Path.of(Grasscutter.config.folderStructure.data); + private static final Path PACKETS_PATH = Path.of(Grasscutter.config.folderStructure.packets); + private static final Path PLUGINS_PATH = Path.of(Grasscutter.config.folderStructure.plugins); + private static final Path RESOURCES_PATH; + private static final Path SCRIPTS_PATH; + static { + FileSystem fs = null; + Path path = DATA_USER_PATH; + // Setup Data paths + // Get pathUri of the current running JAR + try { + URI jarUri = Grasscutter.class.getProtectionDomain() + .getCodeSource() + .getLocation() + .toURI(); + fs = FileSystems.newFileSystem(Path.of(jarUri)); + path = fs.getPath("/defaults/data"); + } catch (URISyntaxException | IOException e) { + // Failed to load this jar. How? + System.err.println("Failed to load jar?????????????????????"); + } finally { + JAR_FILE_SYSTEM = fs; + DATA_DEFAULT_PATH = path; + } + + // Setup Resources path + final String resources = Grasscutter.config.folderStructure.resources; + fs = null; + path = Path.of(resources); + if (resources.endsWith(".zip")) { // Would be nice to support .tar.gz too at some point, but it doesn't come for free in Java + try { + fs = FileSystems.newFileSystem(path); + } catch (IOException e) { + Grasscutter.getLogger().error("Failed to load resources zip \"" + resources + "\""); + } + } + + if (fs != null) { + var root = fs.getPath(""); + try (Stream pathStream = Files.find(root, 3, (p, a) -> { + var filename = p.getFileName(); + if (filename == null) return false; + return filename.toString().equals("ExcelBinOutput"); + })) { + var excelBinOutput = pathStream.findFirst(); + if (excelBinOutput.isPresent()) { + path = excelBinOutput.get().getParent(); + if (path == null) + path = root; + Grasscutter.getLogger().debug("Resources will be loaded from \"" + resources + "/" + path.toString() + "\""); + } else { + Grasscutter.getLogger().error("Failed to find ExcelBinOutput in resources zip \"" + resources + "\""); + } + } catch (IOException e) { + Grasscutter.getLogger().error("Failed to scan resources zip \"" + resources + "\""); + } + } + RESOURCES_PATH = path; + + // Setup Scripts path + final String scripts = Grasscutter.config.folderStructure.scripts; + SCRIPTS_PATH = (scripts.startsWith("resources:")) + ? RESOURCES_PATH.resolve(scripts.substring("resources:".length())) + : Path.of(scripts); + }; + + public static Path getDataPath(String path) { + Path userPath = DATA_USER_PATH.resolve(path); + if (Files.exists(userPath)) return userPath; + Path defaultPath = DATA_DEFAULT_PATH.resolve(path); + if (Files.exists(defaultPath)) return defaultPath; + return userPath; // Maybe they want to write to a new file + } + + public static Path getDataUserPath(String path) { + return DATA_USER_PATH.resolve(path); + } + + public static Path getPacketPath(String path) { + return PACKETS_PATH.resolve(path); + } + + public static Path getPluginPath(String path) { + return PLUGINS_PATH.resolve(path); + } + + public static Path getResourcePath(String path) { + return RESOURCES_PATH.resolve(path); + } + + public static Path getScriptPath(String path) { + return SCRIPTS_PATH.resolve(path); + } + public static void write(String dest, byte[] bytes) { Path path = Path.of(dest); @@ -79,20 +175,11 @@ public final class FileUtils { public static List getPathsFromResource(String folder) throws URISyntaxException { List result = null; - // Get pathUri of the current running JAR - URI pathUri = Grasscutter.class.getProtectionDomain() - .getCodeSource() - .getLocation() - .toURI(); - try { // file walks JAR - URI uri = URI.create("jar:file:" + pathUri.getRawPath()); - try (FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap())) { - result = Files.walk(fs.getPath(folder)) - .filter(Files::isRegularFile) - .collect(Collectors.toList()); - } + result = Files.walk(JAR_FILE_SYSTEM.getPath(folder)) + .filter(Files::isRegularFile) + .collect(Collectors.toList()); } catch (Exception e) { // Eclipse puts resources in its bin folder File f = new File(System.getProperty("user.dir") + folder); diff --git a/src/main/java/emu/grasscutter/utils/Language.java b/src/main/java/emu/grasscutter/utils/Language.java index f82fdb166..93c06ead2 100644 --- a/src/main/java/emu/grasscutter/utils/Language.java +++ b/src/main/java/emu/grasscutter/utils/Language.java @@ -17,6 +17,7 @@ import lombok.EqualsAndHashCode; import javax.annotation.Nullable; import static emu.grasscutter.config.Configuration.*; +import static emu.grasscutter.utils.FileUtils.getResourcePath; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index 9ae7f95bf..8800b0197 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -22,7 +22,7 @@ import org.slf4j.Logger; import javax.annotation.Nullable; -import static emu.grasscutter.config.Configuration.getResourcePath; +import static emu.grasscutter.utils.FileUtils.getResourcePath; import static emu.grasscutter.utils.Language.translate; @SuppressWarnings({"UnusedReturnValue", "BooleanMethodIsAlwaysInverted"})