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
This commit is contained in:
Luke H-W 2022-10-07 23:01:08 +10:30 committed by GitHub
parent f6ce7e349d
commit dd6e1bb8a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 242 additions and 244 deletions

View File

@ -84,7 +84,7 @@ public class ConfigContainer {
public String resources = "./resources/"; public String resources = "./resources/";
public String data = "./data/"; public String data = "./data/";
public String packets = "./packets/"; public String packets = "./packets/";
public String scripts = "./resources/Scripts/"; public String scripts = "resources:Scripts/";
public String plugins = "./plugins/"; public String plugins = "./plugins/";
// UNUSED (potentially added later?) // UNUSED (potentially added later?)

View File

@ -1,16 +1,9 @@
package emu.grasscutter.config; package emu.grasscutter.config;
import java.util.Locale; import emu.grasscutter.utils.FileUtils;
import java.util.stream.Stream;
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.Path;
import java.nio.file.Paths; import java.util.Locale;
import static emu.grasscutter.Grasscutter.config; 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 Locale FALLBACK_LANGUAGE = config.language.fallback;
public static final String DOCUMENT_LANGUAGE = config.language.document; public static final String DOCUMENT_LANGUAGE = config.language.document;
private static final String DATA_FOLDER = config.folderStructure.data; 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 PLUGINS_FOLDER = config.folderStructure.plugins;
private static final String SCRIPTS_FOLDER = config.folderStructure.scripts; private static final String SCRIPTS_FOLDER = config.folderStructure.scripts;
private static final String PACKETS_FOLDER = config.folderStructure.packets; 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<Path> 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 Server SERVER = config.server;
public static final Database DATABASE = config.databaseInfo; public static final Database DATABASE = config.databaseInfo;
@ -93,22 +49,27 @@ public final class Configuration extends ConfigContainer {
/* /*
* Utilities * Utilities
*/ */
@Deprecated(forRemoval = true)
public static String DATA() { public static String DATA() {
return DATA_FOLDER; return DATA_FOLDER;
} }
@Deprecated(forRemoval = true)
public static String DATA(String path) { public static String DATA(String path) {
return Path.of(DATA_FOLDER, path).toString(); return Path.of(DATA_FOLDER, path).toString();
} }
@Deprecated(forRemoval = true)
public static Path getResourcePath(String path) { public static Path getResourcePath(String path) {
return RESOURCES_PATH.resolve(path); return FileUtils.getResourcePath(path);
} }
@Deprecated(forRemoval = true)
public static String RESOURCE(String path) { public static String RESOURCE(String path) {
return getResourcePath(path).toString(); return FileUtils.getResourcePath(path).toString();
} }
@Deprecated(forRemoval = true)
public static String PLUGIN() { public static String PLUGIN() {
return PLUGINS_FOLDER; return PLUGINS_FOLDER;
} }
@ -117,10 +78,12 @@ public final class Configuration extends ConfigContainer {
return Path.of(PLUGINS_FOLDER, path).toString(); return Path.of(PLUGINS_FOLDER, path).toString();
} }
@Deprecated(forRemoval = true)
public static String SCRIPT(String path) { public static String SCRIPT(String path) {
return Path.of(SCRIPTS_FOLDER, path).toString(); return Path.of(SCRIPTS_FOLDER, path).toString();
} }
@Deprecated(forRemoval = true)
public static String PACKET(String path) { public static String PACKET(String path) {
return Path.of(PACKETS_FOLDER, path).toString(); return Path.of(PACKETS_FOLDER, path).toString();
} }

View File

@ -5,15 +5,12 @@ import emu.grasscutter.server.http.handlers.GachaHandler;
import emu.grasscutter.tools.Tools; import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.JsonUtils; 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.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -59,15 +56,17 @@ public class DataLoader {
* @throws FileNotFoundException * @throws FileNotFoundException
*/ */
public static InputStream load(String resourcePath, boolean useFallback) 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 // Data is in the resource directory
return new FileInputStream(DATA(resourcePath)); try {
} else { return Files.newInputStream(path);
if (useFallback) { } catch (IOException e) {
return FileUtils.readResourceAsStream("/defaults/data/" + resourcePath); throw new FileNotFoundException(e.getMessage()); // This is evil but so is changing the function signature at this point
} }
} }
return null; return null;
} }
@ -95,11 +94,11 @@ public class DataLoader {
if (filenames == null) { if (filenames == null) {
Grasscutter.getLogger().error("We were unable to locate your default data files."); Grasscutter.getLogger().error("We were unable to locate your default data files.");
} else for (Path file : filenames) { } //else for (Path file : filenames) {
String relativePath = String.valueOf(file).split("defaults[\\\\\\/]data[\\\\\\/]")[1]; // String relativePath = String.valueOf(file).split("defaults[\\\\\\/]data[\\\\\\/]")[1];
checkAndCopyData(relativePath); // checkAndCopyData(relativePath);
} // }
} catch (Exception e) { } catch (Exception e) {
Grasscutter.getLogger().error("An error occurred while trying to check the data folder.", 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) { 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)) { if (!Files.exists(filePath)) {
// Check if file is in subdirectory var root = filePath.getParent();
if (name.contains("/")) { if (root.toFile().mkdirs())
String[] path = name.split("/"); Grasscutter.getLogger().info("Created data folder '" + root + "'");
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);
}
}
}
Grasscutter.getLogger().info("Creating default '" + name + "' data"); Grasscutter.getLogger().info("Creating default '" + name + "' data");
FileUtils.copyResource("/defaults/data/" + name, filePath); FileUtils.copyResource("/defaults/data/" + name, filePath.toString());
} }
} }
private static void generateGachaMappings() { private static void generateGachaMappings() {
if (!Utils.fileExists(GachaHandler.gachaMappings)) { var path = GachaHandler.getGachaMappingsPath();
if (!Files.exists(path)) {
try { try {
Grasscutter.getLogger().info("Creating default '" + GachaHandler.gachaMappings + "' data"); Grasscutter.getLogger().info("Creating default '" + path.toString() + "' data");
Tools.createGachaMapping(GachaHandler.gachaMappings); Tools.createGachaMappings(path);
} catch (Exception exception) { } catch (Exception exception) {
Grasscutter.getLogger().warn("Failed to create gacha mappings. \n" + exception); Grasscutter.getLogger().warn("Failed to create gacha mappings. \n" + exception);
} }

View File

@ -26,8 +26,8 @@ import java.util.*;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Stream; import java.util.stream.Stream;
import static emu.grasscutter.config.Configuration.DATA; import static emu.grasscutter.utils.FileUtils.getDataPath;
import static emu.grasscutter.config.Configuration.getResourcePath; import static emu.grasscutter.utils.FileUtils.getResourcePath;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
public class ResourceLoader { public class ResourceLoader {
@ -174,7 +174,7 @@ public class ResourceLoader {
// Read from cached file if exists // Read from cached file if exists
try { try {
embryoList = JsonUtils.loadToList(DATA("AbilityEmbryos.json"), AbilityEmbryoEntry.class); embryoList = JsonUtils.loadToList(getDataPath("AbilityEmbryos.json"), AbilityEmbryoEntry.class);
} catch (Exception ignored) {} } catch (Exception ignored) {}
if (embryoList == null) { if (embryoList == null) {
@ -318,7 +318,7 @@ public class ResourceLoader {
List<OpenConfigEntry> list = null; List<OpenConfigEntry> list = null;
try { try {
list = JsonUtils.loadToList(DATA("OpenConfig.json"), OpenConfigEntry.class); list = JsonUtils.loadToList(getDataPath("OpenConfig.json"), OpenConfigEntry.class);
} catch (Exception ignored) {} } catch (Exception ignored) {}
if (list == null) { if (list == null) {

View File

@ -33,6 +33,7 @@ import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.game.GameServerTickEvent; import emu.grasscutter.server.game.GameServerTickEvent;
import emu.grasscutter.server.packet.send.PacketDoGachaRsp; import emu.grasscutter.server.packet.send.PacketDoGachaRsp;
import emu.grasscutter.server.packet.send.PacketGachaWishRsp; import emu.grasscutter.server.packet.send.PacketGachaWishRsp;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@ -383,8 +384,7 @@ public class GachaSystem extends BaseGameSystem {
if (this.watchService == null) { if (this.watchService == null) {
try { try {
this.watchService = FileSystems.getDefault().newWatchService(); this.watchService = FileSystems.getDefault().newWatchService();
Path path = new File(DATA()).toPath(); FileUtils.getDataUserPath("").register(watchService, new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_MODIFY}, SensitivityWatchEventModifier.HIGH);
path.register(watchService, new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_MODIFY}, SensitivityWatchEventModifier.HIGH);
} catch (Exception e) { } catch (Exception e) {
Grasscutter.getLogger().error("Unable to load the Gacha Manager Watch Service. If ServerOptions.watchGacha is true it will not auto-reload"); Grasscutter.getLogger().error("Unable to load the Gacha Manager Watch Service. If ServerOptions.watchGacha is true it will not auto-reload");
e.printStackTrace(); e.printStackTrace();

View File

@ -190,13 +190,12 @@ public class GameMainQuest {
} }
public void addRewindPoints() { public void addRewindPoints() {
Bindings bindings = ScriptLoader.getEngine().createBindings(); Bindings bindings = ScriptLoader.getEngine().createBindings();
String script = "Quest/Share/Q" + getParentQuestId() + "ShareConfig.lua";
CompiledScript cs = ScriptLoader.getScriptByPath( CompiledScript cs = ScriptLoader.getScript(script);
SCRIPT("Quest/Share/Q" + getParentQuestId() + "ShareConfig." + ScriptLoader.getScriptType()));
//mainQuest 303 doesn't have a ShareConfig //mainQuest 303 doesn't have a ShareConfig
if (cs == null) { if (cs == null) {
Grasscutter.getLogger().debug("Couldn't find Q" + getParentQuestId() + "ShareConfig." + ScriptLoader.getScriptType()); Grasscutter.getLogger().debug("Couldn't find " + script);
return; return;
} }

View File

@ -3,11 +3,11 @@ package emu.grasscutter.plugin;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.plugin.api.ServerHook; import emu.grasscutter.plugin.api.ServerHook;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.utils.FileUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import static emu.grasscutter.config.Configuration.*;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import java.net.URLClassLoader; import java.net.URLClassLoader;
@ -37,7 +37,7 @@ public abstract class Plugin {
this.identifier = identifier; this.identifier = identifier;
this.classLoader = classLoader; this.classLoader = classLoader;
this.dataFolder = new File(PLUGIN(), identifier.name); this.dataFolder = FileUtils.getPluginPath(identifier.name).toFile();
this.logger = LoggerFactory.getLogger(identifier.name); this.logger = LoggerFactory.getLogger(identifier.name);
if (!this.dataFolder.exists() && !this.dataFolder.mkdirs()) { if (!this.dataFolder.exists() && !this.dataFolder.mkdirs()) {

View File

@ -2,13 +2,12 @@ package emu.grasscutter.plugin;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.server.event.*; import emu.grasscutter.server.event.*;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.JsonUtils; import emu.grasscutter.utils.JsonUtils;
import emu.grasscutter.utils.Utils;
import lombok.*; import lombok.*;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static emu.grasscutter.config.Configuration.PLUGIN;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
import java.io.*; import java.io.*;
@ -43,7 +42,7 @@ public final class PluginManager {
* Loads plugins from the config-specified directory. * Loads plugins from the config-specified directory.
*/ */
private void loadPlugins() { private void loadPlugins() {
File pluginsDir = new File(Utils.toFilePath(PLUGIN())); File pluginsDir = FileUtils.getPluginPath("").toFile();
if (!pluginsDir.exists() && !pluginsDir.mkdirs()) { if (!pluginsDir.exists() && !pluginsDir.mkdirs()) {
Grasscutter.getLogger().error(translate("plugin.directory_failed", pluginsDir.getAbsolutePath())); Grasscutter.getLogger().error(translate("plugin.directory_failed", pluginsDir.getAbsolutePath()));
return; return;

View File

@ -9,6 +9,9 @@ import emu.grasscutter.scripts.constants.ScriptRegionShape;
import emu.grasscutter.scripts.data.SceneMeta; import emu.grasscutter.scripts.data.SceneMeta;
import emu.grasscutter.scripts.serializer.LuaSerializer; import emu.grasscutter.scripts.serializer.LuaSerializer;
import emu.grasscutter.scripts.serializer.Serializer; import emu.grasscutter.scripts.serializer.Serializer;
import emu.grasscutter.utils.FileUtils;
import lombok.Getter;
import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue; import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction; import org.luaj.vm2.lib.OneArgFunction;
@ -19,6 +22,8 @@ import javax.script.*;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.lang.ref.SoftReference; import java.lang.ref.SoftReference;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -26,12 +31,11 @@ import java.util.concurrent.ConcurrentHashMap;
public class ScriptLoader { public class ScriptLoader {
private static ScriptEngineManager sm; private static ScriptEngineManager sm;
private static ScriptEngine engine; @Getter private static ScriptEngine engine;
private static ScriptEngineFactory factory; private static ScriptEngineFactory factory;
private static String fileType; @Getter private static Serializer serializer;
private static Serializer serializer; @Getter private static ScriptLib scriptLib;
private static ScriptLib scriptLib; @Getter private static LuaValue scriptLibLua;
private static LuaValue scriptLibLua;
/** /**
* suggest GC to remove it if the memory is less * suggest GC to remove it if the memory is less
*/ */
@ -52,7 +56,6 @@ public class ScriptLoader {
factory = getEngine().getFactory(); factory = getEngine().getFactory();
// Lua stuff // Lua stuff
fileType = "lua";
serializer = new LuaSerializer(); serializer = new LuaSerializer();
// Set engine to replace require as a temporary fix to missing scripts // Set engine to replace require as a temporary fix to missing scripts
@ -81,26 +84,6 @@ public class ScriptLoader {
ctx.globals.set("ScriptLib", scriptLibLua); 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 <T> Optional<T> tryGet(SoftReference<T> softReference){ public static <T> Optional<T> tryGet(SoftReference<T> softReference){
try{ try{
return Optional.ofNullable(softReference.get()); return Optional.ofNullable(softReference.get());
@ -108,6 +91,8 @@ public class ScriptLoader {
return Optional.empty(); return Optional.empty();
} }
} }
@Deprecated(forRemoval = true)
public static CompiledScript getScriptByPath(String path) { public static CompiledScript getScriptByPath(String path) {
var sc = tryGet(scriptsCache.get(path)); var sc = tryGet(scriptsCache.get(path));
if (sc.isPresent()) { 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) { public static SceneMeta getSceneMeta(int sceneId) {
return tryGet(sceneMetaCache.get(sceneId)).orElseGet(() -> { return tryGet(sceneMetaCache.get(sceneId)).orElseGet(() -> {
var instance = SceneMeta.of(sceneId); var instance = SceneMeta.of(sceneId);

View File

@ -14,8 +14,6 @@ import javax.script.Bindings;
import javax.script.CompiledScript; import javax.script.CompiledScript;
import javax.script.ScriptException; import javax.script.ScriptException;
import static emu.grasscutter.config.Configuration.SCRIPT;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -52,8 +50,7 @@ public class SceneBlock {
this.sceneId = sceneId; this.sceneId = sceneId;
this.setLoaded(true); this.setLoaded(true);
CompiledScript cs = ScriptLoader.getScriptByPath( CompiledScript cs = ScriptLoader.getScript("Scene/" + sceneId + "/scene" + sceneId + "_block" + this.id + ".lua");
SCRIPT("Scene/" + sceneId + "/scene" + sceneId + "_block" + this.id + "." + ScriptLoader.getScriptType()));
if (cs == null) { if (cs == null) {
return null; return null;

View File

@ -11,8 +11,6 @@ import javax.script.Bindings;
import javax.script.CompiledScript; import javax.script.CompiledScript;
import javax.script.ScriptException; import javax.script.ScriptException;
import static emu.grasscutter.config.Configuration.SCRIPT;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -84,8 +82,7 @@ public class SceneGroup {
this.bindings = ScriptLoader.getEngine().createBindings(); this.bindings = ScriptLoader.getEngine().createBindings();
CompiledScript cs = ScriptLoader.getScriptByPath( CompiledScript cs = ScriptLoader.getScript("Scene/" + sceneId + "/scene" + sceneId + "_group" + this.id + ".lua");
SCRIPT("Scene/" + sceneId + "/scene" + sceneId + "_group" + this.id + "." + ScriptLoader.getScriptType()));
if (cs == null) { if (cs == null) {
return this; return this;

View File

@ -12,8 +12,6 @@ import javax.script.Bindings;
import javax.script.CompiledScript; import javax.script.CompiledScript;
import javax.script.ScriptException; import javax.script.ScriptException;
import static emu.grasscutter.config.Configuration.SCRIPT;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -35,8 +33,7 @@ public class SceneMeta {
public SceneMeta load(int sceneId) { public SceneMeta load(int sceneId) {
// Get compiled script if cached // Get compiled script if cached
CompiledScript cs = ScriptLoader.getScriptByPath( CompiledScript cs = ScriptLoader.getScript("Scene/" + sceneId + "/scene" + sceneId + ".lua");
SCRIPT("Scene/" + sceneId + "/scene" + sceneId + "." + ScriptLoader.getScriptType()));
if (cs == null) { if (cs == null) {
Grasscutter.getLogger().warn("No script found for scene " + sceneId); Grasscutter.getLogger().warn("No script found for scene " + sceneId);

View File

@ -2,7 +2,8 @@ package emu.grasscutter.server.game;
import java.io.File; import java.io.File;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Set; import java.nio.file.Path;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.Grasscutter.ServerDebugMode; import emu.grasscutter.Grasscutter.ServerDebugMode;
import emu.grasscutter.game.Account; import emu.grasscutter.game.Account;
@ -16,6 +17,8 @@ import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import lombok.Getter;
import lombok.Setter;
import static emu.grasscutter.config.Configuration.*; import static emu.grasscutter.config.Configuration.*;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
@ -24,14 +27,14 @@ public class GameSession implements GameSessionManager.KcpChannel {
private final GameServer server; private final GameServer server;
private GameSessionManager.KcpTunnel tunnel; private GameSessionManager.KcpTunnel tunnel;
private Account account; @Getter @Setter private Account account;
private Player player; @Getter private Player player;
private boolean useSecretKey; @Setter private boolean useSecretKey;
private SessionState state; @Getter @Setter private SessionState state;
private int clientTime; @Getter private int clientTime;
private long lastPingTime; @Getter private long lastPingTime;
private int lastClientSeq = 10; private int lastClientSeq = 10;
public GameSession(GameServer server) { public GameSession(GameServer server) {
@ -56,52 +59,20 @@ public class GameSession implements GameSessionManager.KcpChannel {
return useSecretKey; return useSecretKey;
} }
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
public String getAccountId() { public String getAccountId() {
return this.getAccount().getId(); return this.getAccount().getId();
} }
public Player getPlayer() {
return player;
}
public synchronized void setPlayer(Player player) { public synchronized void setPlayer(Player player) {
this.player = player; this.player = player;
this.player.setSession(this); this.player.setSession(this);
this.player.setAccount(this.getAccount()); this.player.setAccount(this.getAccount());
} }
public SessionState getState() {
return state;
}
public void setState(SessionState state) {
this.state = state;
}
public boolean isLoggedIn() { public boolean isLoggedIn() {
return this.getPlayer() != null; 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) { public void updateLastPingTime(int clientTime) {
this.clientTime = clientTime; this.clientTime = clientTime;
this.lastPingTime = System.currentTimeMillis(); this.lastPingTime = System.currentTimeMillis();
@ -112,8 +83,8 @@ public class GameSession implements GameSessionManager.KcpChannel {
} }
public void replayPacket(int opcode, String name) { public void replayPacket(int opcode, String name) {
String filePath = PACKET(name); Path filePath = FileUtils.getPluginPath(name);
File p = new File(filePath); File p = filePath.toFile();
if (!p.exists()) return; if (!p.exists()) return;

View File

@ -11,42 +11,38 @@ import emu.grasscutter.data.excels.MonsterData;
import emu.grasscutter.data.excels.SceneData; import emu.grasscutter.data.excels.SceneData;
import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Language; import emu.grasscutter.utils.Language;
import emu.grasscutter.utils.Utils;
import io.javalin.http.ContentType; import io.javalin.http.ContentType;
import io.javalin.http.Context; import io.javalin.http.Context;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import java.io.File; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.file.Files;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
final class HandbookRequestHandler implements DocumentationHandler { final class HandbookRequestHandler implements DocumentationHandler {
private List<String> handbookHtmls; private List<String> handbookHtmls;
private final String template;
public HandbookRequestHandler() { public HandbookRequestHandler() {
final File templateFile = new File(Utils.toFilePath(DATA("documentation/handbook.html"))); var templatePath = FileUtils.getDataPath("documentation/handbook.html");
if (templateFile.exists()) { try {
this.template = new String(FileUtils.read(templateFile), StandardCharsets.UTF_8); this.handbookHtmls = generateHandbookHtmls(Files.readString(templatePath));
this.handbookHtmls = generateHandbookHtmls(); } catch (IOException ignored) {
} else { Grasscutter.getLogger().warn("File does not exist: " + templatePath);
Grasscutter.getLogger().warn("File does not exist: " + templateFile);
this.template = null;
} }
} }
@Override @Override
public void handle(Context ctx) { 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 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); ctx.status(500);
} else { } else {
ctx.contentType(ContentType.TEXT_HTML); ctx.contentType(ContentType.TEXT_HTML);
ctx.result(handbookHtmls.get(langIdx)); ctx.result(this.handbookHtmls.get(langIdx));
} }
} }
private List<String> generateHandbookHtmls() { private List<String> generateHandbookHtmls(String template) {
final int NUM_LANGUAGES = Language.TextStrings.NUM_LANGUAGES; final int NUM_LANGUAGES = Language.TextStrings.NUM_LANGUAGES;
final List<String> output = new ArrayList<>(NUM_LANGUAGES); final List<String> output = new ArrayList<>(NUM_LANGUAGES);
final List<Language> languages = Language.TextStrings.getLanguages(); final List<Language> languages = Language.TextStrings.getLanguages();

View File

@ -1,29 +1,28 @@
package emu.grasscutter.server.http.documentation; package emu.grasscutter.server.http.documentation;
import static emu.grasscutter.config.Configuration.DATA;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils;
import io.javalin.http.ContentType; import io.javalin.http.ContentType;
import io.javalin.http.Context; import io.javalin.http.Context;
import java.io.File; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.file.Files;
final class RootRequestHandler implements DocumentationHandler { final class RootRequestHandler implements DocumentationHandler {
private final String template; private final String template;
public RootRequestHandler() { public RootRequestHandler() {
final File templateFile = new File(Utils.toFilePath(DATA("documentation/index.html"))); var templatePath = FileUtils.getDataPath("documentation/index.html");
if (templateFile.exists()) { String t = null;
template = new String(FileUtils.read(templateFile), StandardCharsets.UTF_8); try {
} else { t = Files.readString(templatePath);
Grasscutter.getLogger().warn("File does not exist: " + templateFile); } catch (IOException ignored) {
template = null; Grasscutter.getLogger().warn("File does not exist: " + templatePath);
} }
this.template = t;
} }
@Override @Override

View File

@ -5,7 +5,6 @@ import emu.grasscutter.data.DataLoader;
import emu.grasscutter.server.http.objects.HttpJsonResponse; import emu.grasscutter.server.http.objects.HttpJsonResponse;
import emu.grasscutter.server.http.Router; import emu.grasscutter.server.http.Router;
import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils;
import io.javalin.Javalin; import io.javalin.Javalin;
import io.javalin.http.ContentType; import io.javalin.http.ContentType;
import io.javalin.http.Context; import io.javalin.http.Context;
@ -74,7 +73,7 @@ public final class AnnouncementsHandler implements Router {
private static void getPageResources(Context ctx) { private static void getPageResources(Context ctx) {
try (InputStream filestream = DataLoader.load(ctx.path())) { 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)); ContentType fromExtension = ContentType.getContentTypeByExtension(possibleFilename.substring(possibleFilename.lastIndexOf(".") + 1));
ctx.contentType(fromExtension != null ? fromExtension : ContentType.APPLICATION_OCTET_STREAM); ctx.contentType(fromExtension != null ? fromExtension : ContentType.APPLICATION_OCTET_STREAM);

View File

@ -13,31 +13,36 @@ import io.javalin.Javalin;
import io.javalin.http.ContentType; import io.javalin.http.ContentType;
import io.javalin.http.Context; import io.javalin.http.Context;
import io.javalin.http.staticfiles.Location; import io.javalin.http.staticfiles.Location;
import lombok.Getter;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
import static emu.grasscutter.config.Configuration.DATA;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
/** /**
* Handles all gacha-related HTTP requests. * Handles all gacha-related HTTP requests.
*/ */
public final class GachaHandler implements Router { 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) { @Override public void applyRoutes(Javalin javalin) {
javalin.get("/gacha", GachaHandler::gachaRecords); javalin.get("/gacha", GachaHandler::gachaRecords);
javalin.get("/gacha/details", GachaHandler::gachaDetails); 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) { 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()) { if (!recordsTemplate.exists()) {
Grasscutter.getLogger().warn("File does not exist: " + recordsTemplate); Grasscutter.getLogger().warn("File does not exist: " + recordsTemplate);
ctx.status(500); ctx.status(500);
@ -77,13 +82,7 @@ public final class GachaHandler implements Router {
} }
private static void gachaDetails(Context ctx) { private static void gachaDetails(Context ctx) {
File detailsTemplate = new File(Utils.toFilePath(DATA("gacha/details.html"))); Path detailsTemplate = FileUtils.getDataPath("gacha/details.html");
if (!detailsTemplate.exists()) {
Grasscutter.getLogger().warn("File does not exist: " + detailsTemplate);
ctx.status(500);
return;
}
String sessionKey = ctx.queryParam("s"); String sessionKey = ctx.queryParam("s");
Account account = DatabaseHelper.getAccountBySessionKey(sessionKey); Account account = DatabaseHelper.getAccountBySessionKey(sessionKey);
if (account == null) { if (account == null) {
@ -96,7 +95,14 @@ public final class GachaHandler implements Router {
return; 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. // Add translated title etc. to the page.
template = template.replace("{{TITLE}}", translate(player, "gacha.details.title")) template = template.replace("{{TITLE}}", translate(player, "gacha.details.title"))

View File

@ -7,6 +7,7 @@ import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
@ -28,6 +29,7 @@ import it.unimi.dsi.fastutil.ints.Int2IntRBTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import static emu.grasscutter.config.Configuration.*; import static emu.grasscutter.config.Configuration.*;
import static emu.grasscutter.utils.FileUtils.getResourcePath;
public final class Tools { public final class Tools {
public static void createGmHandbooks() throws Exception { public static void createGmHandbooks() throws Exception {
@ -109,10 +111,6 @@ public final class Tools {
Grasscutter.getLogger().info("GM Handbooks generated!"); Grasscutter.getLogger().info("GM Handbooks generated!");
} }
public static void createGachaMapping(String location) throws Exception {
createGachaMappings(location);
}
public static List<String> createGachaMappingJsons() { public static List<String> createGachaMappingJsons() {
final int NUM_LANGUAGES = Language.TextStrings.NUM_LANGUAGES; final int NUM_LANGUAGES = Language.TextStrings.NUM_LANGUAGES;
final Language.TextStrings CHARACTER = Language.getTextMapKey(4233146695L); // "Character" in EN 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(); 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(); ResourceLoader.loadResources();
List<String> jsons = createGachaMappingJsons(); List<String> jsons = createGachaMappingJsons();
StringBuilder sb = new StringBuilder("mappings = {\n"); StringBuilder sb = new StringBuilder("mappings = {\n");
@ -207,13 +205,9 @@ public final class Tools {
sb.setLength(sb.length() - 2); // Delete trailing ",\n" sb.setLength(sb.length() - 2); // Delete trailing ",\n"
sb.append("\n}"); sb.append("\n}");
try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(location), StandardCharsets.UTF_8), false)) { Files.createDirectories(location.getParent());
// if the user made choices for language, I assume it's okay to assign his/her selected language to "en-us" Files.writeString(location, sb);
// since it's the fallback language and there will be no difference in the gacha record page. Grasscutter.getLogger().info("Mappings generated to " + location);
// 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 + " !");
}
} }
public static List<String> getAvailableLanguage() { public static List<String> getAvailableLanguage() {

View File

@ -10,11 +10,107 @@ import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.*; import java.nio.file.*;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
public final class FileUtils { 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<Path> 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) { public static void write(String dest, byte[] bytes) {
Path path = Path.of(dest); Path path = Path.of(dest);
@ -79,20 +175,11 @@ public final class FileUtils {
public static List<Path> getPathsFromResource(String folder) throws URISyntaxException { public static List<Path> getPathsFromResource(String folder) throws URISyntaxException {
List<Path> result = null; List<Path> result = null;
// Get pathUri of the current running JAR
URI pathUri = Grasscutter.class.getProtectionDomain()
.getCodeSource()
.getLocation()
.toURI();
try { try {
// file walks JAR // file walks JAR
URI uri = URI.create("jar:file:" + pathUri.getRawPath()); result = Files.walk(JAR_FILE_SYSTEM.getPath(folder))
try (FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap())) { .filter(Files::isRegularFile)
result = Files.walk(fs.getPath(folder)) .collect(Collectors.toList());
.filter(Files::isRegularFile)
.collect(Collectors.toList());
}
} catch (Exception e) { } catch (Exception e) {
// Eclipse puts resources in its bin folder // Eclipse puts resources in its bin folder
File f = new File(System.getProperty("user.dir") + folder); File f = new File(System.getProperty("user.dir") + folder);

View File

@ -17,6 +17,7 @@ import lombok.EqualsAndHashCode;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static emu.grasscutter.config.Configuration.*; import static emu.grasscutter.config.Configuration.*;
import static emu.grasscutter.utils.FileUtils.getResourcePath;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;

View File

@ -22,7 +22,7 @@ import org.slf4j.Logger;
import javax.annotation.Nullable; 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; import static emu.grasscutter.utils.Language.translate;
@SuppressWarnings({"UnusedReturnValue", "BooleanMethodIsAlwaysInverted"}) @SuppressWarnings({"UnusedReturnValue", "BooleanMethodIsAlwaysInverted"})