├── .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 | 
--------------------------------------------------------------------------------
/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}
--------------------------------------------------------------------------------