diff --git a/src/main/java/emu/grasscutter/tools/Dumpers.java b/src/main/java/emu/grasscutter/tools/Dumpers.java index e1d0b7089..268a6ec33 100644 --- a/src/main/java/emu/grasscutter/tools/Dumpers.java +++ b/src/main/java/emu/grasscutter/tools/Dumpers.java @@ -1,16 +1,215 @@ -package emu.grasscutter.tools; - -import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp; -import emu.grasscutter.net.proto.GetShopRspOuterClass.GetShopRsp; - -public final class Dumpers { - public static void extractBanner(byte[] data) throws Exception { - GetGachaInfoRsp proto = GetGachaInfoRsp.parseFrom(data); - System.out.println(proto); - } - - public static void extractShop(byte[] data) throws Exception { - GetShopRsp proto = GetShopRsp.parseFrom(data); - System.out.println(proto); - } -} +package emu.grasscutter.tools; + +import emu.grasscutter.command.Command; +import emu.grasscutter.command.Command.TargetRequirement; +import emu.grasscutter.command.CommandMap; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.ResourceLoader; +import emu.grasscutter.game.inventory.ItemType; +import emu.grasscutter.utils.JsonUtils; +import emu.grasscutter.utils.Language; +import lombok.AllArgsConstructor; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public interface Dumpers { + /** + * Fetches the description of a command. + * + * @param locale The locale to use. + * @param command The command to get the description of. + * @return The description of the command. + */ + private static String commandDescription(String locale, Command command) { + try { + // Get the language by the locale. + var language = Language.getLanguage(locale); + if (language == null) throw new IllegalArgumentException("Invalid language."); + + return language.get("commands." + command.label() + ".description"); + } catch (IllegalArgumentException ignored) { + return command.label(); + } + } + + /** + * Encodes the dump into comma separated values. + * + * @param dump The dump to encode. + * @return The encoded dump. + */ + private static String miniEncode(Map dump) { + return dump.entrySet().stream() + .map(entry -> entry.getKey() + "," + entry.getValue().toString()) + .collect(Collectors.joining("\n")); + } + + /** + * Dumps all commands to a JSON file. + * + * @param locale The language to dump the commands in. + */ + static void dumpCommands(String locale) { + // Check that commands are registered. + var commandMap = CommandMap.getInstance(); + if (commandMap == null) commandMap = new CommandMap(true); + + // Convert all registered commands to an info map. + var dump = new HashMap(); + commandMap.getAnnotationsAsList().forEach(command -> { + var description = Dumpers.commandDescription(locale, command); + var labels = new ArrayList(){{ + this.add(command.label()); + this.addAll(List.of(command.aliases())); + }}; + + // Add the command info to the list. + dump.put(command.label(), new CommandInfo( + labels, description, List.of(command.usage()), List.of( + command.permission(), command.permissionTargeted()), + command.targetRequirement())); + }); + + try { + // Create a file for the dump. + var file = new File("commands.json"); + if (file.exists() && !file.delete()) + throw new RuntimeException("Failed to delete file."); + if (!file.exists() && !file.createNewFile()) + throw new RuntimeException("Failed to create file."); + + // Write the dump to the file. + Files.writeString(file.toPath(), JsonUtils.encode(dump)); + } catch (IOException ignored) { + throw new RuntimeException("Failed to write to file."); + } + } + + /** + * Dumps all avatars to a JSON file. + * + * @param locale The language to dump the avatars in. + */ + static void dumpAvatars(String locale) { + // Reload resources. + ResourceLoader.loadAll(); + Language.loadTextMaps(); + + // Convert all known avatars to an avatar map. + var dump = new HashMap(); + GameData.getAvatarDataMap().forEach((id, avatar) -> { + var langHash = avatar.getNameTextMapHash(); + dump.put(id, new AvatarInfo( + langHash == 0 ? avatar.getName() : Language.getTextMapKey(langHash).get(locale), + avatar.getQualityType().equals("QUALITY_PURPLE") ? Quality.EPIC : Quality.LEGENDARY, + avatar.getId() + )); + }); + + try { + // Create a file for the dump. + var file = new File("avatars.json"); + if (file.exists() && !file.delete()) + throw new RuntimeException("Failed to delete file."); + if (!file.exists() && !file.createNewFile()) + throw new RuntimeException("Failed to create file."); + + // Write the dump to the file. + Files.writeString(file.toPath(), JsonUtils.encode(dump)); + } catch (IOException ignored) { + throw new RuntimeException("Failed to write to file."); + } + } + + /** + * Dumps all items to a JSON file. + * + * @param locale The language to dump the items in. + */ + static void dumpItems(String locale) { + // Reload resources. + ResourceLoader.loadAll(); + Language.loadTextMaps(); + + // Convert all known items to an item map. + var dump = new HashMap(); + GameData.getItemDataMap().forEach((id, item) -> dump.put(id, new ItemData( + item.getId(), Language.getTextMapKey(item.getNameTextMapHash()).get(locale), + Quality.from(item.getRankLevel()), item.getItemType() + ))); + + try { + // Create a file for the dump. + var file = new File("items.csv"); + if (file.exists() && !file.delete()) + throw new RuntimeException("Failed to delete file."); + if (!file.exists() && !file.createNewFile()) + throw new RuntimeException("Failed to create file."); + + // Write the dump to the file. + Files.writeString(file.toPath(), Dumpers.miniEncode(dump)); + } catch (IOException ignored) { + throw new RuntimeException("Failed to write to file."); + } + } + + @AllArgsConstructor + class CommandInfo { + public List name; + public String description; + public List usage; + public List permission; + public TargetRequirement target; + } + + @AllArgsConstructor + class AvatarInfo { + public String name; + public Quality quality; + public int id; + } + + @AllArgsConstructor + class ItemData { + public int id; + public String name; + public Quality quality; + public ItemType type; + + @Override + public String toString() { + return this.id + "," + + this.name + "," + + this.quality + "," + + this.type; + } + } + + enum Quality { + LEGENDARY, EPIC, RARE, UNCOMMON, COMMON, UNKNOWN; + + /** + * Convert a rank level to a quality. + * + * @param rankLevel The rank level to convert. + * @return The quality. + */ + static Quality from(int rankLevel) { + return switch (rankLevel) { + case 0 -> UNKNOWN; + case 1 -> COMMON; + case 2 -> UNCOMMON; + case 3 -> RARE; + case 4 -> EPIC; + default -> LEGENDARY; + }; + } + } +} diff --git a/src/main/java/emu/grasscutter/utils/StartupArguments.java b/src/main/java/emu/grasscutter/utils/StartupArguments.java index 0adfee7bd..ab9534554 100644 --- a/src/main/java/emu/grasscutter/utils/StartupArguments.java +++ b/src/main/java/emu/grasscutter/utils/StartupArguments.java @@ -1,129 +1,164 @@ -package emu.grasscutter.utils; - -import static emu.grasscutter.config.Configuration.*; - -import ch.qos.logback.classic.Level; -import ch.qos.logback.classic.Logger; -import emu.grasscutter.BuildConfig; -import emu.grasscutter.Grasscutter; -import emu.grasscutter.Grasscutter.ServerRunMode; -import emu.grasscutter.net.packet.PacketOpcodesUtils; -import java.util.Map; -import java.util.function.Function; -import org.slf4j.LoggerFactory; - -/** A parser for start-up arguments. */ -public final class StartupArguments { - /* A map of parameter -> argument handler. */ - private static final Map> argumentHandlers = - Map.of( - "-dumppacketids", - parameter -> { - PacketOpcodesUtils.dumpPacketIds(); - return true; - }, - "-version", StartupArguments::printVersion, - "-debug", StartupArguments::enableDebug, - "-lang", - parameter -> { - Grasscutter.setPreferredLanguage(parameter); - return false; - }, - "-game", - parameter -> { - Grasscutter.setRunModeOverride(ServerRunMode.GAME_ONLY); - return false; - }, - "-dispatch", - parameter -> { - Grasscutter.setRunModeOverride(ServerRunMode.DISPATCH_ONLY); - return false; - }, - "-test", - parameter -> { - // Disable the console. - SERVER.game.enableConsole = false; - // Disable HTTP encryption. - SERVER.http.encryption.useEncryption = false; - return false; - }, - - // Aliases. - "-v", StartupArguments::printVersion, - "-debugall", - parameter -> { - StartupArguments.enableDebug("all"); - return false; - }); - - private StartupArguments() { - // This class is not meant to be instantiated. - } - - /** - * Parses the provided start-up arguments. - * - * @param args The application start-up arguments. - * @return If the application should exit. - */ - public static boolean parse(String[] args) { - boolean exitEarly = false; - - // Parse the arguments. - for (var input : args) { - var containsParameter = input.contains("="); - - var argument = containsParameter ? input.split("=")[0] : input; - var handler = argumentHandlers.get(argument.toLowerCase()); - - if (handler != null) { - exitEarly |= handler.apply(containsParameter ? input.split("=")[1] : null); - } - } - - return exitEarly; - } - - /** - * Prints the server version. - * - * @param parameter Additional parameters. - * @return True to exit early. - */ - private static boolean printVersion(String parameter) { - System.out.println("Grasscutter version: " + BuildConfig.VERSION + "-" + BuildConfig.GIT_HASH); - return true; - } - - /** - * Enables debug logging. - * - * @param parameter Additional parameters. - * @return False to continue execution. - */ - private static boolean enableDebug(String parameter) { - if (parameter != null && parameter.equals("all")) { - // Override default debug configs - GAME_INFO.isShowLoopPackets = DEBUG_MODE_INFO.isShowLoopPackets; - GAME_INFO.isShowPacketPayload = DEBUG_MODE_INFO.isShowPacketPayload; - GAME_INFO.logPackets = DEBUG_MODE_INFO.logPackets; - DISPATCH_INFO.logRequests = DEBUG_MODE_INFO.logRequests; - } - - // Set the main logger to debug. - Grasscutter.getLogger().setLevel(DEBUG_MODE_INFO.serverLoggerLevel); - Grasscutter.getLogger().debug("The logger is now running in debug mode."); - - // Log level to other third-party services - Level loggerLevel = DEBUG_MODE_INFO.servicesLoggersLevel; - - // Change loggers to debug. - ((Logger) LoggerFactory.getLogger("io.javalin")).setLevel(loggerLevel); - ((Logger) LoggerFactory.getLogger("org.quartz")).setLevel(loggerLevel); - ((Logger) LoggerFactory.getLogger("org.reflections")).setLevel(loggerLevel); - ((Logger) LoggerFactory.getLogger("org.eclipse.jetty")).setLevel(loggerLevel); - ((Logger) LoggerFactory.getLogger("org.mongodb.driver")).setLevel(loggerLevel); - - return false; - } -} +package emu.grasscutter.utils; + +import static emu.grasscutter.config.Configuration.*; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import emu.grasscutter.BuildConfig; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.Grasscutter.ServerRunMode; +import emu.grasscutter.net.packet.PacketOpcodesUtils; +import java.util.Map; +import java.util.function.Function; + +import emu.grasscutter.tools.Dumpers; +import org.slf4j.LoggerFactory; + +/** A parser for start-up arguments. */ +public final class StartupArguments { + /* A map of parameter -> argument handler. */ + private static final Map> argumentHandlers = + Map.of( + "-dumppacketids", + parameter -> { + PacketOpcodesUtils.dumpPacketIds(); + return true; + }, + "-version", StartupArguments::printVersion, + "-debug", StartupArguments::enableDebug, + "-lang", + parameter -> { + Grasscutter.setPreferredLanguage(parameter); + return false; + }, + "-game", + parameter -> { + Grasscutter.setRunModeOverride(ServerRunMode.GAME_ONLY); + return false; + }, + "-dispatch", + parameter -> { + Grasscutter.setRunModeOverride(ServerRunMode.DISPATCH_ONLY); + return false; + }, + "-test", + parameter -> { + // Disable the console. + SERVER.game.enableConsole = false; + // Disable HTTP encryption. + SERVER.http.encryption.useEncryption = false; + return false; + }, + "-dump", StartupArguments::dump, + + // Aliases. + "-v", StartupArguments::printVersion, + "-debugall", + parameter -> { + StartupArguments.enableDebug("all"); + return false; + }); + + private StartupArguments() { + // This class is not meant to be instantiated. + } + + /** + * Parses the provided start-up arguments. + * + * @param args The application start-up arguments. + * @return If the application should exit. + */ + public static boolean parse(String[] args) { + boolean exitEarly = false; + + // Parse the arguments. + for (var input : args) { + var containsParameter = input.contains("="); + + var argument = containsParameter ? input.split("=")[0] : input; + var handler = argumentHandlers.get(argument.toLowerCase()); + + if (handler != null) { + exitEarly |= handler.apply(containsParameter ? input.split("=")[1] : null); + } + } + + return exitEarly; + } + + /** + * Prints the server version. + * + * @param parameter Additional parameters. + * @return True to exit early. + */ + private static boolean printVersion(String parameter) { + System.out.println("Grasscutter version: " + BuildConfig.VERSION + "-" + BuildConfig.GIT_HASH); + return true; + } + + /** + * Enables debug logging. + * + * @param parameter Additional parameters. + * @return False to continue execution. + */ + private static boolean enableDebug(String parameter) { + if (parameter != null && parameter.equals("all")) { + // Override default debug configs + GAME_INFO.isShowLoopPackets = DEBUG_MODE_INFO.isShowLoopPackets; + GAME_INFO.isShowPacketPayload = DEBUG_MODE_INFO.isShowPacketPayload; + GAME_INFO.logPackets = DEBUG_MODE_INFO.logPackets; + DISPATCH_INFO.logRequests = DEBUG_MODE_INFO.logRequests; + } + + // Set the main logger to debug. + Grasscutter.getLogger().setLevel(DEBUG_MODE_INFO.serverLoggerLevel); + Grasscutter.getLogger().debug("The logger is now running in debug mode."); + + // Log level to other third-party services + Level loggerLevel = DEBUG_MODE_INFO.servicesLoggersLevel; + + // Change loggers to debug. + ((Logger) LoggerFactory.getLogger("io.javalin")).setLevel(loggerLevel); + ((Logger) LoggerFactory.getLogger("org.quartz")).setLevel(loggerLevel); + ((Logger) LoggerFactory.getLogger("org.reflections")).setLevel(loggerLevel); + ((Logger) LoggerFactory.getLogger("org.eclipse.jetty")).setLevel(loggerLevel); + ((Logger) LoggerFactory.getLogger("org.mongodb.driver")).setLevel(loggerLevel); + + return false; + } + + /** + * Dumps the specified information. + * + * @param parameter The parameter to dump. + * @return True to exit early. + */ + private static boolean dump(String parameter) { + // Parse the parameter. + if (!parameter.contains(",")) { + Grasscutter.getLogger().error("Dumper usage: -dump=,"); + return true; + } + + var split = parameter.split(","); + var content = split[0]; + var language = split[1]; + + try { + switch (content.toLowerCase()) { + case "commands" -> Dumpers.dumpCommands(language); + case "avatars" -> Dumpers.dumpAvatars(language); + case "items" -> Dumpers.dumpItems(language); + } + + Grasscutter.getLogger().info("Finished dumping."); + } catch (Exception exception) { + Grasscutter.getLogger().error("Unable to complete dump.", exception); + } + + return true; + } +}