diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8a0a60d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# Project exclude paths +/.gradle/ +/build/ +/build/classes/java/main/ \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..d8e9561 --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..0be8e21 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..fdc392f --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..87a20fc --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..789399f --- /dev/null +++ b/build.gradle @@ -0,0 +1,27 @@ +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation('commons-io:commons-io:2.11.0') + implementation('net.sf.sevenzipjbinding:sevenzipjbinding:16.02-2.01') + implementation('net.sf.sevenzipjbinding:sevenzipjbinding-all-platforms:16.02-2.01') + implementation('org.apache.commons:commons-compress:1.21') + implementation('com.twelvemonkeys.imageio:imageio-tga:3.9.3') + implementation('org.jsoup:jsoup:1.15.4') + implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.13' +} + +task fatJar(type: Jar) { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + manifest { + attributes 'Main-Class': 'swim.porter.Main' + } + baseName = project.name + '-all' + from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } + with jar +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..41d9927 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..41dfb87 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..946a54d --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'PorterEngine' + diff --git a/src/main/java/swim/porter/Main.java b/src/main/java/swim/porter/Main.java new file mode 100644 index 0000000..4869b3b --- /dev/null +++ b/src/main/java/swim/porter/Main.java @@ -0,0 +1,70 @@ +package swim.porter; + +import org.apache.commons.io.FileUtils; +import swim.porter.engine.PortFileProcessor; +import swim.porter.engine.ZipUtil; + +import java.io.File; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.nio.file.Paths; + +public class Main { + + public static String portDir; + + static { + try { + File jarFile = new File(Main.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()); + portDir = Paths.get(jarFile.getParent(), "Pack Jar").toString(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + public static void main(String[] args) { + setUpAssets(); + String returnValue = ""; // The string value you want to return + + if (args.length > 0 && args[0].equalsIgnoreCase("port") && args.length > 1) { + String arg = args[1]; + String portedPackPath = PortFileProcessor.handlePortCommand(arg); + returnValue = portedPackPath; // Set the return value + } else { + System.out.println("Proper command syntax is 'port url'\n Examples:\n port \"mediafire.com/file.zip\"\n port \"C:\\file.zip\""); + } + + // Set the exit code + // our server will have to parse the hash code back into a string and upload file back to client + System.exit(returnValue.hashCode()); + } + + private static void setUpAssets() { + try { + File portBotDirFile = new File(portDir); + File assetsDir = new File(portBotDirFile, "assets"); + // Check if we don't have an assets folder + if (!assetsDir.exists()) { + portBotDirFile.mkdirs(); + // If running from the JAR, copy the assets.zip to a temporary file and extract from there + if (Main.class.getResource("/assets.zip").toString().startsWith("jar:")) { + try (InputStream inputStream = Main.class.getResourceAsStream("/assets.zip")) { + File tempAssetsZip = File.createTempFile("assets", ".zip"); + FileUtils.copyInputStreamToFile(inputStream, tempAssetsZip); + ZipUtil.lightUnzip(tempAssetsZip, assetsDir); + tempAssetsZip.delete(); + } + } else { + // If running from IDE or directly from the extracted JAR, extract assets.zip directly + File assetsZipFile = new File(Main.class.getResource("/assets.zip").toURI()); + ZipUtil.lightUnzip(assetsZipFile, assetsDir); + } + } + // need to clean up from any past uses + PortFileProcessor.cleanPackDir(); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/swim/porter/engine/CubeMapper.java b/src/main/java/swim/porter/engine/CubeMapper.java new file mode 100644 index 0000000..e5226ac --- /dev/null +++ b/src/main/java/swim/porter/engine/CubeMapper.java @@ -0,0 +1,41 @@ +package swim.porter.engine; + +import javax.imageio.ImageIO; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +public class CubeMapper { + + public static void CubeMapBuild(BufferedImage skyMap, String cubeMapPath) throws IOException { + int x = skyMap.getWidth() / 3; + int y = skyMap.getHeight() / 2; + // top right + writeCubeMapImage(skyMap.getSubimage(skyMap.getHeight(), 0, x, y), cubeMapPath + "\\cubemap_0.png", false); + // bottom left + writeCubeMapImage(skyMap.getSubimage(0, skyMap.getHeight() / 2, x, y), cubeMapPath + "\\cubemap_1.png", false); + // bottom middle + writeCubeMapImage(skyMap.getSubimage(skyMap.getWidth() / 3, skyMap.getHeight() / 2, x, y), cubeMapPath + "\\cubemap_2.png", false); + // bottom right + writeCubeMapImage(skyMap.getSubimage(skyMap.getHeight(), skyMap.getHeight() / 2, x, y), cubeMapPath + "\\cubemap_3.png", false); + // top middle, does 180 degree flip + writeCubeMapImage(skyMap.getSubimage(skyMap.getWidth() / 3, 0, x, y), cubeMapPath + "\\cubemap_4.png", true); + // top left, does 180 degree flip + writeCubeMapImage(skyMap.getSubimage(0, 0, x, y), cubeMapPath + "\\cubemap_5.png", true); + } + + private static void writeCubeMapImage(BufferedImage image, String filePath, boolean flip) throws IOException { + if (flip) { + AffineTransform tx = AffineTransform.getScaleInstance(-1, -1); + tx.translate(image.getWidth(null) * -1, image.getHeight(null) * -1); + AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); + BufferedImage flippedImage = op.filter(image, null); + ImageIO.write(flippedImage, "png", new File(filePath)); + } else { + ImageIO.write(image, "png", new File(filePath)); + } + } + +} diff --git a/src/main/java/swim/porter/engine/Downloader.java b/src/main/java/swim/porter/engine/Downloader.java new file mode 100644 index 0000000..af26970 --- /dev/null +++ b/src/main/java/swim/porter/engine/Downloader.java @@ -0,0 +1,101 @@ +package swim.porter.engine; + +import org.apache.commons.io.FileUtils; +import org.apache.http.client.utils.URIBuilder; +import org.jsoup.Jsoup; +import org.jsoup.nodes.DataNode; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import swim.porter.Main; + +import java.io.File; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; + +public class Downloader { + + final static String fs = File.separator; + + // returns the path of the downloaded pvprp file + public static File downloadPVPRP(String url) { + try { + assert url != null; + URL checkSite = new URL(url); + HttpURLConnection huc = (HttpURLConnection) checkSite.openConnection(); + int responseCode = huc.getResponseCode(); + if (url.contains("pvprp.com/pack?p=") && responseCode == 200) { + Document document = Jsoup.connect(url).get(); + Elements scriptElements = document.getElementsByTag("script"); + String funct = ""; + for (Element element : scriptElements) { + for (DataNode node : element.dataNodes()) { + funct = node.getWholeData(); + } + } + String[] assetSplit = funct.split("assets/packs"); + assetSplit = assetSplit[1].split("\""); + String downloadURL = assetSplit[0]; + String downloadURLBase = downloadURL.substring(0, downloadURL.indexOf('?')); + String formattedURL = new URIBuilder().setPath("pvprp.com/assets/packs" + downloadURLBase).toString().substring(1); + File portBotDir = new File(Main.portDir); // important + Elements fileName = document.getElementsByClass("f-mc extra-large shadow"); + String name = "placeHolderName"; + for (Element el : fileName) { + name = el.text(); + } + File export = new File(portBotDir + fs + name + ".zip"); + FileUtils.copyURLToFile(new URL("https://" + formattedURL), export, 30000, 30000); + return export; + } else { + System.out.println("Error : Unable to download pack from PVPRP"); + } + } catch (Exception e) { + System.out.println("Error downloading pvprp pack, check the pack for errors and remove any illegal characters in file paths!"); + e.printStackTrace(); + } + return null; + } + + // returns the path of the downloaded mediafire file + public static File downloadMediafire(String originalURL, boolean isRar) { + // we take advantage of corsproxy to bypass mediafire's cloudflare protection + // String url = "https://corsproxy.io/?" + URLEncoder.encode(originalURL, "UTF-8"); // jsoup 1.15.4 or something else random made encoding not needed + String url = "https://corsproxy.io/?" + originalURL; + Document document = null; + try { + document = Jsoup.connect(url).get(); + } catch (IOException e) { + e.printStackTrace(); + } + Elements buttons = document.getElementsByClass("input popsok"); // get a list of the element class we care about + if (!buttons.isEmpty()) { + Element directDownload = document.getElementById("downloadButton"); // can cause an exception at times + assert directDownload != null; + String[] hrefSplit = directDownload.outerHtml().split("href=\""); + String[] href = hrefSplit[1].split("\""); + try { + Elements fileName = document.getElementsByClass("dl-btn-label"); + String name = "placeHolderName"; + for (Element el : fileName) { + name = el.text(); + } + assert originalURL != null; + File portBotDir = new File(Main.portDir); // important + File export = new File(portBotDir + fs + name + ".zip"); + if (isRar) { + export = new File(portBotDir + fs + name + ".rar"); + } + FileUtils.copyURLToFile(new URL(href[0]), export, 30000, 30000); + return export; // return the path of the file we just downloaded + } catch (Exception e) { + e.printStackTrace(); + } + } else { + System.out.println("mediafire link error"); + } + return null; + } + +} diff --git a/src/main/java/swim/porter/engine/FileImageUtils.java b/src/main/java/swim/porter/engine/FileImageUtils.java new file mode 100644 index 0000000..c84374e --- /dev/null +++ b/src/main/java/swim/porter/engine/FileImageUtils.java @@ -0,0 +1,40 @@ +package swim.porter.engine; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +public class FileImageUtils { + + // rescales an image file and writes it instantly to the disk + public static void resizeImageWrite(File originalImagePath, File resizedImagePath, int rescaleWidth, int rescaleHeight, String format) { + try { + if (originalImagePath.exists()) { + BufferedImage original = ImageIO.read(originalImagePath); + BufferedImage resized = Rescale.nearestNeighborRescale(original, rescaleWidth, rescaleHeight); + ImageIO.write(resized, format, resizedImagePath); + } + } catch (IOException e) { + System.out.println("failed to resize image"); + } + } + + // removes the useless alpha pixels in an image that interfere with directX's sprite rendering + public static BufferedImage imageTransparencyFix(BufferedImage raw) { + int WIDTH = raw.getWidth(); + int HEIGHT = raw.getHeight(); + BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_4BYTE_ABGR); + int pixels[] = new int[WIDTH * HEIGHT]; + raw.getRGB(0, 0, WIDTH, HEIGHT, pixels, 0, WIDTH); + for (int i = 0; i < pixels.length; i++) { + int alpha = (pixels[i] & 0xff000000) >>> 24; + if (pixels[i] >= alpha) { + pixels[i] = 0x00ffffff; + } + } + image.setRGB(0, 0, WIDTH, HEIGHT, pixels, 0, WIDTH); + return image; + } + +} diff --git a/src/main/java/swim/porter/engine/Port.java b/src/main/java/swim/porter/engine/Port.java new file mode 100644 index 0000000..f420a48 --- /dev/null +++ b/src/main/java/swim/porter/engine/Port.java @@ -0,0 +1,936 @@ +package swim.porter.engine; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import swim.porter.Main; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.*; +import java.util.*; +import java.util.regex.Matcher; + +public class Port { + + final static String fs = File.separator; + + public static File port(File texturePackFile) { + String packPath = (Main.portDir + fs); + String filePath = texturePackFile.getAbsolutePath(); + boolean failed = false; + String originalDescription = ""; + try { + File mcmeta = new File(texturePackFile + fs + "pack.mcmeta"); + Scanner reader = new Scanner(mcmeta); + // this gets the packs description from the mcmeta file + try { + while (reader.hasNextLine()) { + String data = reader.nextLine(); + if (data.contains("description")) { + String[] splitName = data.split("\"", 4); + if (splitName.length >= 4) { // hopefully this if check prevents [1] + String desc = splitName[3]; // [1] POTENTIALLY HUGE ERROR this causes packs to screw up sometimes when they really shouldn't + originalDescription = desc.replaceAll("\\\\", " "); // filter out escape chars + originalDescription = originalDescription.replaceAll("\"", " "); // filter out quotes + // there could possibly be more escape chars to worry about having to filter, might want to write regex for this + } + } + } + reader.close(); + } catch (Exception e) { + System.out.println("failed to read pack data"); + e.printStackTrace(); + PortFileProcessor.cleanPackDir(); + } + } catch (FileNotFoundException ex) { + System.out.println(" Failed to port: " + filePath + "\n could not locate pack.mcmeta"); + ex.printStackTrace(); + PortFileProcessor.cleanPackDir(); + } + + String rawName = FilenameUtils.removeExtension(texturePackFile.getName()); + String packName = FilenameUtils.getName(rawName + " PORT"); + String description = originalDescription + "\\ndiscord.gg/swim | Ported with Swimfan72's Auto Port "; + + if (!failed) { + String texturePackPath = packPath + packName; + new File(texturePackPath).mkdirs(); // folder of pack + new File(texturePackPath + fs + "textures").mkdirs(); // textures + new File(texturePackPath + fs + "textures" + fs + "ui").mkdirs(); // ui + + createManifest(packName, rawName, description, packPath); // creates the manifest for the ported pack + icon(texturePackFile, texturePackPath); // copies over the pack icon + textures(texturePackFile, texturePackPath); // copies over textures folder + blockItemFix(texturePackPath); // renames block directory to blocks if needed, same for items + lowerCaseAll(new File(texturePackPath)); // lower cases all file and directory names recursively in the pack + armor(texturePackPath); // ports over the armor for the pack + painting(texturePackPath); // copies and renames over the paintings + colorMap(texturePackFile, texturePackPath); // copies over the colormap + font(texturePackFile, texturePackPath); // folder move + rename ascii to default8 + gui(texturePackPath); + fire(texturePackPath); // copies over the fire sprite sheets + environment(texturePackFile, texturePackPath); // copies over the environment folder + sky(texturePackFile, texturePackPath); // ports the java sky to a cubemap + xp(texturePackPath); // ports over the xp bar from the gui + guiFix(texturePackPath); // resizes accordingly + crossHairFix(texturePackPath); // gets the crosshair from the gui and cleans the background + chestFix(texturePackPath); // renames chests + potionEffectsUI(texturePackPath); // extracts out the potion effect UI + panorama(texturePackFile, texturePackPath); // if possible set a menu background for the pack + sounds(texturePackFile, texturePackPath); // copy over the sounds files + itemsFix(texturePackPath); // delete the weird alpha pixels from every item + potions(texturePackPath); // iteratively create the potions + containerUI(texturePackFile, packPath, packName); // ports containers UI + grassSide(texturePackPath); // tga handling (broken) + mobileButtons(texturePackPath); // creates default mobile buttons for the pack + + // finally we will need to zip the pack and turn its file extension to a .mcpack + File mcpack = new File(texturePackPath + ".mcpack"); + finalizePort(texturePackPath); + return (mcpack); + } + return null; + } + + private static void createManifest(String PackName, String rawName, String description, String packPath) { + try { + FileWriter manifest = new FileWriter(packPath + PackName + fs + "manifest.json"); + // create the UUIDs for the manifest file + String uuid = UUID.randomUUID().toString(); + String uuid2 = UUID.randomUUID().toString(); + manifest.write("{\n"); + manifest.write(" \"format_version\": 1,\n"); + manifest.write(" \"header\": {\n"); + manifest.write(" \"description\": \"" + description + "\",\n"); + manifest.write(" \"name\": \"" + rawName + "\",\n"); + manifest.write(" \"uuid\": \"" + uuid + "\",\n"); + manifest.write(" \"version\": [1, 0, 0],\n"); + manifest.write(" \"min_engine_version\": [1, 12, 1]\n"); + manifest.write(" },\n"); + manifest.write(" \"modules\": [\n"); + manifest.write(" {\n"); + manifest.write(" \"description\": \"" + description + "\",\n"); + manifest.write(" \"type\": \"resources\",\n"); + manifest.write(" \"uuid\": \"" + uuid2 + "\",\n"); + manifest.write(" \"version\": [1, 0, 0]\n"); + manifest.write(" }\n"); + manifest.write(" ]\n"); + manifest.write("}\n"); + manifest.close(); + } catch (IOException e) { + System.out.println("An error occurred creating the manifest.json"); + e.printStackTrace(); + PortFileProcessor.cleanPackDir(); + } + } + + private static void guiFix(String packPath) { + try { + File gui = new File(packPath + fs + "textures" + fs + "gui" + fs + "gui.png"); + if (gui.exists()) { + BufferedImage guiIMG = ImageIO.read(gui); + if (guiIMG.getHeight() != 256) { + FileImageUtils.resizeImageWrite(gui, gui, 256, 256, "png"); + } + } + } catch (Exception e) { + System.out.println("failed to scale gui"); + } + } + + private static void icon(File file, String packPath) { + File source = new File(file + fs + "pack.png"); + if (source.exists()) { + File dest = new File(packPath + fs + "pack.png"); + try { + FileUtils.moveFile(source, dest); // was originally copy + File newIconName = new File(packPath + fs + "pack.png"); + File fixedName = new File(packPath + fs + "pack_icon.png"); + newIconName.renameTo(fixedName); + } catch (IOException e) { + System.out.println("icon error " + e.getMessage()); + } + } + } + + private static void chestFix(String packPath) { + File chestOld = new File(packPath + fs + "textures" + fs + "entity" + fs + "chest" + fs + "normal_double.png"); + if (chestOld.exists()) { + File chestNew = new File(packPath + fs + "textures" + fs + "entity" + fs + "chest" + fs + "double_normal.png"); + try { + chestOld.renameTo(chestNew); + } catch (Exception e) { + System.out.println("failed to port chests: " + e.getMessage()); + } + } + } + + private static void textures(File file, String packPath) { + File source = new File(file + fs + "assets" + fs + "minecraft" + fs + "textures"); + if (source.exists()) { + File dest = new File(packPath + fs + "textures"); + try { + FileUtils.copyDirectory(source, dest); + //FileUtils.moveDirectory(source, dest); + } catch (IOException e) { + e.printStackTrace(); + System.out.println("failed to port textures: " + e.getMessage()); + } + } + } + + private static void mobileButtons(String packPath) { + try { + File guiURL = new File(packPath + fs + "textures" + fs + "gui" + fs + "gui.png"); + if (guiURL.exists()) { + File mobileURL = new File(Main.portDir + fs + "assets" + fs + "mobile" + fs + "mobile_buttons.png"); + BufferedImage guiOriginal = ImageIO.read(guiURL); + BufferedImage mobileImage = ImageIO.read(mobileURL); + Graphics g = guiOriginal.getGraphics(); + g.drawImage(mobileImage, 0, 0, null); + g.dispose(); + ImageIO.write(guiOriginal, "png", guiURL); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void grassSide(String packPath) { + // we can only do this if we have the dirt block in the pack + File dirt = new File(packPath + fs + "textures" + fs + "blocks" + fs + "dirt.png"); + if (dirt.exists()) { + // get the grass side overlay + File grassSide = new File(packPath + fs + "textures" + fs + "blocks" + fs + "grass_side_overlay.png"); + // we can only do this if grass side exists + if (grassSide.exists()) { + // make the grass side overlay + sideOverlayTGA(packPath + fs + "textures" + fs + "blocks" + fs + "grass_side.tga", grassSide, dirt); + // remove the old one + File grass_side = new File(packPath + fs + "textures" + fs + "blocks" + fs + "grass_side.png"); + if (grass_side.exists()) { + grass_side.delete(); + } + // make the snow side overlay + sideOverlayTGA(packPath + fs + "textures" + fs + "blocks" + fs + "grass_side_snow.tga", grassSide, dirt); + // remove the old one + File snowSide = new File(packPath + fs + "textures" + fs + "blocks" + fs + "grass_side_snowed.png"); + if (snowSide.exists()) { + snowSide.delete(); + } + } + } + } + + private static void sideOverlayTGA(String exportPath, File overlay, File base) { + try { + BufferedImage overlayImage = ImageIO.read(overlay); + BufferedImage baseImage = ImageIO.read(base); + if (overlayImage != null && baseImage != null) { + int width = baseImage.getWidth(); + int height = baseImage.getHeight(); + // draw the images on top of each other with the baseImage having 50 alpha value + BufferedImage canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = canvas.createGraphics(); + // Set the alpha value for the base image to be 20% opaque + graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.2f)); + graphics.drawImage(baseImage, 0, 0, null); + // Scale and draw the grass overlay image on top of the base image + BufferedImage scaledGrassSideImage = Rescale.nearestNeighborRescale(overlayImage, width, height); + graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); // set transparency back to full + graphics.drawImage(scaledGrassSideImage, 0, 0, null); + // now write it to Targa format + File outputImageFile = new File(exportPath); + ImageIO.write(canvas, "tga", outputImageFile); // 12monkeys dependency should handle this + graphics.dispose(); // close resources after use! + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + // works recursively + private static void lowerCaseAll(File master) { + try { + File[] directoryListing = master.listFiles(); + if (directoryListing != null) { + for (File currentFile : directoryListing) { + String lowerCasedName = FilenameUtils.getName(currentFile.toString().toLowerCase()); + File newName = new File(currentFile.getParentFile() + fs + lowerCasedName); + currentFile.renameTo(newName); + if (currentFile.isDirectory()) { + lowerCaseAll(currentFile); + } + } + } + } catch (Exception e) { + System.out.println("failed to lower case files: " + e.getMessage()); + } + } + + private static void blockItemFix(String packPath) { + try { + File blockPath = new File(packPath + fs + "textures" + fs + "block" + fs); + File blockPathFixed = new File(packPath + fs + "textures" + fs + "blocks" + fs); + if (blockPath.exists()) { + blockPath.renameTo(blockPathFixed); + } + File itemPath = new File(packPath + fs + "textures" + fs + "item" + fs); + File itemPathFixed = new File(packPath + fs + "textures" + fs + "items" + fs); + if (itemPath.exists()) { + itemPath.renameTo(itemPathFixed); + } + } catch (Exception e) { + e.printStackTrace(); + System.out.println("directory renaming not required for this pack?"); + } + } + + private static void potionEffectsUI(String packPath) { + try { + File inventory = new File(packPath + fs + "textures" + fs + "gui" + fs + "container" + fs + "inventory.png"); + if (inventory.exists()) { + String path = packPath + fs + "textures" + fs + "ui" + fs; + BufferedImage spriteSheet = ImageIO.read(inventory); + double sin = spriteSheet.getHeight() / 4.41379310345; + int epilsonFlat = (int) Math.round(sin); + int startingY = spriteSheet.getHeight() - epilsonFlat; + double hypo = spriteSheet.getHeight() / 14.2222222222; + int cellChangeFactor = (int) Math.round(hypo); + String[] effects = {"speed", "slowness", "haste", "mining_fatigue", "strength", "weakness", "poison", "regeneration", "invisibility", "saturation", "jump_boost", "nausea", "night_vision", "blindness", "resistance", "fire_resistance", "water_breathing", "wither", "absorption"}; + // now that we have all our variables, loop through and sub image + int sheetRow = 0; + int x; + for (int i = 0; i < 19; i++) { + if (i % 8 == 0 && i != 0) { + x = 0; + sheetRow = 1; + startingY = startingY + cellChangeFactor; + } else { + x = sheetRow * cellChangeFactor; + sheetRow++; + } + ImageIO.write(spriteSheet.getSubimage(x, startingY, cellChangeFactor, cellChangeFactor), "png", new File(path + effects[i] + "_effect.png")); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static final Map PotionColorMap = new HashMap<>(); + + static { + PotionColorMap.put("blindness", Color.darkGray); + PotionColorMap.put("damageBoost", new Color(139, 0, 0)); + PotionColorMap.put("fireResistance", Color.orange); + PotionColorMap.put("harm", new Color(139, 0, 0)); + PotionColorMap.put("heal", Color.RED); + PotionColorMap.put("invisibility", Color.lightGray); + PotionColorMap.put("jump", Color.green); + PotionColorMap.put("luck", Color.GREEN); + PotionColorMap.put("moveSlowdown", Color.lightGray); + PotionColorMap.put("moveSpeed", Color.CYAN); + PotionColorMap.put("nightVision", Color.blue); + PotionColorMap.put("poison", Color.green); + PotionColorMap.put("regeneration", Color.PINK); + PotionColorMap.put("slowFall", Color.LIGHT_GRAY); + PotionColorMap.put("turtleMaster", Color.lightGray); + PotionColorMap.put("waterBreathing", Color.lightGray); + PotionColorMap.put("weakness", Color.black); + PotionColorMap.put("haste", Color.yellow); + PotionColorMap.put("wither", new Color(61, 43, 31)); + } + + private static void tintPots(String packPath, File potBlank, File overlay, boolean splash) { + try { + BufferedImage overImage = ImageIO.read(overlay); + BufferedImage sourceImage = ImageIO.read(potBlank); + // iterate the color map for each potion + for (Map.Entry entry : PotionColorMap.entrySet()) { + String effect = entry.getKey(); + Color color = entry.getValue(); + // we need to copy in a new image buffer + BufferedImage source = new BufferedImage(sourceImage.getColorModel(), sourceImage.copyData(null), sourceImage.isAlphaPremultiplied(), null); + BufferedImage over = new BufferedImage(overImage.getColorModel(), overImage.copyData(null), overImage.isAlphaPremultiplied(), null); + // tint it and draw it in graphics image overlayed on top + over = Recolor.tint(over, color); + Graphics g = source.getGraphics(); + g.drawImage(over, 0, 0, null); + g.dispose(); + // write to disk + File path = new File(packPath + fs + "textures" + fs + "items" + fs + "potion_bottle_" + (splash ? "splash_" : "") + effect + ".png"); + ImageIO.write(source, "png", path); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void potions(String packPath) { + try { + File drinkBlank = new File(packPath + fs + "textures" + fs + "items" + fs + "potion_bottle_drinkable.png"); + File splashBlank = new File(packPath + fs + "textures" + fs + "items" + fs + "potion_bottle_splash.png"); + File overlay = new File(packPath + fs + "textures" + fs + "items" + fs + "potion_overlay.png"); + // check if our overlay png exists + if (overlay.exists()) { + // tint the drink pots + if (drinkBlank.exists()) { + BufferedImage overlayCompare = ImageIO.read(overlay); + BufferedImage drinkCompare = ImageIO.read(drinkBlank); + if (overlayCompare.getHeight() != drinkCompare.getHeight()) { + // correcting the potion overlay size (drink) + FileImageUtils.resizeImageWrite(overlay, overlay, drinkCompare.getHeight(), drinkCompare.getWidth(), "png"); + } + tintPots(packPath, drinkBlank, overlay, false); + } + // tint the splash pots + if (splashBlank.exists()) { + BufferedImage overlayCompare = ImageIO.read(overlay); + BufferedImage splashCompare = ImageIO.read(splashBlank); + if (overlayCompare.getHeight() != splashCompare.getHeight()) { + // correcting the potion overlay size (splash) + FileImageUtils.resizeImageWrite(overlay, overlay, splashCompare.getWidth(), splashCompare.getHeight(), "png"); + } + tintPots(packPath, splashBlank, overlay, true); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void itemsFix(String packPath) { + try { + File items = new File(packPath + fs + "textures" + fs + "items"); + if (items.exists()) { + String[] images = items.list(); + assert images != null; + for (String currentImage : images) { + try { + if (currentImage.toLowerCase().endsWith(".png")) { + File img = new File(packPath + fs + "textures" + fs + "items" + fs + currentImage); + BufferedImage item = ImageIO.read(img); + BufferedImage clean = FileImageUtils.imageTransparencyFix(item); + ImageIO.write(clean, "png", img); + } + } catch (Exception ex) { + ex.printStackTrace(); + System.out.println("failed to fix image " + currentImage + " (might not be a .png file?)"); + } + } + } + } catch (Exception e) { + System.out.println("failed to find items folder"); + } + } + + private static void xp(String packPath) { + try { + String path = packPath + fs + "textures" + fs + "ui" + fs; + File iconsFile = new File(packPath + fs + "textures" + fs + "gui" + fs + "icons.png"); + if (iconsFile.exists()) { + BufferedImage icons = ImageIO.read(iconsFile); + int x = 0; + int y = icons.getHeight() / 4; + double padding = icons.getWidth() / 3.45945945946; // calculate extra space between the xp bar and img width + int extra = (int) Math.round(padding); // round it to a whole integer + int xpBarLength = icons.getWidth() - extra; // subtract the extra white space to find the xp bar's length + // 256x256 is probably the smallest icons.png can be + int xpBarWidth = 5 * (icons.getWidth() / 256); // height doubles as a multiple of 5, factor of width + + ImageIO.write(icons.getSubimage(x, y, xpBarLength, xpBarWidth), "png", new File(path + "experiencebarempty.png")); + ImageIO.write(icons.getSubimage(x, y + xpBarWidth, xpBarLength, xpBarWidth), "png", new File(path + "experiencebarfull.png")); + + File source = new File(packPath + fs + "textures" + fs + "ui"); + File dest = new File(packPath + fs + "textures" + fs + "gui" + fs + "achievements"); + + FileUtils.copyDirectory(source, dest); + File xpBarEmpty2 = new File(packPath + fs + "textures" + fs + "gui" + fs + "achievements" + fs + "experiencebarempty.png"); + File xpBarFull2 = new File(packPath + fs + "textures" + fs + "gui" + fs + "achievements" + fs + "experiencebarfull.png"); + + FileUtils.copyFile(xpBarEmpty2, new File(packPath + fs + "textures" + fs + "ui" + fs + "empty_progress_bar.png")); // new + FileUtils.copyFile(xpBarFull2, new File(packPath + fs + "textures" + fs + "ui" + fs + "filled_progress_bar.png")); // new + + File hotdogempty = new File(packPath + fs + "textures" + fs + "gui" + fs + "achievements" + fs + "hotdogempty.png"); + File hotdogfull = new File(packPath + fs + "textures" + fs + "gui" + fs + "achievements" + fs + "hotdogfull.png"); + + xpBarEmpty2.renameTo(hotdogempty); + xpBarFull2.renameTo(hotdogfull); + + // now we need to put in notnub.png and nub.png into the achievements folder + BufferedImage nubImage = ImageIO.read(new File(Main.portDir + fs + "assets" + fs + "nub" + fs + "nub.png")); + File nubPath = new File(packPath + fs + "textures" + fs + "gui" + fs + "achievements" + fs + "nub.png"); + + BufferedImage experienceNubImage = ImageIO.read(new File + (Main.portDir + fs + "assets" + fs + "nub" + fs + "experiencenub.png")); + File xpNubPath = new File(packPath + fs + "textures" + fs + "ui" + fs + "experiencenub.png"); + + BufferedImage experienceBarBlueNubImage = ImageIO.read(new File(Main.portDir + fs + "assets" + fs + "nub" + fs + "experience_bar_nub_blue.png")); + File xpBarNubPath = new File(packPath + fs + "textures" + fs + "ui" + fs + "experience_bar_nub_blue.png"); + if (new File(packPath + fs + "textures" + fs + "gui" + fs + "achievements").exists()) { + ImageIO.write(nubImage, "png", nubPath); + ImageIO.write(experienceNubImage, "png", xpNubPath); + ImageIO.write(experienceBarBlueNubImage, "png", xpBarNubPath); + } + + // a ghost achievements folder gets generated *sometimes* so I guess just delete it + File ghost = new File(packPath + fs + "textures" + fs + "gui" + fs + "achievements" + fs + "achievements"); + if (ghost.exists()) { + ghost.delete(); + } + // now we need to make the xp bar json files + // which will be done in barJson(); + barJson(packPath); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void barJson(String packPath) { + try { + FileWriter experiencebarempty = new FileWriter(packPath + fs + "textures" + fs + "ui" + fs + "experiencebarempty.json"); + experiencebarempty.write("{\n"); + experiencebarempty.write(" \"nineslice_size\": [\n"); + experiencebarempty.write(" 6,\n"); + experiencebarempty.write(" 1,\n"); + experiencebarempty.write(" 6,\n"); + experiencebarempty.write(" 1\n"); + experiencebarempty.write(" ],\n"); + experiencebarempty.write(" \"base_size\": [\n"); + experiencebarempty.write(" " + 182 + ",\n"); + experiencebarempty.write(" " + 5 + "\n"); + experiencebarempty.write(" ]\n"); + experiencebarempty.write("}\n"); + experiencebarempty.close(); + } catch (IOException e) { + // System.out.println("An error occurred creating the experiencebarempty.json"); + } + try { + FileWriter experiencebarfull = new FileWriter(packPath + fs + "textures" + fs + "ui" + fs + "experiencebarfull.json"); + experiencebarfull.write("{\n"); + experiencebarfull.write(" \"nineslice_size\": [\n"); + experiencebarfull.write(" 1,\n"); + experiencebarfull.write(" 0,\n"); + experiencebarfull.write(" 1,\n"); + experiencebarfull.write(" 0\n"); + experiencebarfull.write(" ],\n"); + experiencebarfull.write(" \"base_size\": [\n"); + experiencebarfull.write(" " + 182 + ",\n"); + experiencebarfull.write(" " + 5 + "\n"); + experiencebarfull.write(" ]\n"); + experiencebarfull.write("}\n"); + experiencebarfull.close(); + // new + File xpJSON = new File(packPath + fs + "textures" + fs + "ui" + fs + "experiencebarfull.json"); + FileUtils.copyFile(xpJSON, new File(packPath + fs + "textures" + fs + "gui" + fs + "achievements" + fs + "hotdogempty.json")); + FileUtils.copyFile(xpJSON, new File(packPath + fs + "textures" + fs + "gui" + fs + "achievements" + fs + "hotdogfull.json")); + FileUtils.copyFile(xpJSON, new File(packPath + fs + "textures" + fs + "ui" + fs + "empty_progress_bar.json")); + FileUtils.copyFile(xpJSON, new File(packPath + fs + "textures" + fs + "ui" + fs + "filled_progress_bar.json")); + } catch (IOException e) { + e.printStackTrace(); + System.out.println("An error occurred creating the experiencebarfull.json"); + } + } + + private static void sky(File file, String packPath) { + String[] skyboxNames = {"cloud1", "cloud2", "starfield03", "starfield", "skybox", "skybox2"}; + BufferedImage skyMap = null; + for (String skyboxName : skyboxNames) { + File skyPath = new File(file + fs + "assets" + fs + "minecraft" + fs + "mcpatcher" + fs + "sky" + fs + "world0" + fs + skyboxName + ".png"); + if (skyPath.exists()) { + try { + skyMap = ImageIO.read(skyPath); + break; + } catch (Exception e) { + e.printStackTrace(); + } + } + } + // hopefully that managed to grab a sky box + if (skyMap != null) { + String path = packPath + fs + "textures" + fs + "environment" + fs + "overworld_cubemap" + fs; + try { + CubeMapper.CubeMapBuild(skyMap, path); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private static void panorama(File file, String packPath) { + try { + File panorama = new File(file + fs + "assets" + fs + "minecraft" + fs + "textures" + fs + "gui" + fs + "title" + fs + "background" + fs); + if (panorama.exists()) { + FileUtils.moveDirectoryToDirectory(panorama, new File(packPath + fs + "textures" + fs + "gui" + fs), true); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void sounds(File file, String packPath) { + try { + File soundsDir = new File(file + fs + "assets" + fs + "minecraft" + fs + "sounds"); + if (soundsDir.exists()) { + File dest = new File(packPath); + // FileUtils.copyDirectoryToDirectory(soundsDir, dest); + FileUtils.moveDirectoryToDirectory(soundsDir, dest, true); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static HashMap armorNames = new HashMap<>(); + + static { + armorNames.put("diamond_layer_1", "diamond_1"); + armorNames.put("diamond_layer_2", "diamond_2"); + armorNames.put("chainmail_layer_1", "chain_1"); + armorNames.put("chainmail_layer_2", "chain_2"); + armorNames.put("gold_layer_1", "gold_1"); + armorNames.put("gold_layer_2", "gold_2"); + armorNames.put("iron_layer_1", "iron_1"); + armorNames.put("iron_layer_2", "iron_2"); + armorNames.put("leather_layer_1", "cloth_1"); + armorNames.put("leather_layer_2", "cloth_2"); + } + + private static void armor(String packPath) { + String path = packPath + fs + "textures" + fs + "models" + fs + "armor" + fs; + if (new File(path).exists()) { + for (Map.Entry entry : armorNames.entrySet()) { + try { + File original = new File(path + entry.getKey() + ".png"); + if (original.exists()) { + File newName = new File(path + entry.getValue() + ".png"); + original.renameTo(newName); + } + } catch (Exception e) { + System.out.println("error renaming armor"); + } + } + } + } + + private static void colorMap(File file, String packPath) { + try { + File source = new File(file + fs + "assets" + fs + "minecraft" + fs + "mcpatcher" + fs + "colormap"); + if (source.exists()) { + File dest = new File(packPath + fs + "textures" + fs + "colormap"); + FileUtils.moveDirectory(source, dest); // was originally copy + } + } catch (Exception e) { + System.out.println("failed to locate colormap"); + } + } + + private static void environment(File file, String packPath) { + try { + new File(packPath + fs + "textures" + fs + "environment" + fs + "overworld_cubemap").mkdirs(); + for (int i = 0; i < 10; i++) { + File source = new File(file + fs + "assets" + fs + "minecraft" + fs + "textures" + fs + "blocks" + fs + "destroy_stage_" + i + ".png"); + if (source.exists()) { + File dest = new File(packPath + fs + "textures" + fs + "environment" + fs + "destroy_stage_" + i + ".png"); + FileUtils.moveFile(source, dest); // was originally copy + } + } + } catch (Exception e) { + System.out.println("failed to port environment folder"); + } + } + + private static void font(File file, String packPath) { + try { + File source1 = new File(file + fs + "assets" + fs + "minecraft" + fs + "mcpatcher" + fs + "font"); // copy the font + File source2 = new File(file + fs + "assets" + fs + "minecraft" + fs + "font"); + File source3 = new File(file + fs + "assets" + fs + "minecraft" + fs + "textures" + fs + "font"); + File dest = new File(packPath + fs + "font"); + // refactored this greatly to use move instead of copy + if (source1.exists()) { + FileUtils.moveDirectory(source1, dest); + } else if (source2.exists()) { + FileUtils.moveDirectory(source2, dest); + } else if (source3.exists()) { + FileUtils.moveDirectory(source3, dest); + } + // now rename the font from ascii to default8, so it works on bedrock + String path = dest + fs; + File original = new File(path + "ascii.png"); + File newName = new File(path + "default8.png"); + original.renameTo(newName); + } catch (Exception e) { + e.printStackTrace(); + System.out.println("failed to port font"); + } + } + + private static void gui(String packPath) { + try { + String path = packPath + fs + "textures" + fs + "gui" + fs; + File original = new File(path + "widgets.png"); + if (original.exists()) { + File newName = new File(path + "gui.png"); + original.renameTo(newName); + } + } catch (Exception e) { + e.printStackTrace(); + System.out.println("failed to port gui"); + } + } + + private static void crossHairFix(String packPath) { + try { + String path = packPath + fs + "textures" + fs + "gui" + fs; + File icons = new File(path + "icons.png"); + if (icons.exists()) { + BufferedImage readIcons = ImageIO.read(icons); + // crosshair boxes are base 16 according to icons.png size + int crossHairSize = readIcons.getWidth() / 16; + // sub image out the crosshair from the top left of icons.png + BufferedImage crosshair = readIcons.getSubimage(0, 0, crossHairSize, crossHairSize); + // create a canvas of the crosshair size + BufferedImage canvas = new BufferedImage(crossHairSize, crossHairSize, BufferedImage.TYPE_INT_ARGB); + Graphics g = canvas.getGraphics(); + // fill the canvas with a black rectangle + g.setColor(Color.black); + g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight()); + // now draw the crosshair + g.drawImage(crosshair, 0, 0, null); + g.dispose(); + ImageIO.write(canvas, "png", new File(packPath + fs + "textures" + fs + "ui" + fs + "cross_hair.png")); + } + } catch (Exception e) { + e.printStackTrace(); + System.out.println("failed to port cross hair"); + } + } + + private static void fire(String packPath) { + try { + String path = packPath + fs + "textures" + fs + "blocks" + fs; + File original = new File(path + "fire_layer_1.png"); + if (original.exists()) { + File newName = new File(path + "fire_1.png"); + original.renameTo(newName); + } + File original2 = new File(path + "fire_layer_0.png"); + if (original2.exists()) { + File newName2 = new File(path + "fire_0.png"); + original2.renameTo(newName2); + } + } catch (Exception e) { + e.printStackTrace(); + System.out.println("failed to port fire"); + } + } + + private static void painting(String packPath) { + try { + String path = packPath + fs + "textures" + fs + "painting" + fs; + File original = new File(path + "paintings_kristoffer_zetterstrand.png"); + if (original.exists()) { + File newName = new File(path + "kz.png"); + original.renameTo(newName); + } + } catch (Exception e) { + e.printStackTrace(); + System.out.println("failed to port painting"); + } + } + + // entire gui container UI port as well (chests and that stuff) + // a lot of potentially redundant code, could maybe get be improved + // this part of the process is fairly slow due to having to copy over folders from assets + private static void containerUI(File file, String packPath, String packName) { + String packPathFull = packPath + packName; + File checkInventory = new File(packPathFull + fs + "textures" + fs + "gui" + fs + "container" + fs + "inventory.png"); + File checkChest = new File(packPathFull + fs + "textures" + fs + "entity" + fs + "chest" + fs + "double_normal.png"); + // we only port containers if we know an inventory or a chest in the pack exists + if (checkInventory.exists() || checkChest.exists()) { + try { + String path = packPathFull + fs; + // First thing to do is to put in the UI folder + Path destUI = Paths.get(path.replaceAll("\\\\|\\/", Matcher.quoteReplacement(File.separator))); + File UI = new File(Main.portDir + fs + "assets" + fs + "ui"); + FileUtils.copyDirectoryToDirectory(UI, new File(destUI.toString())); + // uidx + Path destUIDX = destUI; + File UIDX = new File(Main.portDir + fs + "assets" + fs + "uidx"); + FileUtils.copyDirectoryToDirectory(UIDX, new File(destUIDX.toString())); + // uidx textures folder + Path texturesUIDXdest = Paths.get(path.replaceAll("\\\\|\\/", Matcher.quoteReplacement(File.separator)) + "textures"); + File texturesUIDX = new File(Main.portDir + fs + "assets" + fs + "textures_uidx" + fs + "uidx"); + FileUtils.copyDirectoryToDirectory(texturesUIDX, new File(texturesUIDXdest.toString())); + // java assets + File assets = new File(file + fs + "assets" + fs + "minecraft" + fs + "textures" + fs + "gui" + fs + "container"); + File assetsDest = new File(packPathFull + fs + "assets" + fs + "minecraft" + fs + "textures" + fs + "gui" + fs + "container" + fs); + try { + FileUtils.copyDirectory(assets, assetsDest); + } catch (Exception ex) { + // System.out.println("pack being ported does not have a container"); + } + // recipe book + File recipeBook = new File(Main.portDir + fs + "assets" + fs + "recipe_book"); + FileUtils.copyDirectoryToDirectory(recipeBook, new File(path + "assets/uidx/textures/gui/container")); + FileUtils.copyDirectoryToDirectory(recipeBook, new File(texturesUIDXdest.toString())); + // container + File containerDest = new File(packPathFull + fs + "assets" + fs + "minecraft" + fs + "textures" + fs + "gui" + fs + "container" + fs); + File container = new File(file + fs + "assets" + fs + "minecraft" + fs + "textures" + fs + "gui" + fs + "container"); + try { + FileUtils.copyDirectory(container, containerDest); + } catch (Exception ex) { + // System.out.println("pack being ported does not have a container for assets"); + } + // chest and end chest + File generic54 = new File(packPathFull + fs + "assets" + fs + "minecraft" + fs + "textures" + fs + "gui" + fs + "container" + fs + "generic_54.png"); + File newGeneric = new File(packPathFull + fs + "assets" + fs + "uidx" + fs + "textures" + fs + "gui" + fs + "container" + fs + "generic_54.png"); + + try { + FileUtils.copyFile(generic54, newGeneric); + File renameEndChest = new File(packPathFull + fs + "assets" + fs + "uidx" + fs + "textures" + fs + "gui" + fs + "container" + fs + "generic_54.png"); + File ender_chest = new File(packPathFull + fs + "assets" + fs + "uidx" + fs + "textures" + fs + "gui" + fs + "container" + fs + "ender_chest.png"); + renameEndChest.renameTo(ender_chest); + } catch (Exception e) { + // System.out.println("generic54.png does not exist"); + } + + try { + FileUtils.copyFile(generic54, newGeneric); + File renameChest = new File(packPathFull + fs + "assets" + fs + "uidx" + fs + "textures" + fs + "gui" + fs + "container" + fs + "generic_54.png"); + File small_chest = new File(packPathFull + fs + "assets" + fs + "uidx" + fs + "textures" + fs + "gui" + fs + "container" + fs + "small_chest.png"); + renameChest.renameTo(small_chest); + } catch (Exception e) { + // System.out.println("generic54.png does not exist"); + } + + File inventoryCreativeCheck = new File(packPathFull + fs + "assets" + fs + "minecraft" + fs + "textures" + fs + "gui" + fs + "container" + fs + "creative_inventory"); + if (!inventoryCreativeCheck.exists()) { + File creative = new File(Main.portDir + fs + "assets" + fs + "container" + fs + "creative_inventory"); + FileUtils.copyDirectoryToDirectory(creative, inventoryCreativeCheck.getParentFile()); + } + + try { + // handle this later in check catch if it fails + FileUtils.copyFile(generic54, newGeneric); + File renameChest = new File(packPathFull + fs + "assets" + fs + "uidx" + fs + "textures" + fs + "gui" + fs + "container" + fs + "generic_54.png"); + File small_chest = new File(packPathFull + fs + "assets" + fs + "uidx" + fs + "textures" + fs + "gui" + fs + "container" + fs + "small_chest.png"); + renameChest.renameTo(small_chest); + } catch (Exception e) { + // System.out.println("can not find generic54, copying over default container assets"); + File containerDir = new File(Main.portDir + fs + "assets" + fs + "container" + fs); + new File(Main.portDir + fs + packName + fs + "tempIMGS").mkdirs(); + File tempIMGS = new File(Main.portDir + fs + packName + fs + "tempIMGS"); + FileUtils.copyDirectoryToDirectory(containerDir, tempIMGS); + File temps = new File(tempIMGS + fs + "container"); + File[] imgs = temps.listFiles(); + for (File currentGUI : imgs) { + File checker = new File(packPathFull + fs + "assets" + fs + "minecraft" + fs + "textures" + fs + "gui" + fs + "container" + fs + currentGUI.getName()); + if (!checker.exists() && !currentGUI.isDirectory()) { + // System.out.println("Importing default container image " + currentGUI); + try { + FileUtils.copyFile(currentGUI, checker); + } catch (Exception ex) { + // System.out.println("save location does not exist (no container)"); + } + } + } + try { + // small_chest handling from here + File uidxContainer = new File(packPathFull + fs + "assets" + fs + "uidx" + fs + "textures" + fs + "gui" + fs + "container"); + File generic = new File(packPathFull + fs + "assets" + fs + "minecraft" + fs + "textures" + fs + "gui" + fs + "container" + fs + "generic_54.png"); + File genDest = new File(uidxContainer + fs + "generic_54.png"); + FileUtils.copyFile(generic, genDest); + File smallChest = new File(uidxContainer + fs + "small_chest.png"); + genDest.renameTo(smallChest); + FileUtils.copyFile(generic, genDest); + File eChest = new File(uidxContainer + fs + "ender_chest.png"); + genDest.renameTo(eChest); + FileUtils.copyFile(generic, genDest); + } catch (Exception e1) { + // System.out.println("failed to port small_chest.png"); + } + } + } catch (Exception e) { + System.out.println("failed to port container"); + } + + // scale method via global_variables.json editing + try { + Path global_variables = Paths.get(packPathFull + fs + "ui" + fs + "_global_variables.json"); + Charset charset = StandardCharsets.UTF_8; + String content = Files.readString(global_variables, charset); + String[] containerNames = {"inventory", "generic_54", "brewing_stand", "crafting_table", "furnace", "blast_furnace", "smoker", "enchanting_table", "anvil", "hopper", "dispenser", "beacon", "horse"}; + // iterate through the containers and fill in the resolutions + for (String containerName : containerNames) { + String currentName = containerName; // this is used for file look up only + // blast furnace and smoker share the furnace UI + if (containerName.equals("blast_furnace") || containerName.equals("smoker")) { + currentName = "furnace"; + } + File containerFile = new File(packPathFull + fs + "assets" + fs + "minecraft" + fs + "textures" + fs + "gui" + fs + "container" + fs + currentName + ".png"); // file look up + if (containerFile.exists()) { + BufferedImage containerImage = ImageIO.read(containerFile); + int height = containerImage.getHeight(); + int width = containerImage.getWidth(); + // System.out.println(containerName + " | width: " + width + " | height: " + height); + int dimension = height; + // Valid height and width values + int[] validDimensions = {256, 512, 1024, 2048, 4096, 8192}; + // Check if the height and width are valid dimensions + boolean validHeight = Arrays.stream(validDimensions).anyMatch(x -> x == height); + boolean validWidth = Arrays.stream(validDimensions).anyMatch(x -> x == width); + // If height and width are not equal or not valid, rescale to the closest valid width value + if (height != width || !validHeight || !validWidth) { + dimension = findClosestDimension(height, width, validDimensions); + // System.out.println(containerName + " | " + dimension); + FileImageUtils.resizeImageWrite(containerFile, containerFile, dimension, dimension, "png"); + } + content = content.replaceAll("\"\\$" + containerName + "_resolution\": \"256x\",", "\"\\$" + containerName + "_resolution\"\\: \"" + dimension + "x\","); + } + } + // once we have updated the content we can write its new data to the file + Files.writeString(global_variables, content, charset); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private static int findClosestDimension(int height, int width, int[] validDimensions) { + int averageDimension = (height + width) / 2; + int closestDimension = validDimensions[0]; + int closestDifference = Math.abs(validDimensions[0] - averageDimension); + + for (int dimension : validDimensions) { + int difference = Math.abs(dimension - averageDimension); + if (difference < closestDifference) { + closestDimension = dimension; + closestDifference = difference; + } + } + + return closestDimension; + } + + // compresses to an mcpack file + private static void finalizePort(String packPath) { + try { + File pack = new File(packPath); + ZipUtil.LightZip(pack.getAbsolutePath()); // this copies the file to a zip + File zippedPack = new File(packPath + ".zip"); + File mcpack = new File(packPath + ".mcpack"); + zippedPack.renameTo(mcpack); + System.out.println(" Successfully Ported Pack: \n " + mcpack); + } catch (Exception e) { + System.out.println("failed in final port stage clean up"); + } + } + +} diff --git a/src/main/java/swim/porter/engine/PortFileProcessor.java b/src/main/java/swim/porter/engine/PortFileProcessor.java new file mode 100644 index 0000000..9a7f9f1 --- /dev/null +++ b/src/main/java/swim/porter/engine/PortFileProcessor.java @@ -0,0 +1,105 @@ +package swim.porter.engine; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import swim.porter.Main; + +import java.io.File; +import java.io.IOException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; + +public class PortFileProcessor { + + final static String fs = File.separator; + + public static String handlePortCommand(String arg) { + File pack = null; + // Check if arg is a file path + File fileCheck = new File(URLDecoder.decode(arg, StandardCharsets.UTF_8)); + if (fileCheck.exists()) { + pack = fileCheck; + } else if (arg.contains("mediafire.com")) { // else wise check if arg was a website url + boolean isRar = arg.contains(".rar"); + pack = Downloader.downloadMediafire(arg, isRar); + } else if (arg.contains("pvprp.com")) { + pack = Downloader.downloadPVPRP(arg); + } + + // now port it + if (pack != null) { + File portedPack = processPort(pack); + if (portedPack != null) { + return portedPack.getAbsolutePath(); + } + } + + return "failed"; + } + + // takes in file for pack to port, returns path to the ported pack + private static File processPort(File export) { + File pack; + + if (!export.isDirectory()) { + String ext = FilenameUtils.getExtension(export.getName()); + String extractionPath = Paths.get(Main.portDir, FilenameUtils.removeExtension(export.getName())).toString(); + if (ext.equalsIgnoreCase("zip")) { + ZipUtil.lightUnzip(export, new File(extractionPath)); + } else { + ZipUtil.SevenExtract(export.getAbsolutePath(), extractionPath); + } + pack = new File(FilenameUtils.removeExtension(extractionPath)); + } else { + pack = export; + } + + // find the pack.mcmeta, the folder it is located is the root dir of the pack + File manifest = findManifestFile(pack); + if (manifest.exists()) { + return Port.port(new File(manifest.getParentFile().getAbsolutePath())); + } else { + System.out.println("Error: uploaded pack does not contain pack.mcmeta"); + } + + return null; + } + + + // iteratively searches the pack for the mcmeta file + private static File findManifestFile(File dir) { + File manifest = new File(dir, "pack.mcmeta"); + if (manifest.exists()) { + return manifest; + } + File[] files = dir.listFiles(); + assert files != null; + for (File file : files) { + if (file.isDirectory()) { + manifest = findManifestFile(file); + if (manifest.exists()) { + return manifest; + } + } + } + return manifest; + } + + // delete everything in the pack dir except for the assets folder + public static void cleanPackDir() { + File swimServicesDir = new File(Main.portDir); + File[] swimFiles = swimServicesDir.listFiles(); + assert swimFiles != null; + for (File file : swimFiles) { + if (!file.getName().equalsIgnoreCase("assets")) { + try { + FileUtils.forceDelete(file); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + +} diff --git a/src/main/java/swim/porter/engine/Recolor.java b/src/main/java/swim/porter/engine/Recolor.java new file mode 100644 index 0000000..bbdfc32 --- /dev/null +++ b/src/main/java/swim/porter/engine/Recolor.java @@ -0,0 +1,24 @@ +package swim.porter.engine; + +import java.awt.*; +import java.awt.image.BufferedImage; + +public class Recolor { + + // tints a BufferedImage to a color + public static BufferedImage tint(BufferedImage image, Color color) { + for (int x = 0; x < image.getWidth(); x++) { + for (int y = 0; y < image.getHeight(); y++) { + Color pixelColor = new Color(image.getRGB(x, y), true); + int r = (pixelColor.getRed() + color.getRed()) / 2; + int g = (pixelColor.getGreen() + color.getGreen()) / 2; + int b = (pixelColor.getBlue() + color.getBlue()) / 2; + int a = pixelColor.getAlpha(); + int rgba = (a << 24) | (r << 16) | (g << 8) | b; + image.setRGB(x, y, rgba); + } + } + return image; + } + +} diff --git a/src/main/java/swim/porter/engine/Rescale.java b/src/main/java/swim/porter/engine/Rescale.java new file mode 100644 index 0000000..a5daead --- /dev/null +++ b/src/main/java/swim/porter/engine/Rescale.java @@ -0,0 +1,20 @@ +package swim.porter.engine; + +import java.awt.*; +import java.awt.image.BufferedImage; + +import static java.awt.image.BufferedImage.TYPE_INT_ARGB; + +public class Rescale { + + // this is just the cheapest and easiest method to implement for quick rescaling, if users really want more customization they can download the software + public static BufferedImage nearestNeighborRescale(BufferedImage originalImage, int targetWidth, int targetHeight) { + BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, TYPE_INT_ARGB); + Graphics2D g = resizedImage.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + g.drawImage(originalImage, 0, 0, targetWidth, targetHeight, null); + g.dispose(); + return resizedImage; + } + +} diff --git a/src/main/java/swim/porter/engine/ZipUtil.java b/src/main/java/swim/porter/engine/ZipUtil.java new file mode 100644 index 0000000..d9f4429 --- /dev/null +++ b/src/main/java/swim/porter/engine/ZipUtil.java @@ -0,0 +1,123 @@ +package swim.porter.engine; + +import net.sf.sevenzipjbinding.ExtractOperationResult; +import net.sf.sevenzipjbinding.IInArchive; +import net.sf.sevenzipjbinding.SevenZip; +import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream; +import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem; +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipFile; + +import java.io.*; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public class ZipUtil { + + // lightweight method for zipping a directory relatively quick + // not sure if using 7zip binding would have any benefit beyond speed, which would be worth it if that is the case + public static void LightZip(String dirPath) { + final Path sourceDir = Paths.get(dirPath); + String zipFileName = dirPath.concat(".zip"); + try { + final ZipOutputStream outputStream = new ZipOutputStream(new FileOutputStream(zipFileName)); + Files.walkFileTree(sourceDir, new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) { + try { + Path targetFile = sourceDir.relativize(file); + outputStream.putNextEntry(new ZipEntry(targetFile.toString())); + byte[] bytes = Files.readAllBytes(file); + outputStream.write(bytes, 0, bytes.length); + outputStream.closeEntry(); + } catch (IOException e) { + System.out.println("file compression error"); + } + return FileVisitResult.CONTINUE; + } + }); + outputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // a lightweight unzip method that ignores null data + public static void lightUnzip(File zipFile, File outputDir) { + try (ZipFile zf = new ZipFile(zipFile)) { + Enumeration entries = zf.getEntries(); + while (entries.hasMoreElements()) { + ZipArchiveEntry entry = entries.nextElement(); + try { + File outputFile = new File(outputDir, entry.getName()); + if (entry.isDirectory()) { + Files.createDirectories(Paths.get(outputFile.getAbsolutePath())); + } else { + Files.createDirectories(Paths.get(outputFile.getParent())); + try (InputStream inputStream = zf.getInputStream(entry); FileOutputStream outputStream = new FileOutputStream(outputFile)) { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + } + } + } catch (IOException e) { + System.err.println("Error processing entry: " + entry.getName() + ". Skipping it."); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + // we use 7zip binding to extract any type of archive possible, but using lightUnzip is a lot faster for normal zip archives + public static void SevenExtract(String archiveFilePath, String outputDirectory) { + try { + RandomAccessFile randomAccessFile = new RandomAccessFile(archiveFilePath, "r"); + RandomAccessFileInStream randomAccessFileStream = new RandomAccessFileInStream(randomAccessFile); + IInArchive inArchive = SevenZip.openInArchive(null, randomAccessFileStream); + try { + for (ISimpleInArchiveItem item : inArchive.getSimpleInterface().getArchiveItems()) { + if (!item.isFolder()) { + try { + File outputFile = new File(outputDirectory, item.getPath()); + if (!outputFile.isDirectory()) { + Files.createDirectories(Paths.get(outputFile.getParent())); + try (FileOutputStream outputStream = new FileOutputStream(outputFile)) { + ExtractOperationResult result = item.extractSlow(data -> { + if (data == null) { + return 0; + } + try { + outputStream.write(data); + } catch (IOException e) { + e.printStackTrace(); + } + return data.length; + }); + if (result != ExtractOperationResult.OK) { + System.err.println(String.format("Error extracting archive item %s. Extracting error: %s", item.getPath(), result)); + } + } + } + } catch (Exception e) { + System.err.println("Error processing item: " + item.getPath() + ". Skipping it."); + e.printStackTrace(); + } + } + } + } finally { + inArchive.close(); + randomAccessFileStream.close(); + randomAccessFile.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/resources/assets.zip b/src/main/resources/assets.zip new file mode 100644 index 0000000..f719a68 Binary files /dev/null and b/src/main/resources/assets.zip differ