├── .gitignore ├── LICENSE.md ├── README.md ├── pom.xml └── src └── main ├── java └── com │ └── jeff_media │ └── resourcepackmerger │ ├── PrettyObjectMapper.java │ ├── ResourcePackFileFilter.java │ ├── ResourcePackMerger.java │ ├── Test.java │ ├── Utils.java │ ├── data │ ├── Config.java │ ├── Replacer.java │ ├── ResourcePackVersion.java │ └── ZipCompression.java │ ├── gui │ ├── AddFileAction.java │ ├── ChooseOutputFileAction.java │ ├── CompressionLevelListRenderer.java │ ├── GUI.java │ ├── ResourcePackVersionListRenderer.java │ ├── StartButtonAction.java │ └── WindowListener.java │ ├── logging │ ├── ConsoleLogger.java │ ├── GuiLogger.java │ └── Logger.java │ └── mergers │ ├── DirectoryMerger.java │ ├── IconOverride.java │ ├── JsonMerger.java │ └── McMetaMerger.java └── resources ├── 128.png ├── 16.png ├── 192.png ├── 24.png ├── 256.png ├── 32.png ├── 364.png ├── 48.png ├── 64.png ├── 96.png ├── logback.xml ├── logo_32_32.png ├── logo_64_64.png ├── project.properties └── version.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Project exclude paths 2 | /target/ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Use this howevever you want, but do not redistribute or sell it or do similar things that would make people think "its yours". Happy now? 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Allows you to merge two or more Minecraft resource packs into one. 2 | 3 | It doesn't only overwrite, but merge conflicting files. 4 | 5 | ## Screenshots 6 | ![Screenshot](https://static.jeff-media.com/img/resource-pack-merger/screenshot1.png) -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.jeff_media 8 | ResourcePackMerger 9 | 1.4.0 10 | 11 | 12 | 8 13 | 8 14 | 15 | 16 | 17 | 18 | 19 | src/main/resources 20 | true 21 | 22 | 23 | 24 | 25 | org.apache.maven.plugins 26 | maven-jar-plugin 27 | 3.2.2 28 | 29 | 30 | 31 | com.jeff_media.resourcepackmerger.ResourcePackMerger 32 | 33 | 34 | 35 | 36 | 37 | org.apache.maven.plugins 38 | maven-shade-plugin 39 | 3.3.0 40 | 41 | false 42 | 43 | 44 | 45 | package 46 | 47 | shade 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | commons-io 58 | commons-io 59 | 2.11.0 60 | compile 61 | 62 | 63 | net.lingala.zip4j 64 | zip4j 65 | 2.11.5 66 | compile 67 | 68 | 69 | ch.qos.logback 70 | logback-classic 71 | 1.2.11 72 | compile 73 | 74 | 75 | com.fasterxml.jackson.core 76 | jackson-core 77 | 2.13.3 78 | compile 79 | 80 | 81 | com.fasterxml.jackson.core 82 | jackson-databind 83 | 2.13.4.2 84 | compile 85 | 86 | 87 | com.miglayout 88 | miglayout-swing 89 | 5.2 90 | compile 91 | 92 | 93 | com.formdev 94 | flatlaf 95 | 3.0 96 | compile 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/PrettyObjectMapper.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.databind.ObjectWriter; 5 | 6 | public class PrettyObjectMapper { 7 | 8 | public static final ObjectWriter get() { 9 | return new ObjectMapper().writerWithDefaultPrettyPrinter(); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/ResourcePackFileFilter.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger; 2 | 3 | import javax.swing.filechooser.FileFilter; 4 | import java.io.File; 5 | import java.util.Locale; 6 | 7 | public class ResourcePackFileFilter extends FileFilter { 8 | @Override 9 | public boolean accept(File f) { 10 | return f.isDirectory() || (f.isFile() && f.getAbsolutePath().toLowerCase(Locale.ROOT).endsWith(".zip")); 11 | } 12 | 13 | @Override 14 | public String getDescription() { 15 | return ".zip files and directories"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/ResourcePackMerger.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger; 2 | 3 | import com.formdev.flatlaf.FlatDarculaLaf; 4 | import com.formdev.flatlaf.themes.FlatMacDarkLaf; 5 | import com.jeff_media.resourcepackmerger.gui.GUI; 6 | import com.jeff_media.resourcepackmerger.logging.ConsoleLogger; 7 | import com.jeff_media.resourcepackmerger.logging.Logger; 8 | import com.jeff_media.resourcepackmerger.mergers.DirectoryMerger; 9 | import com.jeff_media.resourcepackmerger.mergers.McMetaMerger; 10 | 11 | import javax.swing.*; 12 | import java.awt.*; 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.nio.file.Paths; 16 | import java.util.*; 17 | 18 | public class ResourcePackMerger { 19 | 20 | private static final Properties PROPERTIES = new Properties(); 21 | public static final java.util.List TEMP_WORK_DIRECTORIES = new ArrayList<>(); 22 | 23 | public static Logger getLogger() { 24 | return logger; 25 | } 26 | private static Logger logger = new ConsoleLogger(); 27 | 28 | public static void setLogger(Logger logger) { 29 | ResourcePackMerger.logger = logger; 30 | } 31 | 32 | public static void main(String[] args) { 33 | 34 | try { 35 | UIManager.setLookAndFeel(new FlatDarculaLaf()); 36 | } catch (Exception e) { 37 | throw new RuntimeException(e); 38 | } 39 | 40 | 41 | try { 42 | loadProperties(); 43 | } catch (IOException e) { 44 | e.printStackTrace(); 45 | return; 46 | } 47 | 48 | 49 | if (args.length == 0) { 50 | GUI gui = new GUI(); 51 | gui.setTitle("Resource Pack Merger"); 52 | gui.setIconImages(Utils.getIcons()); 53 | // gui.setMinimumSize(new Dimension(1800,900)); 54 | gui.setVisible(true); 55 | gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 56 | return; 57 | } 58 | if (args.length < 2) { 59 | throw new IllegalArgumentException("Usage: ... "); 60 | } 61 | 62 | String outputFileName = args[args.length - 1]; 63 | String[] inputFileNames = Utils.removeLastElement(args); 64 | 65 | File outputFile = Paths.get(outputFileName).toFile(); 66 | File[] inputFiles = Arrays.stream(inputFileNames).map(s -> { 67 | File file = new File(s); 68 | Utils.throwIfNotAFile(file); 69 | return new File(s); 70 | }).toArray(File[]::new); 71 | for (int i = 0; i < inputFiles.length; i++) { 72 | File file = inputFiles[i]; 73 | if (file.getName().toLowerCase(Locale.ROOT).endsWith(".zip")) { 74 | inputFiles[i] = Utils.extractZip(file); 75 | } 76 | } 77 | 78 | DirectoryMerger merger = new DirectoryMerger(outputFile, inputFiles); 79 | merger.checkPrerequisites(); 80 | if (merger.merge()) { 81 | logger.info("Merging complete! Result: " + outputFile.getAbsolutePath()); 82 | } else { 83 | logger.error("Could not merge resource packs!"); 84 | } 85 | } 86 | 87 | private static void loadProperties() throws IOException { 88 | PROPERTIES.load(ResourcePackMerger.class.getClassLoader().getResourceAsStream("project.properties")); 89 | } 90 | 91 | public static String getVersion() { 92 | return PROPERTIES.getProperty("version",""); 93 | } 94 | 95 | public static void deleteWorkDirectories() { 96 | ListIterator it = TEMP_WORK_DIRECTORIES.listIterator(); 97 | while(it.hasNext()) { 98 | File file = it.next(); 99 | try { 100 | Utils.delete(file); 101 | } catch (IOException e) { 102 | e.printStackTrace(); 103 | } 104 | if(file.exists()) { 105 | getLogger().warn("Could not delete work directory " + file.getAbsolutePath()); 106 | } else { 107 | it.remove(); 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/Test.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.jeff_media.resourcepackmerger.data.Config; 6 | import com.jeff_media.resourcepackmerger.data.ResourcePackVersion; 7 | 8 | import java.util.Arrays; 9 | 10 | public class Test { 11 | 12 | public static void main(String[] args) { 13 | 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/Utils.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import net.lingala.zip4j.ZipFile; 5 | import net.lingala.zip4j.exception.ZipException; 6 | import net.lingala.zip4j.model.ZipParameters; 7 | import net.lingala.zip4j.model.enums.CompressionLevel; 8 | 9 | import javax.imageio.ImageIO; 10 | import java.awt.*; 11 | import java.awt.image.BufferedImage; 12 | import java.io.*; 13 | import java.lang.reflect.Array; 14 | import java.nio.file.Files; 15 | import java.util.ArrayList; 16 | import java.util.Locale; 17 | import java.util.Map; 18 | 19 | public class Utils { 20 | 21 | public static T[] removeLastElement(T[] array) { 22 | T[] shortArray = (T[]) Array.newInstance(array.getClass().getComponentType(), array.length - 1); 23 | System.arraycopy(array, 0, shortArray, 0, array.length - 1); 24 | return shortArray; 25 | } 26 | 27 | public static void throwIfNotAFile(File file) { 28 | if (!file.exists()) throw new RuntimeException(new FileNotFoundException(file.getAbsolutePath())); 29 | } 30 | 31 | public static File extractZip(File file) { 32 | try { 33 | File temp = getTempFolder(); 34 | temp.mkdirs(); 35 | if (!temp.exists()) { 36 | throw new RuntimeException("[Error 1] Could not create directory: " + temp.getAbsolutePath()); 37 | } 38 | ResourcePackMerger.getLogger().info("Extracting ZIP File " + file.getAbsolutePath() + " to " + temp.getAbsolutePath()); 39 | ZipFile zipFile = new ZipFile(file); 40 | zipFile.extractAll(temp.getAbsolutePath()); 41 | temp.deleteOnExit(); 42 | //new File(temp, "pack.mcmeta").delete(); 43 | return temp; 44 | } catch (IOException e) { 45 | throw new RuntimeException(e); 46 | } 47 | } 48 | 49 | public static File getTempFolder() { 50 | try { 51 | File temp = Files.createTempDirectory("resource-pack-merger-").toFile(); 52 | ResourcePackMerger.TEMP_WORK_DIRECTORIES.add(temp); 53 | temp.deleteOnExit(); 54 | return temp; 55 | } catch (IOException e) { 56 | throw new RuntimeException(e); 57 | } 58 | } 59 | 60 | public static void print(Map map) { 61 | map.forEach((key, value) -> ResourcePackMerger.getLogger().debug(key + " -> " + value)); 62 | } 63 | 64 | private static final String[] ENDINGS = {".json",".mcmeta"}; 65 | public static boolean isJsonFile(File file) { 66 | //if(file.getName().equals("pack.mcmeta")) return false; 67 | try { 68 | new ObjectMapper().readValue(file, Map.class); 69 | return true; 70 | } catch (Throwable t) { 71 | return false; 72 | } 73 | } 74 | 75 | public static ArrayList getIcons() { 76 | ArrayList list = new ArrayList<>(); 77 | for (String filename : new String[]{"364","256","192","128","96","64","48","32","24","16"}) { 78 | try { 79 | Image image = ImageIO.read(ResourcePackMerger.class.getResourceAsStream("/" + filename + ".png")); 80 | list.add(image); 81 | } catch (IOException ignored) { 82 | 83 | } 84 | } 85 | return list; 86 | } 87 | 88 | public static BufferedImage getLogo() { 89 | try { 90 | BufferedImage picture = ImageIO.read(ResourcePackMerger.class.getResourceAsStream("/64.png")); 91 | return picture; 92 | } catch (IOException ignored) { 93 | throw new RuntimeException(ignored); 94 | } 95 | } 96 | 97 | public static void delete(File file) throws IOException { 98 | if (!file.exists()) return; 99 | if (!file.isDirectory()) { 100 | if (!file.delete()) { 101 | Files.delete(file.toPath()); 102 | } 103 | } else { 104 | for (File child : file.listFiles()) { 105 | delete(child); 106 | } 107 | if (!file.delete()) { 108 | Files.delete(file.toPath()); 109 | } 110 | } 111 | } 112 | 113 | public static File zipDirectory(File inputFolder, File zipFile, CompressionLevel level) { 114 | ZipParameters params = new ZipParameters(); 115 | params.setIncludeRootFolder(false); 116 | params.setCompressionLevel(level); 117 | 118 | try(ZipFile zip = new ZipFile(zipFile)) { 119 | zip.addFolder(inputFolder, params); 120 | } catch (ZipException e) { 121 | e.printStackTrace(); 122 | return null; 123 | } catch (IOException e) { 124 | throw new RuntimeException(e); 125 | } 126 | return zipFile; 127 | } 128 | 129 | public static String asZipFileName(String text) { 130 | if(text.toLowerCase(Locale.ROOT).endsWith(".zip")) return text; 131 | return text + ".zip"; 132 | } 133 | 134 | public static String asFolderFileName(String text) { 135 | if(text.toLowerCase(Locale.ROOT).endsWith(".zip")) return text.substring(0,text.length() - ".zip".length()); 136 | return text; 137 | } 138 | 139 | private static String version = null; 140 | public static String getVersion() { 141 | if(version != null) return version; 142 | try(InputStreamReader reader = new InputStreamReader(ResourcePackMerger.class.getResourceAsStream("/version.txt"))) { 143 | BufferedReader br = new BufferedReader(reader); 144 | version = br.readLine(); 145 | return version; 146 | } catch (IOException e) { 147 | throw new RuntimeException(e); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/data/Config.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger.data; 2 | 3 | import com.fasterxml.jackson.core.JacksonException; 4 | import com.fasterxml.jackson.core.JsonGenerator; 5 | import com.fasterxml.jackson.core.JsonParser; 6 | import com.fasterxml.jackson.databind.DeserializationContext; 7 | import com.fasterxml.jackson.databind.JsonNode; 8 | import com.fasterxml.jackson.databind.ObjectMapper; 9 | import com.fasterxml.jackson.databind.SerializerProvider; 10 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 11 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 12 | import com.fasterxml.jackson.databind.deser.std.StdDeserializer; 13 | import com.fasterxml.jackson.databind.ser.std.StdSerializer; 14 | import com.jeff_media.resourcepackmerger.PrettyObjectMapper; 15 | import com.jeff_media.resourcepackmerger.gui.GUI; 16 | 17 | import java.io.BufferedWriter; 18 | import java.io.File; 19 | import java.io.FileWriter; 20 | import java.io.IOException; 21 | import java.util.ArrayList; 22 | import java.util.Collections; 23 | import java.util.List; 24 | import java.util.Objects; 25 | 26 | @JsonSerialize(using = Config.ConfigSerializer.class) 27 | @JsonDeserialize(using = Config.ConfigDeserializer.class) 28 | public class Config { 29 | 30 | public final String description; 31 | public final boolean isNewlyGenerated; 32 | public final String outputFile; 33 | public final String overrideIcon; 34 | public final List resourcePackFiles; 35 | public final ResourcePackVersion resourcePackVersion; 36 | public final ZipCompression compression; 37 | 38 | public Config(String description, String outputFile, List resourcePackFiles, ResourcePackVersion resourcePackVersion, ZipCompression compression, String overrideIcon) { 39 | this.description = description; 40 | this.outputFile = outputFile; 41 | this.resourcePackFiles = resourcePackFiles; 42 | this.resourcePackVersion = resourcePackVersion; 43 | this.compression = compression; 44 | this.isNewlyGenerated = false; 45 | this.overrideIcon = overrideIcon; 46 | } 47 | 48 | public Config() { 49 | this.description = "My Custom Resource Pack"; 50 | this.outputFile = new File(System.getProperty("user.home"), "merged-resourcepack.zip").getAbsolutePath(); 51 | this.resourcePackFiles = Collections.emptyList(); 52 | this.resourcePackVersion = ResourcePackVersion.values()[ResourcePackVersion.values().length-1]; 53 | this.compression = ZipCompression.NORMAL; 54 | this.isNewlyGenerated = true; 55 | this.overrideIcon = ""; 56 | } 57 | 58 | public void saveFile() throws IOException { 59 | String json = PrettyObjectMapper.get().writeValueAsString(this); 60 | try(BufferedWriter writer = new BufferedWriter(new FileWriter(getFile(),false))) { 61 | writer.write(json); 62 | } 63 | } 64 | 65 | public static File getFile() { 66 | String dataFolder = System.getenv("APPDATA"); 67 | if (dataFolder == null) dataFolder = System.getProperty("user.home"); 68 | File folder = new File(dataFolder, "JEFF Media GbR"); 69 | File file = new File(folder, "resource-pack-merger.conf"); 70 | return file; 71 | } 72 | 73 | public static Config fromFile() throws IOException { 74 | try { 75 | File file = getFile(); 76 | if (!file.exists()) return new Config(); 77 | return new ObjectMapper().readValue(file, Config.class); 78 | } catch (Throwable t) { 79 | throw new IOException("Could not deserialize config file",t); 80 | } 81 | } 82 | 83 | @Override 84 | public int hashCode() { 85 | return Objects.hash(description, outputFile, resourcePackFiles, resourcePackVersion, overrideIcon); 86 | } 87 | 88 | @Override 89 | public boolean equals(Object o) { 90 | if (this == o) return true; 91 | if (o == null || getClass() != o.getClass()) return false; 92 | Config config = (Config) o; 93 | return Objects.equals(description, config.description) && Objects.equals(outputFile, config.outputFile) && Objects.equals(resourcePackFiles, config.resourcePackFiles) && resourcePackVersion == config.resourcePackVersion && Objects.equals(overrideIcon, config.overrideIcon); 94 | } 95 | 96 | public static class ConfigSerializer extends StdSerializer { 97 | 98 | public ConfigSerializer() { 99 | this(null); 100 | } 101 | 102 | public ConfigSerializer(Class t) { 103 | super(t); 104 | } 105 | 106 | @Override 107 | public void serialize(Config config, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { 108 | jsonGenerator.writeStartObject(); 109 | jsonGenerator.writeStringField("description", config.description); 110 | jsonGenerator.writeStringField("outputFile", config.outputFile); 111 | jsonGenerator.writeArrayFieldStart("resourcePackFiles"); 112 | for(String fileName : config.resourcePackFiles) { 113 | jsonGenerator.writeObject(fileName); 114 | } 115 | jsonGenerator.writeEndArray(); 116 | jsonGenerator.writeNumberField("format", config.resourcePackVersion.getFormat()); 117 | jsonGenerator.writeStringField("compression",config.compression.name()); 118 | jsonGenerator.writeStringField("overrideIcon",config.overrideIcon); 119 | jsonGenerator.writeEndObject(); 120 | } 121 | } 122 | 123 | public static class ConfigDeserializer extends StdDeserializer { 124 | 125 | public ConfigDeserializer() { 126 | this(null); 127 | } 128 | 129 | public ConfigDeserializer(Class vc) { 130 | super(vc); 131 | } 132 | 133 | @Override 134 | public Config deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException { 135 | JsonNode node = jsonParser.getCodec().readTree(jsonParser); 136 | String description = node.get("description").asText(); 137 | String outputFile = node.get("outputFile").asText(); 138 | List resourcePackFiles = new ArrayList<>(); 139 | node.get("resourcePackFiles").elements().forEachRemaining(element -> resourcePackFiles.add(element.asText())); 140 | ResourcePackVersion format = ResourcePackVersion.byFormat(node.get("format").asInt()); 141 | ZipCompression compression = ZipCompression.valueOf(node.get("compression").asText()); 142 | String overrideIcon = GUI.NO_FILE_SELECTED; 143 | try { 144 | String text = node.get("overrideIcon").asText(); 145 | if(!text.equals("null")) { 146 | overrideIcon = text; 147 | } 148 | } catch (Exception ignored) { 149 | 150 | } 151 | return new Config(description, outputFile, resourcePackFiles, format, compression, overrideIcon); 152 | } 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/data/Replacer.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger.data; 2 | 3 | import java.util.Map; 4 | 5 | public class Replacer { 6 | 7 | private final Map map = new java.util.HashMap<>(); 8 | 9 | public Replacer put(final String key, final String value) { 10 | map.put(key, value); 11 | return this; 12 | } 13 | 14 | public String apply(String input) { 15 | for(final Map.Entry entry : map.entrySet()) { 16 | input = input.replace(entry.getKey(), entry.getValue()); 17 | } 18 | return input; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/data/ResourcePackVersion.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger.data; 2 | 3 | // https://minecraft.fandom.com/wiki/Pack_format 4 | public enum ResourcePackVersion { 5 | MC_1_6(1, "1.6.1", "1.8.9"), 6 | MC_1_9(2, "1.9", "1.10.2"), 7 | MC_1_11(3,"1.11","1.12.2"), 8 | MC_1_13(4,"1.13","1.14.4"), 9 | MC_1_15(5,"1.15","1.16.1"), 10 | MC_1_16(6,"1.16.2","1.16.5"), 11 | MC_1_17(7,"1.17","1.17.1"), 12 | MC_1_18(8,"1.18","1.18.2"), 13 | MC_1_19(9,"1.19","1.19.2"), 14 | MC_1_19_22w42a(11,"22w42a","22w44a"), 15 | MC_1_19_3(12, "22w45a", "1.19.3"), 16 | MC_1_19_4(13, "1.19.4", "1.19.4"), 17 | MC_1_20_23w14a(14, "23w14a", "23w16a"), 18 | MC_1_20(15, "1.20", "1.20.1"), 19 | MC_1_20_2(18, "1.20.2", "1.20.2"), 20 | MC_1_20_3(23, "1.20.3", "1.20.4"), 21 | MC_1_20_6(32, "1.20.6", "1.20.6"), 22 | MC_21(34, "1.21", "1.21"); 23 | 24 | private final int format; 25 | private final String nameInMenu; 26 | 27 | ResourcePackVersion(int format, String lowestVersion, String highestVersion) { 28 | this.format = format; 29 | 30 | String supportedVersions = lowestVersion.toString(); 31 | if(!highestVersion.equals(lowestVersion)) supportedVersions += " - " + highestVersion.toString(); 32 | this.nameInMenu = format + " (MC " + supportedVersions + ")"; 33 | } 34 | 35 | public String getNameInMenu() { 36 | return nameInMenu; 37 | } 38 | 39 | public int getFormat() { 40 | return format; 41 | } 42 | 43 | public static ResourcePackVersion byFormat(int format) { 44 | for(ResourcePackVersion version : values()) { 45 | if(version.format == format) return version; 46 | } 47 | throw new IllegalArgumentException(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/data/ZipCompression.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger.data; 2 | 3 | import net.lingala.zip4j.model.enums.CompressionLevel; 4 | 5 | public enum ZipCompression { 6 | 7 | NONE("None", "Level 0, no compression", CompressionLevel.NO_COMPRESSION), 8 | FASTEST("Fastest","Level 1 Deflate compression", CompressionLevel.FASTEST), 9 | FASTER("Faster","Level 2 Deflate compression", CompressionLevel.FASTER), 10 | FAST("Fast","Level 3 Deflate compression", CompressionLevel.FAST), 11 | MEDIUM_FAST("Medium Fast","Level 4 Deflate compression", CompressionLevel.MEDIUM_FAST), 12 | NORMAL("Normal","Level 5 Deflate compression", CompressionLevel.NORMAL), 13 | HIGHER("Higher","Level 6 Deflate compression", CompressionLevel.HIGHER), 14 | MAXIMUM("Maximum","Level 7 Deflate compression", CompressionLevel.MAXIMUM), 15 | PRE_ULTRA("Pre Ultra","Level 8 Deflate compression", CompressionLevel.PRE_ULTRA), 16 | ULTRA("Ultra", "Level 9 Deflate compression", CompressionLevel.ULTRA); 17 | 18 | private final String prettyName; 19 | private final String description; 20 | private final CompressionLevel level; 21 | 22 | ZipCompression(String prettyName, String description, CompressionLevel level) { 23 | this.prettyName = prettyName; 24 | this.description = description; 25 | this.level = level; 26 | } 27 | 28 | public String getNameInMenu() { 29 | return prettyName + " - " + description; 30 | } 31 | 32 | public CompressionLevel getLevel() { 33 | return level; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/gui/AddFileAction.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger.gui; 2 | 3 | import com.jeff_media.resourcepackmerger.ResourcePackFileFilter; 4 | import com.jeff_media.resourcepackmerger.ResourcePackMerger; 5 | 6 | import javax.swing.*; 7 | import java.awt.*; 8 | import java.awt.event.ActionEvent; 9 | import java.awt.event.ActionListener; 10 | import java.io.File; 11 | 12 | public class AddFileAction implements ActionListener { 13 | 14 | private final GUI gui; 15 | 16 | public AddFileAction(GUI gui) { 17 | this.gui = gui; 18 | } 19 | 20 | @Override 21 | public void actionPerformed(ActionEvent e) { 22 | ResourcePackMerger.getLogger().debug("AddFileAction#actionPerformed(ActionEvent)"); 23 | JFileChooser fileChooser = new JFileChooser(); 24 | fileChooser.setFileFilter(new ResourcePackFileFilter()); 25 | fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); 26 | fileChooser.setMultiSelectionEnabled(true); // Enable multi-selection mode 27 | if (gui.lastFile != null && gui.lastFile.exists() && gui.lastFile.isDirectory()) { 28 | fileChooser.setCurrentDirectory(gui.lastFile); 29 | } 30 | int returnValue = fileChooser.showOpenDialog(gui); 31 | if (returnValue == JFileChooser.APPROVE_OPTION) { 32 | File[] files = fileChooser.getSelectedFiles(); // Get the selected files 33 | for (File file : files) { 34 | gui.lastFile = file.getParentFile(); 35 | gui.listFilesModel.addElement(file); 36 | } 37 | gui.updateFileButtons(); 38 | if (gui.listFiles.getSelectedIndex() == -1 && gui.listFilesModel.getSize() > 0) { 39 | gui.listFiles.setSelectedIndex(0); 40 | } 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/gui/ChooseOutputFileAction.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger.gui; 2 | 3 | import com.jeff_media.resourcepackmerger.ResourcePackFileFilter; 4 | import com.jeff_media.resourcepackmerger.Utils; 5 | 6 | import javax.swing.*; 7 | import java.awt.event.ActionEvent; 8 | import java.awt.event.ActionListener; 9 | import java.io.File; 10 | 11 | public class ChooseOutputFileAction implements ActionListener { 12 | private final GUI gui; 13 | 14 | public ChooseOutputFileAction(GUI gui) { 15 | this.gui = gui; 16 | } 17 | 18 | @Override 19 | public void actionPerformed(ActionEvent e) { 20 | 21 | /*JFileChooser fileChooser = new JFileChooser() { 22 | @Override 23 | public void approveSelection() { 24 | File f = getSelectedFile(); 25 | if (f.exists() && getDialogType() == SAVE_DIALOG) { 26 | int result = JOptionPane.showConfirmDialog(this, "The file exists, overwrite?", "Existing file", JOptionPane.YES_NO_CANCEL_OPTION); 27 | switch (result) { 28 | case JOptionPane.YES_OPTION: 29 | super.approveSelection(); 30 | return; 31 | case JOptionPane.CANCEL_OPTION: 32 | cancelSelection(); 33 | return; 34 | default: 35 | return; 36 | } 37 | } 38 | super.approveSelection(); 39 | } 40 | };*/ 41 | JFileChooser fileChooser = new JFileChooser(); 42 | if (gui.buttonOutputFile.getText() != null) { 43 | File output = new File(gui.labelOutputFileValue.getText()); 44 | File directory = output.getParentFile(); 45 | if (!output.exists()) directory = output.getParentFile(); 46 | if (directory.isDirectory()) { 47 | fileChooser.setCurrentDirectory(directory); 48 | fileChooser.setSelectedFile(output); 49 | } 50 | fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); 51 | fileChooser.setFileFilter(new ResourcePackFileFilter()); 52 | int result = fileChooser.showSaveDialog(gui); 53 | if (result == JFileChooser.APPROVE_OPTION) { 54 | File selected = fileChooser.getSelectedFile(); 55 | gui.labelOutputFileValue.setText(Utils.asZipFileName(selected.getAbsolutePath())); 56 | } 57 | } 58 | 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/gui/CompressionLevelListRenderer.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger.gui; 2 | 3 | import com.jeff_media.resourcepackmerger.data.ResourcePackVersion; 4 | import com.jeff_media.resourcepackmerger.data.ZipCompression; 5 | import net.lingala.zip4j.model.enums.CompressionLevel; 6 | 7 | import javax.swing.*; 8 | import java.awt.*; 9 | 10 | public class CompressionLevelListRenderer extends DefaultListCellRenderer { 11 | 12 | @Override 13 | public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { 14 | if(value instanceof ZipCompression) { 15 | value = ((ZipCompression)value).getNameInMenu(); 16 | } 17 | 18 | return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/gui/GUI.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger.gui; 2 | 3 | import com.formdev.flatlaf.themes.FlatMacDarkLaf; 4 | import com.jeff_media.resourcepackmerger.ResourcePackMerger; 5 | import com.jeff_media.resourcepackmerger.Utils; 6 | import com.jeff_media.resourcepackmerger.data.Config; 7 | import com.jeff_media.resourcepackmerger.data.Replacer; 8 | import com.jeff_media.resourcepackmerger.data.ResourcePackVersion; 9 | import com.jeff_media.resourcepackmerger.data.ZipCompression; 10 | import com.jeff_media.resourcepackmerger.logging.GuiLogger; 11 | import net.miginfocom.swing.MigLayout; 12 | 13 | import javax.swing.*; 14 | import java.awt.*; 15 | import java.awt.image.BufferedImage; 16 | import java.io.File; 17 | import java.io.IOException; 18 | import java.nio.file.Paths; 19 | import java.util.Arrays; 20 | import java.util.Calendar; 21 | import java.util.Collections; 22 | import java.util.stream.Collectors; 23 | 24 | public class GUI extends JFrame { 25 | 26 | public static final String INCEPTION_YEAR = "2022"; 27 | public static final String NO_FILE_SELECTED = "No file selected"; 28 | 29 | final JLabel labelName = new JLabel(); 30 | final JTextField fieldName = new JTextField(); 31 | final JLabel labelIcon = new JLabel(); 32 | final JLabel labelIconValue = new JLabel(); 33 | final JButton buttonIcon = new JButton(); 34 | final JButton buttonResetIcon = new JButton(); 35 | final JLabel labelFormat = new JLabel(); 36 | final JComboBox fieldFormat = new JComboBox<>(); 37 | final JComboBox fieldCompression = new JComboBox<>(); 38 | final JLabel labelFiles = new JLabel(); 39 | final JLabel labelOutputFile = new JLabel(); 40 | final JLabel labelOutputFileValue = new JLabel(); 41 | final JScrollPane scrollPanelListFiles = new JScrollPane(); 42 | final DefaultListModel listFilesModel = new DefaultListModel<>(); 43 | final JList listFiles = new JList<>(listFilesModel); 44 | final JPanel fileButtons = new JPanel(); 45 | final JButton buttonAddFile = new JButton(); 46 | final JButton buttonMoveUp = new JButton(); 47 | final JButton buttonMoveDown = new JButton(); 48 | final JButton buttonDeleteFile = new JButton(); 49 | final JScrollPane scrollPaneLog = new JScrollPane(); 50 | final JTextArea log = new JTextArea(); 51 | final JButton buttonStart = new JButton(); 52 | final JProgressBar progressBar = new JProgressBar(); 53 | final JButton buttonOutputFile = new JButton(); 54 | final JButton buttonAbout = new JButton("About"); 55 | File lastFile = null; 56 | 57 | public GUI() { 58 | ResourcePackMerger.setLogger(new GuiLogger(log)); 59 | initComponents(); 60 | loadConfig(); 61 | } 62 | 63 | private void initComponents() { 64 | 65 | int height = 0; 66 | Container contentPane = getContentPane(); 67 | contentPane.setLayout(new MigLayout("hidemode 0", 68 | // columns 69 | "[fill]" + "[fill]" + "[fill][fill]", 70 | // rows 71 | "[]" + "[]" + "[]" + "[]" + "[]" + "[]" + "[]" + "[]")); 72 | 73 | 74 | JPanel headerPanel = new JPanel(); 75 | headerPanel.setLayout(new MigLayout("hidemode 3", 76 | // columns 77 | "[fill][fill]", 78 | // rows 79 | "[][][][]")); 80 | 81 | BufferedImage logo = Utils.getLogo(); 82 | JLabel picLabel = new JLabel(new ImageIcon(logo)); 83 | headerPanel.add(picLabel, "cell 0 0 1 4"); 84 | int height2 = 0; 85 | headerPanel.add(new JLabel(""), "cell 1 " + height2 + " 1 1"); 86 | height2++; 87 | headerPanel.add(new JLabel("Resource Pack Merger v" + ResourcePackMerger.getVersion()), "cell 1 " + height2 + " 1 1"); 88 | height2++; 89 | headerPanel.add(new JLabel("Copyright " + INCEPTION_YEAR + " JEFF Media GbR / mfnalex"), "cell 1 " + height2 + " 1 1"); 90 | height2++; 91 | headerPanel.add(buttonAbout, "cell 1 " + height2 + " 1 1"); 92 | contentPane.add(headerPanel, "cell 0 " + height + " 3 1"); 93 | buttonAbout.addActionListener(new java.awt.event.ActionListener() { 94 | public void actionPerformed(java.awt.event.ActionEvent evt) { 95 | JFrame frame = new JFrame("About ResourcePackMerger"); 96 | frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 97 | frame.setLocationByPlatform(true); 98 | frame.add(new JLabel(new Replacer().put("$version", Utils.getVersion()).put("$year", INCEPTION_YEAR).apply("ResourcePackMerger $version
Copyright $year Alexander Maja (mfnalex) / JEFF Media GbR

