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 data = "./data/";
public String packets = "./packets/";
public String scripts = "./resources/Scripts/";
public String scripts = "resources:Scripts/";
public String plugins = "./plugins/";
// UNUSED (potentially added later?)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 <T> Optional<T> tryGet(SoftReference<T> 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);

View File

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

View File

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

View File

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

View File

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

View File

@ -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<String> 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<String> generateHandbookHtmls() {
private List<String> generateHandbookHtmls(String template) {
final int NUM_LANGUAGES = Language.TextStrings.NUM_LANGUAGES;
final List<String> output = new ArrayList<>(NUM_LANGUAGES);
final List<Language> languages = Language.TextStrings.getLanguages();

View File

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

View File

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

View File

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

View File

@ -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<String> 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<String> 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<String> getAvailableLanguage() {

View File

@ -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<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) {
Path path = Path.of(dest);
@ -79,20 +175,11 @@ public final class FileUtils {
public static List<Path> getPathsFromResource(String folder) throws URISyntaxException {
List<Path> 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))
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);

View File

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

View File

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