GitHub: https://github.com/JEFF-Media-GbR/ResourcePackMerger
Discord: https://discord.jeff-media.com
Donate: https://paypal.me/mfnalex"))); 99 | frame.pack(); 100 | frame.setVisible(true); 101 | } 102 | }); 103 | 104 | // 105 | height++; 106 | labelName.setText("Description"); 107 | contentPane.add(labelName, "cell 0 " + height); 108 | 109 | fieldName.setText("My Custom Resource Pack"); 110 | contentPane.add(fieldName, "cell 1 " + height + " 2 1"); 111 | // 112 | 113 | // 114 | height++; 115 | labelIcon.setText("Override Icon"); 116 | contentPane.add(labelIcon, "cell 0 " + height); 117 | labelIconValue.setText(NO_FILE_SELECTED); 118 | contentPane.add(labelIconValue, "cell 1 " + height + " 2 1"); 119 | 120 | JPanel iconButtonsPanel = new JPanel(new MigLayout("insets 0", "[fill][fill]", "")); 121 | buttonIcon.setText("Change"); 122 | iconButtonsPanel.add(buttonIcon, "cell 0 0"); 123 | buttonResetIcon.setText("Reset"); 124 | iconButtonsPanel.add(buttonResetIcon, "cell 1 0"); 125 | contentPane.add(iconButtonsPanel, "cell 3 " + height); 126 | 127 | buttonIcon.addActionListener(new ChooseIconFileAction(this)); 128 | buttonResetIcon.addActionListener(e -> labelIconValue.setText("No file selected")); 129 | // 130 | 131 | // 132 | height++; 133 | labelFormat.setText("Resource Pack Format"); 134 | contentPane.add(labelFormat, "cell 0 " + height); 135 | contentPane.add(fieldFormat, "cell 1 " + height + " 2 1"); 136 | 137 | Arrays.stream(ResourcePackVersion.values()).forEachOrdered(version -> fieldFormat.addItem(version)); 138 | fieldFormat.setRenderer(new ResourcePackVersionListRenderer()); 139 | fieldFormat.setSelectedIndex(fieldFormat.getItemCount() - 1); 140 | // 141 | 142 | // 143 | height++; 144 | labelOutputFile.setText("Output file"); 145 | contentPane.add(labelOutputFile, "cell 0 " + height); 146 | labelOutputFileValue.setText(new File("merged-resourcepack.zip").getAbsolutePath()); 147 | contentPane.add(labelOutputFileValue, "cell 1 " + height + " 2 1"); 148 | 149 | JPanel outputFileButtonsPanel = new JPanel(new MigLayout("insets 0", "[fill][fill]", "")); 150 | buttonOutputFile.setText("Change"); 151 | outputFileButtonsPanel.add(buttonOutputFile, "cell 0 0"); 152 | contentPane.add(outputFileButtonsPanel, "cell 3 " + height); 153 | 154 | buttonOutputFile.addActionListener(new ChooseOutputFileAction(this)); 155 | // 156 | 157 | // 158 | height++; 159 | labelFiles.setText("Resource Packs"); 160 | contentPane.add(labelFiles, "cell 0 " + height); 161 | scrollPanelListFiles.setViewportView(listFiles); 162 | scrollPanelListFiles.setMinimumSize(new Dimension(500, 200)); 163 | contentPane.add(scrollPanelListFiles, "cell 1 " + height + " 2 1"); 164 | 165 | fileButtons.setLayout(new MigLayout("hidemode 3", 166 | // columns 167 | "[fill]", 168 | // rows 169 | "[]" + "[]" + "[]" + "[]")); 170 | 171 | buttonAddFile.setText("Add..."); 172 | fileButtons.add(buttonAddFile, "cell 0 0"); 173 | 174 | buttonMoveUp.setText("Move Up"); 175 | buttonMoveUp.setEnabled(false); 176 | fileButtons.add(buttonMoveUp, "cell 0 1"); 177 | 178 | buttonMoveDown.setText("Move Down"); 179 | buttonMoveDown.setEnabled(false); 180 | fileButtons.add(buttonMoveDown, "cell 0 2"); 181 | 182 | buttonDeleteFile.setText("Delete"); 183 | buttonDeleteFile.setEnabled(false); 184 | fileButtons.add(buttonDeleteFile, "cell 0 3"); 185 | 186 | contentPane.add(fileButtons, "cell 3 " + height); 187 | 188 | buttonAddFile.addActionListener(new AddFileAction(this)); 189 | listFiles.addListSelectionListener(__ -> updateFileButtons()); 190 | buttonMoveDown.addActionListener(__ -> moveFileInList(1)); 191 | buttonMoveUp.addActionListener(__ -> moveFileInList(-1)); 192 | buttonDeleteFile.addActionListener(__ -> { 193 | listFilesModel.remove(listFiles.getSelectedIndex()); 194 | updateFileButtons(); 195 | }); 196 | // 197 | 198 | // 199 | height++; 200 | contentPane.add(new JLabel("ZIP Compression"), "cell 0 " + height); 201 | contentPane.add(fieldCompression, "cell 1 " + height + " 2 1"); 202 | 203 | fieldCompression.setRenderer(new CompressionLevelListRenderer()); 204 | Arrays.stream(ZipCompression.values()).forEachOrdered(level -> fieldCompression.addItem(level)); 205 | // 206 | 207 | // 208 | height++; 209 | log.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); 210 | scrollPaneLog.setViewportView(log); 211 | contentPane.add(scrollPaneLog, "cell 0 " + height + " 4 1,hmin 150"); 212 | 213 | ResourcePackMerger.getLogger().info("Waiting for input. Add at least one resource pack, then click on \"Start\"."); 214 | ResourcePackMerger.getLogger().info("JSON files will automatically be merged when two or more resource packs contain conflicting files."); 215 | ResourcePackMerger.getLogger().info("When files cannot be merged, the file of the resource pack that's higher in the list takes precedence."); 216 | // 217 | 218 | // 219 | height++; 220 | contentPane.add(progressBar, "cell 0 " + height + " 4 1"); 221 | 222 | progressBar.setIndeterminate(false); 223 | // 224 | 225 | // 226 | height++; 227 | buttonStart.setText("Start"); 228 | contentPane.add(buttonStart, "cell 0 " + height + " 4 1"); 229 | 230 | buttonStart.addActionListener(new StartButtonAction(this)); 231 | // 232 | 233 | pack(); 234 | setLocationRelativeTo(getOwner()); 235 | 236 | this.addWindowListener(new WindowListener(this)); 237 | } 238 | 239 | private void loadConfig() { 240 | Config config = new Config(); 241 | try { 242 | config = Config.fromFile(); 243 | if (!config.isNewlyGenerated) { 244 | ResourcePackMerger.getLogger().debug("Config loaded successfully."); 245 | } 246 | } catch (IOException e) { 247 | ResourcePackMerger.getLogger().error("Could not load config: " + e.getMessage()); 248 | } 249 | fieldName.setText(config.description); 250 | fieldFormat.setSelectedItem(config.resourcePackVersion); 251 | fieldCompression.setSelectedItem(config.compression); 252 | config.resourcePackFiles.stream().forEachOrdered(name -> { 253 | File file = Paths.get(name).toFile(); 254 | if (file.exists()) { 255 | listFilesModel.addElement(file); 256 | } else { 257 | ResourcePackMerger.getLogger().warn("Recently used Resource Pack " + name + " not found."); 258 | } 259 | }); 260 | labelOutputFileValue.setText(config.outputFile); 261 | labelIconValue.setText(config.overrideIcon != null ? config.overrideIcon : "No file selected"); 262 | } 263 | 264 | void updateFileButtons() { 265 | boolean enabled = true; 266 | if (listFilesModel.isEmpty() || listFiles.getSelectedIndex() == -1) { 267 | enabled = false; 268 | } 269 | boolean moveUpAllowed = enabled; 270 | boolean moveDownAllowed = enabled; 271 | if (listFiles.getSelectedIndex() < 1) { 272 | moveUpAllowed = false; 273 | } 274 | if (listFiles.getSelectedIndex() >= listFilesModel.size() - 1) { 275 | moveDownAllowed = false; 276 | } 277 | buttonDeleteFile.setEnabled(enabled); 278 | buttonMoveUp.setEnabled(moveUpAllowed); 279 | buttonMoveDown.setEnabled(moveDownAllowed); 280 | buttonStart.setEnabled(enabled); 281 | } 282 | 283 | private void moveFileInList(int offset) { 284 | int selected = listFiles.getSelectedIndex(); 285 | File current = listFilesModel.remove(selected); 286 | listFilesModel.add(selected + offset, current); 287 | listFiles.setSelectedIndex(selected + offset); 288 | updateFileButtons(); 289 | } 290 | 291 | public void saveConfig() { 292 | try { 293 | new Config(fieldName.getText(), labelOutputFileValue.getText(), Collections.list(listFilesModel.elements()).stream().map(file -> file.getAbsolutePath()).collect(Collectors.toList()), (ResourcePackVersion) fieldFormat.getSelectedItem(), (ZipCompression) fieldCompression.getSelectedItem(), labelIconValue.getText().equals("No file selected") ? null : labelIconValue.getText()).saveFile(); 294 | } catch (IOException e) { 295 | e.printStackTrace(); 296 | } 297 | } 298 | 299 | private class ChooseIconFileAction extends AbstractAction { 300 | private final GUI parent; 301 | 302 | ChooseIconFileAction(GUI parent) { 303 | this.parent = parent; 304 | } 305 | 306 | @Override 307 | public void actionPerformed(java.awt.event.ActionEvent evt) { 308 | JFileChooser fileChooser = new JFileChooser(); 309 | fileChooser.setFileFilter(new javax.swing.filechooser.FileFilter() { 310 | @Override 311 | public boolean accept(File f) { 312 | return f.isDirectory() || f.getName().toLowerCase().endsWith(".png"); 313 | } 314 | 315 | @Override 316 | public String getDescription() { 317 | return "PNG files"; 318 | } 319 | }); 320 | int returnValue = fileChooser.showOpenDialog(parent); 321 | if (returnValue == JFileChooser.APPROVE_OPTION) { 322 | File selectedFile = fileChooser.getSelectedFile(); 323 | labelIconValue.setText(selectedFile.getAbsolutePath()); 324 | } 325 | } 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/gui/ResourcePackVersionListRenderer.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger.gui; 2 | 3 | import com.jeff_media.resourcepackmerger.data.ResourcePackVersion; 4 | 5 | import javax.swing.*; 6 | import java.awt.*; 7 | 8 | public class ResourcePackVersionListRenderer extends DefaultListCellRenderer { 9 | 10 | @Override 11 | public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { 12 | if(value instanceof ResourcePackVersion) { 13 | value = ((ResourcePackVersion)value).getNameInMenu(); 14 | } 15 | 16 | return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/gui/StartButtonAction.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger.gui; 2 | 3 | import com.jeff_media.resourcepackmerger.ResourcePackMerger; 4 | import com.jeff_media.resourcepackmerger.Utils; 5 | import com.jeff_media.resourcepackmerger.data.ZipCompression; 6 | import com.jeff_media.resourcepackmerger.mergers.IconOverride; 7 | import com.jeff_media.resourcepackmerger.mergers.McMetaMerger; 8 | 9 | import javax.swing.*; 10 | import java.awt.event.ActionEvent; 11 | import java.awt.event.ActionListener; 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.nio.file.Path; 15 | import java.nio.file.Paths; 16 | import java.util.ArrayList; 17 | import java.util.Arrays; 18 | import java.util.concurrent.TimeUnit; 19 | 20 | public class StartButtonAction implements ActionListener { 21 | 22 | private final GUI gui; 23 | 24 | public StartButtonAction(GUI gui) { 25 | this.gui = gui; 26 | } 27 | 28 | @Override 29 | public void actionPerformed(ActionEvent e) { 30 | 31 | Path zipFilePath = Paths.get(Utils.asZipFileName(gui.labelOutputFileValue.getText())); 32 | File zipFile = zipFilePath.toFile(); 33 | String zipFilePathName = zipFile.getAbsolutePath(); 34 | File tempFolder = Utils.getTempFolder(); 35 | 36 | if(gui.listFiles.getModel().getSize() == 0) { 37 | ResourcePackMerger.getLogger().error("Please add at least one resource pack file."); 38 | return; 39 | } 40 | 41 | 42 | boolean deleteOld = false; 43 | if (zipFile.exists()) { 44 | int result = JOptionPane.showConfirmDialog(gui, "The file " + zipFilePathName + " already exists. Do you want to overwrite it?", "Existing file", JOptionPane.YES_NO_OPTION); 45 | switch (result) { 46 | case JOptionPane.YES_OPTION: 47 | deleteOld = true; 48 | break; 49 | case JOptionPane.NO_OPTION: 50 | return; 51 | } 52 | } 53 | 54 | gui.progressBar.setIndeterminate(true); 55 | java.util.List params = new ArrayList<>(); 56 | Arrays.stream(gui.listFilesModel.toArray()).map(object -> ((File) object).getAbsolutePath()).forEachOrdered(params::add); 57 | params.add(tempFolder.getAbsolutePath()); 58 | boolean finalDeleteOld = deleteOld; 59 | new Thread(() -> { 60 | long start = System.currentTimeMillis(); 61 | 62 | if(finalDeleteOld) { 63 | try { 64 | ResourcePackMerger.getLogger().info("Deleting old file " + zipFilePathName); 65 | Utils.delete(zipFile); 66 | } catch (IOException ex) { 67 | ex.printStackTrace(); 68 | } 69 | 70 | if (zipFile.exists()) { 71 | ResourcePackMerger.getLogger().error("Could not delete existing file " + zipFilePathName + ". Please delete it manually or choose another output location."); 72 | return; 73 | } 74 | } 75 | 76 | 77 | try { 78 | ResourcePackMerger.main(params.toArray(new String[0])); 79 | File mcMetaFile = new File(tempFolder, "pack.mcmeta"); 80 | if (mcMetaFile.exists()) { 81 | ResourcePackMerger.getLogger().info("Adjusting pack.mcmeta file"); 82 | McMetaMerger.apply(mcMetaFile, gui.fieldName.getText(), gui.fieldFormat.getItemAt(gui.fieldFormat.getSelectedIndex())); 83 | } 84 | 85 | if(!gui.labelIconValue.getText().isEmpty() && !gui.labelIconValue.getText().equals(GUI.NO_FILE_SELECTED)) { 86 | ResourcePackMerger.getLogger().info("Applying icon override"); 87 | IconOverride.apply(gui.labelIconValue.getText(), tempFolder); 88 | } 89 | 90 | } catch (Throwable t) { 91 | ResourcePackMerger.getLogger().error(t.getMessage()); 92 | return; 93 | } 94 | 95 | ResourcePackMerger.getLogger().info("Zipping resource pack (Deflate Level " + (((ZipCompression)gui.fieldCompression.getSelectedItem()).getLevel().getLevel()) + ")"); 96 | Utils.zipDirectory(tempFolder, zipFile, (((ZipCompression)gui.fieldCompression.getSelectedItem()).getLevel())); 97 | ResourcePackMerger.getLogger().info("Deleting work directories"); 98 | ResourcePackMerger.deleteWorkDirectories(); 99 | 100 | 101 | gui.progressBar.setIndeterminate(false); 102 | long end = System.currentTimeMillis(); 103 | ResourcePackMerger.getLogger().info("Finished task in " + TimeUnit.SECONDS.convert(end - start, TimeUnit.MILLISECONDS) + " seconds."); 104 | }).start(); 105 | 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/gui/WindowListener.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger.gui; 2 | 3 | import java.awt.event.WindowEvent; 4 | 5 | public class WindowListener implements java.awt.event.WindowListener { 6 | private final GUI gui; 7 | 8 | public WindowListener(GUI gui) { 9 | this.gui = gui; 10 | } 11 | 12 | @Override 13 | public void windowOpened(WindowEvent e) { 14 | 15 | } 16 | 17 | @Override 18 | public void windowClosing(WindowEvent e) { 19 | gui.saveConfig(); 20 | } 21 | 22 | @Override 23 | public void windowClosed(WindowEvent e) { 24 | 25 | } 26 | 27 | @Override 28 | public void windowIconified(WindowEvent e) { 29 | 30 | } 31 | 32 | @Override 33 | public void windowDeiconified(WindowEvent e) { 34 | 35 | } 36 | 37 | @Override 38 | public void windowActivated(WindowEvent e) { 39 | 40 | } 41 | 42 | @Override 43 | public void windowDeactivated(WindowEvent e) { 44 | gui.saveConfig(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/logging/ConsoleLogger.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger.logging; 2 | 3 | import org.slf4j.LoggerFactory; 4 | 5 | public class ConsoleLogger implements Logger { 6 | 7 | private static final org.slf4j.Logger logger = LoggerFactory.getLogger(ConsoleLogger.class); 8 | 9 | 10 | @Override 11 | public void info(String text) { 12 | logger.info(text); 13 | } 14 | 15 | @Override 16 | public void debug(String text) { 17 | logger.debug(text); 18 | } 19 | 20 | @Override 21 | public void error(String text) { 22 | logger.error(text); 23 | } 24 | 25 | @Override 26 | public void warn(String text) { 27 | logger.warn(text); 28 | } 29 | 30 | @Override 31 | public void error(String text, Throwable t) { 32 | logger.error(text, t); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/logging/GuiLogger.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger.logging; 2 | 3 | import javax.swing.*; 4 | import javax.swing.text.JTextComponent; 5 | import java.text.SimpleDateFormat; 6 | import java.util.Date; 7 | 8 | public class GuiLogger implements Logger { 9 | 10 | private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("HH:mm:ss"); 11 | private static final ConsoleLogger LOGGER = new ConsoleLogger(); 12 | private final JTextArea area; 13 | 14 | public GuiLogger(JTextArea area) { 15 | this.area = area; 16 | } 17 | 18 | private void log(String level, String text) { 19 | log(level, text, null); 20 | } 21 | 22 | private void log(String level, String text, Throwable t) { 23 | Date date = new Date(); 24 | String message = String.format("[%s] [%s] %s%s", SIMPLE_DATE_FORMAT.format(date), level, text, System.lineSeparator()); 25 | area.append(message); 26 | area.setCaretPosition(area.getText().length()); 27 | 28 | if(t != null) { 29 | log(level, t.getMessage()); 30 | } 31 | } 32 | 33 | 34 | @Override 35 | public void info(String text) { 36 | log("INFO",text); 37 | LOGGER.info(text); 38 | } 39 | 40 | @Override 41 | public void debug(String text) { 42 | //log("DEBUG", text); 43 | LOGGER.debug(text); 44 | } 45 | 46 | @Override 47 | public void error(String text) { 48 | log("ERROR", text); 49 | LOGGER.error(text); 50 | } 51 | 52 | @Override 53 | public void warn(String text) { 54 | log("WARN", text); 55 | LOGGER.warn(text); 56 | } 57 | 58 | @Override 59 | public void error(String text, Throwable t) { 60 | log("ERROR", text); 61 | LOGGER.error(text, t); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/logging/Logger.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger.logging; 2 | 3 | public interface Logger { 4 | void info(String text); 5 | void debug(String text); 6 | void error(String text); 7 | void warn(String text); 8 | void error(String text, Throwable t); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/mergers/DirectoryMerger.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger.mergers; 2 | 3 | import com.jeff_media.resourcepackmerger.ResourcePackMerger; 4 | import com.jeff_media.resourcepackmerger.Utils; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.nio.file.Files; 9 | import java.util.Arrays; 10 | import java.util.Objects; 11 | import java.util.stream.Collectors; 12 | 13 | public class DirectoryMerger { 14 | 15 | final File[] inputFiles; 16 | final File outputFile; 17 | 18 | public DirectoryMerger(File outputFile, File... inputFiles) { 19 | this.outputFile = outputFile; 20 | this.inputFiles = inputFiles; 21 | } 22 | 23 | public boolean merge() { 24 | if(!outputFile.isDirectory() && !outputFile.mkdirs()) { 25 | throw new RuntimeException("[Error 2] Could not create directory " + outputFile.getAbsolutePath()); 26 | } 27 | for(File directory : inputFiles) { 28 | //try { 29 | //ResourcePackMerger.LOGGER.info("Copying " + directory.getAbsolutePath() + " to " + outputFile.getAbsolutePath()); 30 | for(File file : Arrays.stream(directory.listFiles()).sorted((o1, o2) -> Boolean.compare(o1.isDirectory(),o2.isDirectory())).collect(Collectors.toList())) { 31 | File newFile = new File(outputFile, file.getName()); 32 | if(file.isDirectory()) { 33 | ResourcePackMerger.getLogger().info("Copying Directory " + file.getAbsolutePath() + " to " + newFile.getAbsolutePath()); 34 | newFile.mkdirs(); 35 | new DirectoryMerger(newFile, file).merge(); 36 | } else { 37 | ResourcePackMerger.getLogger().info(" |- Copying File " + file.getAbsolutePath() + " to " + newFile.getAbsolutePath()); 38 | try { 39 | if(!newFile.exists()) { 40 | Files.copy(file.toPath(), newFile.toPath()); 41 | } else { 42 | if(Utils.isJsonFile(newFile)) { 43 | ResourcePackMerger.getLogger().info(" |- File already exists, trying to merge JSON"); 44 | try { 45 | JsonMerger.merge(newFile, file); 46 | } catch (Throwable t) { 47 | System.out.println("Error while merging JSON"); 48 | t.printStackTrace(); 49 | } 50 | } else { 51 | ResourcePackMerger.getLogger().info(" |- File already exists, skipping"); 52 | } 53 | } 54 | } catch (IOException e) { 55 | throw new RuntimeException(e); 56 | } 57 | } 58 | } 59 | /*} catch (IOException e) { 60 | e.printStackTrace(); 61 | return false; 62 | }*/ 63 | } 64 | return true; 65 | } 66 | 67 | public void checkPrerequisites() throws RuntimeException { 68 | for(File file : inputFiles) { 69 | if(!file.isDirectory()) { 70 | throw new RuntimeException("Not a directory: " + file.getAbsolutePath()); 71 | } 72 | } 73 | 74 | if(outputFile.exists()) { 75 | if(!outputFile.isDirectory()) { 76 | throw new RuntimeException("Not a directory: " + outputFile.getAbsolutePath()); 77 | } 78 | /*if(!outputFile.mkdirs()) { 79 | throw new RuntimeException("[Error 3] Could not create directory: " + outputFile.getAbsolutePath()); 80 | }*/ 81 | if(Objects.requireNonNull(outputFile.list()).length > 0) { 82 | throw new RuntimeException("Directory is not empty: " + outputFile.getAbsolutePath()); 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/mergers/IconOverride.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger.mergers; 2 | 3 | import com.jeff_media.resourcepackmerger.ResourcePackMerger; 4 | import com.jeff_media.resourcepackmerger.gui.GUI; 5 | import org.apache.commons.io.FileUtils; 6 | 7 | import java.io.File; 8 | 9 | public class IconOverride { 10 | 11 | public static void apply(String iconPath, File targetFolder) { 12 | if(iconPath == null || iconPath.isEmpty() || iconPath.equals(GUI.NO_FILE_SELECTED)) return; 13 | 14 | File icon = new File(iconPath); 15 | if(!icon.exists()) { 16 | ResourcePackMerger.getLogger().warn("Icon file "+iconPath+" does not exist."); 17 | return; 18 | } 19 | 20 | File targetFile = new File(targetFolder, "pack.png"); 21 | if(targetFile.exists()) { 22 | targetFile.delete(); 23 | } 24 | try { 25 | FileUtils.copyFile(icon, targetFile); 26 | } catch (Exception e) { 27 | ResourcePackMerger.getLogger().error("Error while copying icon file to target folder: ",e); 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/mergers/JsonMerger.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger.mergers; 2 | 3 | import com.fasterxml.jackson.core.JsonFactory; 4 | import com.fasterxml.jackson.core.JsonParseException; 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import com.jeff_media.resourcepackmerger.PrettyObjectMapper; 8 | import com.jeff_media.resourcepackmerger.ResourcePackMerger; 9 | import com.jeff_media.resourcepackmerger.Utils; 10 | 11 | import java.io.*; 12 | import java.nio.charset.Charset; 13 | import java.nio.charset.StandardCharsets; 14 | import java.nio.file.Files; 15 | import java.nio.file.OpenOption; 16 | import java.nio.file.StandardOpenOption; 17 | import java.util.ArrayList; 18 | import java.util.Arrays; 19 | import java.util.Collections; 20 | import java.util.HashMap; 21 | import java.util.HashSet; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.stream.Collectors; 25 | 26 | public class JsonMerger { 27 | 28 | public static void merge(File existingFile, File newFile) throws IOException { 29 | Map oldMap = convertJsonToMap(existingFile); 30 | Map newMap = convertJsonToMap(newFile); 31 | Map mergedMap = mergeMaps(oldMap, newMap); 32 | 33 | String mergedJson = convertMapToJson(mergedMap); 34 | Files.write(existingFile.toPath(), Collections.singletonList(mergedJson), StandardCharsets.UTF_8, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); 35 | } 36 | 37 | // public static String merge(String oldJson, String newJson) throws JsonProcessingException { 38 | // Map oldMap = convertJsonToMap(oldJson); 39 | // Map newMap = convertJsonToMap(newJson); 40 | // return new ObjectMapper().writeValueAsString(mergeMaps(oldMap, newMap)); 41 | // } 42 | 43 | private static String convertMapToJson(Map map) throws JsonProcessingException { 44 | return PrettyObjectMapper.get().writeValueAsString(map); 45 | } 46 | 47 | private static final HashSet MERGEABLE_ARRAY_KEYS = new HashSet(List.of("overrides", "sources", "supported_formats")); 48 | 49 | private static Map mergeMaps(Map oldMap, Map newMap) { 50 | Map map = new HashMap<>(newMap); 51 | for(Map.Entry entry : oldMap.entrySet()) { 52 | if(!map.containsKey(entry.getKey())) { 53 | map.put(entry.getKey(), entry.getValue()); 54 | } else { 55 | if(entry.getValue() instanceof Map && newMap.get(entry.getKey()) instanceof Map) { 56 | map.put(entry.getKey(), mergeMaps((Map) oldMap.get(entry.getKey()), (Map) newMap.get(entry.getKey()))); 57 | } 58 | else if (newMap.get(entry.getKey()) instanceof ArrayList && MERGEABLE_ARRAY_KEYS.contains(entry.getKey())){ 59 | ArrayList arr = new ArrayList((ArrayList) newMap.get(entry.getKey())); 60 | if (entry.getValue() instanceof ArrayList) { 61 | arr.addAll((ArrayList) entry.getValue()); 62 | } else { 63 | arr.add(entry.getValue()); 64 | } 65 | map.put(entry.getKey(), arr); 66 | } else if(entry.getValue() instanceof ArrayList && MERGEABLE_ARRAY_KEYS.contains(entry.getKey())){ 67 | ArrayList arr = new ArrayList((ArrayList) entry.getValue()); 68 | arr.add(newMap.get(entry.getKey())); 69 | map.put(entry.getKey(), arr); 70 | } else if(entry.getKey().equals("supported_formats")){ 71 | map.put(entry.getKey(), new ArrayList(List.of(newMap.get(entry.getKey()), entry.getValue()))); 72 | } else if(entry.getValue() instanceof String && newMap.get(entry.getKey()) instanceof String){ 73 | String strOld = (String) entry.getValue(); 74 | String strNew = (String) newMap.get(entry.getKey()); 75 | if(strOld.equals(strNew)){ 76 | ; 77 | } else if(strOld.replace("minecraft:", "").equals(strNew.replace("minecraft:", ""))){ 78 | ; /* kind of hacky and not quite correct, but to address and properly ignore differences like: 79 | "parent" : "minecraft:block/structure_block" 80 | versus 81 | "parent" : "block/structure_block", */ 82 | } else { 83 | ResourcePackMerger.getLogger().info(String.format(" !Warn: Unsure how to merge key %s\n preferring %s over %s based on pack order", 84 | entry.getKey(), strOld, strNew)); 85 | } 86 | } else { 87 | ResourcePackMerger.getLogger().info(String.format(" !Warn: Unsure how to merge key %s", entry.getKey())); 88 | } 89 | } 90 | } 91 | return map; 92 | } 93 | 94 | private static Map convertJsonToMap(File file) throws IOException { 95 | try ( 96 | BufferedReader reader = new BufferedReader(new FileReader(file)); 97 | ) { 98 | return convertJsonToMap(reader.lines().collect(Collectors.joining(System.lineSeparator()))); 99 | } 100 | } 101 | 102 | private static Map convertJsonToMap(String json) throws JsonProcessingException { 103 | try { 104 | return new ObjectMapper().readValue(json, HashMap.class); 105 | } catch (JsonParseException exception) { 106 | ResourcePackMerger.getLogger().warn("Failed to parse JSON: " + exception.getMessage()); 107 | exception.printStackTrace(); 108 | return new HashMap<>(); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/jeff_media/resourcepackmerger/mergers/McMetaMerger.java: -------------------------------------------------------------------------------- 1 | package com.jeff_media.resourcepackmerger.mergers; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.jeff_media.resourcepackmerger.PrettyObjectMapper; 5 | import com.jeff_media.resourcepackmerger.data.ResourcePackVersion; 6 | 7 | import java.io.*; 8 | import java.lang.reflect.Field; 9 | import java.util.ArrayList; 10 | import java.util.LinkedHashSet; 11 | import java.util.Map; 12 | 13 | public class McMetaMerger { 14 | 15 | public static void apply(File file, String description, ResourcePackVersion version) throws IOException { 16 | try (BufferedReader reader = new BufferedReader(new FileReader(file))) { 17 | Map map; 18 | Map pack; 19 | try { 20 | map = (Map) new ObjectMapper().readValue(file, Map.class); 21 | pack = (Map) map.get("pack"); 22 | } catch (Throwable ignored) { 23 | map = new java.util.HashMap<>(); 24 | pack = new java.util.HashMap<>(); 25 | } 26 | pack.put("description", description); 27 | pack.put("pack_format", version.getFormat()); 28 | LinkedHashSet supportedFormats = new LinkedHashSet(); 29 | if (pack.containsKey("supported_formats")) { 30 | supportedFormats.addAll((ArrayList) pack.get("supported_formats")); 31 | } 32 | supportedFormats.add(version.getFormat()); 33 | pack.put("supported_formats", new ArrayList(supportedFormats)); 34 | map.put("pack", pack); 35 | String json = PrettyObjectMapper.get().writeValueAsString(map); 36 | try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { 37 | writer.write(json); 38 | } 39 | } catch (Throwable ignored) { 40 | 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/resources/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfnalex/ResourcePackMerger/004a47cdb50b1e6ae252b238863289746ac387b1/src/main/resources/128.png -------------------------------------------------------------------------------- /src/main/resources/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfnalex/ResourcePackMerger/004a47cdb50b1e6ae252b238863289746ac387b1/src/main/resources/16.png -------------------------------------------------------------------------------- /src/main/resources/192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfnalex/ResourcePackMerger/004a47cdb50b1e6ae252b238863289746ac387b1/src/main/resources/192.png -------------------------------------------------------------------------------- /src/main/resources/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfnalex/ResourcePackMerger/004a47cdb50b1e6ae252b238863289746ac387b1/src/main/resources/24.png -------------------------------------------------------------------------------- /src/main/resources/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfnalex/ResourcePackMerger/004a47cdb50b1e6ae252b238863289746ac387b1/src/main/resources/256.png -------------------------------------------------------------------------------- /src/main/resources/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfnalex/ResourcePackMerger/004a47cdb50b1e6ae252b238863289746ac387b1/src/main/resources/32.png -------------------------------------------------------------------------------- /src/main/resources/364.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfnalex/ResourcePackMerger/004a47cdb50b1e6ae252b238863289746ac387b1/src/main/resources/364.png -------------------------------------------------------------------------------- /src/main/resources/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfnalex/ResourcePackMerger/004a47cdb50b1e6ae252b238863289746ac387b1/src/main/resources/48.png -------------------------------------------------------------------------------- /src/main/resources/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfnalex/ResourcePackMerger/004a47cdb50b1e6ae252b238863289746ac387b1/src/main/resources/64.png -------------------------------------------------------------------------------- /src/main/resources/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfnalex/ResourcePackMerger/004a47cdb50b1e6ae252b238863289746ac387b1/src/main/resources/96.png -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%level] [%thread] %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/resources/logo_32_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfnalex/ResourcePackMerger/004a47cdb50b1e6ae252b238863289746ac387b1/src/main/resources/logo_32_32.png -------------------------------------------------------------------------------- /src/main/resources/logo_64_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mfnalex/ResourcePackMerger/004a47cdb50b1e6ae252b238863289746ac387b1/src/main/resources/logo_64_64.png -------------------------------------------------------------------------------- /src/main/resources/project.properties: -------------------------------------------------------------------------------- 1 | version=${project.version} -------------------------------------------------------------------------------- /src/main/resources/version.txt: -------------------------------------------------------------------------------- 1 | ${project.version} --------------------------------------------------------------------------------