├── .github └── workflows │ └── gradle.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshot.png ├── settings.gradle.kts └── src └── main ├── java └── org │ └── allaymc │ └── encryptmypack │ ├── ANSIColor.java │ ├── ConsolePanel.java │ ├── EncryptMyPack.java │ ├── GUI.form │ ├── GUI.java │ └── PackEncryptor.java └── resources ├── icon.png ├── log4j2.component.properties └── log4j2.xml /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Build with Gradle 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | paths: 7 | - .github/workflows/gradle.yml 8 | - src/** 9 | pull_request: 10 | paths: 11 | - .github/workflows/gradle.yml 12 | - src/** 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - run: chmod +x gradlew 20 | - uses: actions/setup-java@v4 21 | with: 22 | java-version: '21' 23 | distribution: 'zulu' 24 | - name: Setup gradle 25 | uses: gradle/actions/setup-gradle@v4 26 | with: 27 | gradle-version: wrapper 28 | cache-overwrite-existing: true 29 | cache-read-only: false 30 | build-scan-publish: true 31 | build-scan-terms-of-use-url: "https://gradle.com/terms-of-service" 32 | build-scan-terms-of-use-agree: "yes" 33 | - name: Build 34 | run: ./gradlew build 35 | - name: Upload Artifact 36 | uses: actions/upload-artifact@v4 37 | if: success() && contains(github.ref_name, 'main') 38 | with: 39 | path: build/libs/EncryptMyPack-*-shaded.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | run/ 7 | 8 | ### IntelliJ IDEA ### 9 | .idea/ 10 | *.iws 11 | *.iml 12 | *.ipr 13 | out/ 14 | !**/src/main/**/out/ 15 | !**/src/test/**/out/ 16 | 17 | ### Eclipse ### 18 | .apt_generated 19 | .classpath 20 | .factorypath 21 | .project 22 | .settings 23 | .springBeans 24 | .sts4-cache 25 | bin/ 26 | !**/src/main/**/bin/ 27 | !**/src/test/**/bin/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | 39 | ### Mac OS ### 40 | .DS_Store 41 | /.idea/ 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EncryptMyPack 2 | 3 | ![license](https://img.shields.io/badge/License-LGPL_3.0-blue.svg) 4 | ![version](https://img.shields.io/badge/Version-2.0.0-green.svg) 5 | 6 | A small util for encrypting and decrypting resource packs. 7 | 8 | screenshot 9 | 10 | ## 🌟 Feature 11 | 12 | - An easy to use graphical interface 13 | - Encrypt and decrypt resource packs 14 | - Support resource pack that contains sub packs 15 | 16 | ## 🔨 Usage 17 | 18 | Download the latest jar file from [release](https://github.com/AllayMC/EncryptMyPack/releases/latest). Before 19 | using this tool, make sure you have Java 21+ installed. Double click the jar file to run it, or run the following command in terminal: 20 | 21 | ```bash 22 | java -jar EncryptMyPack--shaded.jar 23 | ``` 24 | 25 | ### Encryption 26 | 27 | 1. The key should be a 32 character long string. You can click `GenKey` button to generate a random key 28 | 2. Make sure your pack is a zip file, and in your pack should be a manifest.json 29 | 30 | After the encryption, a `contents.json` file should now be in output zip file, and the key will be displayed in the console 31 | 32 | ### Decryption 33 | 34 | 1. Again, the key should be a 32 character long string 35 | 2. To decrypt the pack, you must provide its key 36 | 37 | ## 🎫 License 38 | 39 | Copyright **© 2023-2025 AllayMC**, all rights reserved. LGPL-3.0 40 | 41 | ## ❤️ Special thanks 42 | 43 | Thanks to [mcrputil](https://github.com/valaphee/mcrputil) for their great work! 44 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | id("java") 4 | id("com.github.johnrengelman.shadow") version "8.1.1" 5 | } 6 | 7 | group = "org.allaymc" 8 | version = "2.0.1" 9 | description = "A tool that can encrypt & decrypt Minecraft: Bedrock Edition resource pack" 10 | 11 | java.toolchain.languageVersion = JavaLanguageVersion.of(21) 12 | 13 | repositories { 14 | mavenCentral() 15 | maven("https://www.jitpack.io/") 16 | } 17 | 18 | dependencies { 19 | // Utils 20 | implementation("org.apache.commons:commons-lang3:3.14.0") 21 | implementation("commons-io:commons-io:2.15.1") 22 | implementation("com.google.code.gson:gson:2.10.1") 23 | 24 | // Logging 25 | implementation("org.slf4j:slf4j-api:2.0.17") 26 | implementation("org.apache.logging.log4j:log4j-slf4j2-impl:2.24.3") 27 | implementation("org.apache.logging.log4j:log4j-core:2.24.3") 28 | 29 | // UI 30 | implementation("com.github.steos:jnafilechooser:1.1.2") 31 | implementation("com.formdev:flatlaf:3.6") 32 | implementation("com.intellij:forms_rt:7.0.3") 33 | 34 | compileOnly("org.projectlombok:lombok:1.18.30") 35 | annotationProcessor("org.projectlombok:lombok:1.18.30") 36 | } 37 | 38 | application { 39 | mainClass.set("org.allaymc.encryptmypack.EncryptMyPack") 40 | } 41 | 42 | tasks.shadowJar { 43 | archiveClassifier = "shaded" 44 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AllayMC/EncryptMyPack/54d0a0b25ffdc5dc6a34e215732608f111d326ec/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Feb 04 14:52:37 HKT 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AllayMC/EncryptMyPack/54d0a0b25ffdc5dc6a34e215732608f111d326ec/screenshot.png -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "EncryptMyPack" 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/org/allaymc/encryptmypack/ANSIColor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/Geyser 24 | */ 25 | 26 | package org.allaymc.encryptmypack; 27 | 28 | import lombok.AllArgsConstructor; 29 | import lombok.Getter; 30 | 31 | import java.awt.*; 32 | import java.util.Arrays; 33 | import java.util.HashMap; 34 | import java.util.Map; 35 | import java.util.regex.Pattern; 36 | 37 | /** 38 | * @author daoge_cmd 39 | */ 40 | @Getter 41 | @AllArgsConstructor 42 | public enum ANSIColor { 43 | // Normal colors 44 | BLACK("(0;)?30(0;)?m", Color.BLACK), 45 | RED("(0;)?31(0;)?m", new Color(0xfff0524f)), 46 | GREEN("(0;)?32(0;)?m", new Color(0xff5c962c)), 47 | YELLOW("(0;)?33(0;)?m", new Color(0xffa68a0d)), 48 | BLUE("(0;)?34(0;)?m", new Color(0xff6cb6ff)), 49 | MAGENTA("(0;)?35(0;)?m", new Color(0xffa771bf)), 50 | CYAN("(0;)?36(0;)?m", new Color(0xff96d0ff)), 51 | WHITE("(0;)?37(0;)?m", new Color(0xffbcbec4)), 52 | 53 | // Bold colors 54 | B_BLACK("(0;)?(1;30|30;1)m", Color.BLACK), 55 | B_RED("(0;)?(1;31|31;1)m", new Color(0xfff0524f)), 56 | B_GREEN("(0;)?(1;32|32;1)m", new Color(0xff5c962c)), 57 | B_YELLOW("(0;)?(1;33|33;1)m", new Color(0xffa68a0d)), 58 | B_BLUE("(0;)?(1;34|34;1)m", new Color(0xff3993d4)), 59 | B_MAGENTA("(0;)?(1;35|35;1)m", new Color(0xffa771bf)), 60 | B_CYAN("(0;)?(1;36|36;1)m", new Color(0xff00a3a3)), 61 | B_WHITE("(0;)?(1;37|37;1)m", new Color(0xff808080)), 62 | 63 | RESET("0m", WHITE.color); 64 | 65 | private static final ANSIColor[] VALUES = values(); 66 | private static final String PREFIX = Pattern.quote("\u001B["); 67 | private static final Map PATTERN_MAP = new HashMap<>(); 68 | 69 | static { 70 | for (ANSIColor color : VALUES) { 71 | PATTERN_MAP.put(color, Pattern.compile(PREFIX + color.ANSICode)); 72 | } 73 | } 74 | 75 | private final String ANSICode; 76 | private final Color color; 77 | 78 | public static boolean isBoldColor(Color color) { 79 | return color.equals(B_BLACK.color) || 80 | color.equals(B_RED.color) || 81 | color.equals(B_GREEN.color) || 82 | color.equals(B_YELLOW.color) || 83 | color.equals(B_BLUE.color) || 84 | color.equals(B_MAGENTA.color) || 85 | color.equals(B_CYAN.color) || 86 | color.equals(B_WHITE.color); 87 | } 88 | 89 | public static ANSIColor fromANSI(String code) { 90 | return Arrays.stream(VALUES) 91 | .filter(value -> PATTERN_MAP.get(value).matcher(code).matches()) 92 | .findFirst() 93 | .orElse(RESET); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/org/allaymc/encryptmypack/ConsolePanel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author GeyserMC 23 | * @link https://github.com/GeyserMC/Geyser 24 | */ 25 | 26 | package org.allaymc.encryptmypack; 27 | 28 | import lombok.extern.slf4j.Slf4j; 29 | 30 | import javax.swing.*; 31 | import javax.swing.text.BadLocationException; 32 | import javax.swing.text.SimpleAttributeSet; 33 | import javax.swing.text.StyleConstants; 34 | import java.awt.*; 35 | import java.io.Serial; 36 | 37 | /** 38 | * This class was based on this code 39 | * 40 | * @author daoge_cmd 41 | */ 42 | @Slf4j 43 | public class ConsolePanel extends JTextPane { 44 | 45 | @Serial 46 | private static final long serialVersionUID = 1L; 47 | 48 | private static Color colorCurrent = ANSIColor.RESET.getColor(); 49 | int currentLength = 0; // Used to let ProgressBars work 50 | private String remaining = ""; 51 | 52 | /** 53 | * Append the given string in the given color to the text pane 54 | * 55 | * @param color The color 56 | * @param text The text 57 | */ 58 | private void append(Color color, String text) { 59 | var attribute = new SimpleAttributeSet(); 60 | StyleConstants.setForeground(attribute, color); 61 | StyleConstants.setBold(attribute, ANSIColor.isBoldColor(color)); 62 | 63 | var len = getDocument().getLength(); 64 | 65 | if (text.contains("\r")) { 66 | // We have a carriage, so we should be at the front of the line 67 | if (text.contains("\n")) { 68 | // We're good to normally add to the document 69 | try { 70 | getDocument().insertString(len, text, attribute); 71 | } catch (BadLocationException e) { 72 | log.error("Error while appending text to console", e); 73 | } 74 | 75 | currentLength = 0; 76 | return; 77 | } 78 | 79 | // There's no newline, we should erase our progress to the start 80 | try { 81 | getDocument().remove(len - currentLength, currentLength); 82 | getDocument().insertString(len - currentLength, text, attribute); 83 | currentLength = text.length(); 84 | } catch (BadLocationException e) { 85 | log.error("Error while removing text from console, most likely has to do with printing weirdly", e); 86 | } 87 | return; 88 | } 89 | 90 | currentLength += text.length(); 91 | 92 | try { 93 | getDocument().insertString(len, text, attribute); 94 | } catch (BadLocationException e) { 95 | log.error("Error while appending text to console", e); 96 | } 97 | } 98 | 99 | /** 100 | * Extract the ANSI color codes from the string and add each part to the text pane 101 | * 102 | * @param text The text to parse 103 | */ 104 | public void appendANSI(String text) { // convert ANSI color codes first 105 | int aPos = 0; // current char position in addString 106 | int aIndex; // index of next Escape sequence 107 | int mIndex; // index of "m" terminating Escape sequence 108 | String tmpString; 109 | boolean stillSearching = true; // true until no more Escape sequences 110 | String addString = remaining + text; 111 | remaining = ""; 112 | 113 | if (!addString.isEmpty()) { 114 | aIndex = addString.indexOf("\u001B"); // find first escape 115 | if (aIndex == -1) { // no escape/color change in this string, so just send it with current color 116 | append(colorCurrent, addString); 117 | return; 118 | } 119 | // otherwise There is an escape character in the string, so we must process it 120 | 121 | if (aIndex > 0) { // Escape is not first char, so send text up to first escape 122 | tmpString = addString.substring(0, aIndex); 123 | append(colorCurrent, tmpString); 124 | aPos = aIndex; // aPos is now at the beginning of the first escape sequence 125 | } 126 | 127 | // while there's text in the input buffer 128 | while (stillSearching) { 129 | mIndex = addString.indexOf("m", aPos); // find the end of the escape sequence 130 | if (mIndex < 0) { // the buffer ends halfway through the ansi string! 131 | remaining = addString.substring(aPos); 132 | stillSearching = false; 133 | continue; 134 | } else { 135 | tmpString = addString.substring(aPos, mIndex + 1); 136 | colorCurrent = ANSIColor.fromANSI(tmpString).getColor(); 137 | } 138 | aPos = mIndex + 1; 139 | // now we have the color, send text that is in that color (up to next escape) 140 | 141 | aIndex = addString.indexOf("\u001B", aPos); 142 | 143 | if (aIndex == -1) { // if that was the last sequence of the input, send remaining text 144 | tmpString = addString.substring(aPos); 145 | append(colorCurrent, tmpString); 146 | stillSearching = false; 147 | continue; // jump out of loop early, as the whole string has been sent now 148 | } 149 | 150 | // there is another escape sequence, so send part of the string and prepare for the next 151 | tmpString = addString.substring(aPos, aIndex); 152 | aPos = aIndex; 153 | append(colorCurrent, tmpString); 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/org/allaymc/encryptmypack/EncryptMyPack.java: -------------------------------------------------------------------------------- 1 | package org.allaymc.encryptmypack; 2 | 3 | import com.formdev.flatlaf.themes.FlatMacDarkLaf; 4 | 5 | import javax.swing.*; 6 | 7 | /** 8 | * @author daoge_cmd 9 | */ 10 | public class EncryptMyPack { 11 | public static void main(String[] args) { 12 | FlatMacDarkLaf.setup(); 13 | SwingUtilities.invokeLater(GUI::new); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/allaymc/encryptmypack/GUI.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
114 | -------------------------------------------------------------------------------- /src/main/java/org/allaymc/encryptmypack/GUI.java: -------------------------------------------------------------------------------- 1 | package org.allaymc.encryptmypack; 2 | 3 | import jnafilechooser.api.JnaFileChooser; 4 | 5 | import javax.swing.*; 6 | import javax.swing.text.Document; 7 | import java.awt.*; 8 | import java.awt.event.MouseAdapter; 9 | import java.awt.event.MouseEvent; 10 | import java.io.File; 11 | import java.io.OutputStream; 12 | import java.io.PrintStream; 13 | import java.net.URL; 14 | import java.nio.file.Path; 15 | import java.nio.file.Paths; 16 | 17 | /** 18 | * @author daoge_cmd 19 | */ 20 | public final class GUI { 21 | private JPanel rootPanel; 22 | private JScrollPane scrollPane; 23 | private ConsolePanel consolePanel; 24 | private JTextField keyTextField; 25 | private JButton generateKeyButton; 26 | private JTextField filePathTextField; 27 | private JButton chooseFileButton; 28 | private JButton encryptButton; 29 | private JButton decryptButton; 30 | 31 | public GUI() { 32 | $$$setupUI$$$(); 33 | wrapSystemOutputStreams(); 34 | JFrame frame = new JFrame("EncryptMyPack by @daoge_cmd"); 35 | frame.setContentPane(rootPanel); 36 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 37 | frame.setSize(700, 700); 38 | frame.setLocationRelativeTo(null); 39 | keyTextField.setText(PackEncryptor.generateRandomKey()); 40 | 41 | // Set icon 42 | URL image = GUI.class.getClassLoader().getResource("icon.png"); 43 | if (image != null) { 44 | frame.setIconImage(new ImageIcon(image).getImage()); 45 | } 46 | 47 | // Add action listeners 48 | generateKeyButton.addMouseListener(new MouseAdapter() { 49 | @Override 50 | public void mouseClicked(MouseEvent e) { 51 | if (e.getButton() != MouseEvent.BUTTON1) { 52 | return; 53 | } 54 | 55 | var newKey = PackEncryptor.generateRandomKey(); 56 | keyTextField.setText(newKey); 57 | } 58 | }); 59 | 60 | chooseFileButton.addMouseListener(new MouseAdapter() { 61 | @Override 62 | public void mouseClicked(MouseEvent e) { 63 | if (e.getButton() != MouseEvent.BUTTON1) { 64 | return; 65 | } 66 | 67 | JnaFileChooser fc = new JnaFileChooser(); 68 | fc.addFilter("All Files", "*"); 69 | fc.addFilter("Resource Pack Files", "zip", "mcpack"); 70 | if (fc.showOpenDialog(frame)) { 71 | File f = fc.getSelectedFile(); 72 | filePathTextField.setText(f.toPath().toAbsolutePath().toString()); 73 | } 74 | } 75 | }); 76 | 77 | encryptButton.addMouseListener(new MouseAdapter() { 78 | @Override 79 | public void mouseClicked(MouseEvent e) { 80 | if (e.getButton() != MouseEvent.BUTTON1) { 81 | return; 82 | } 83 | 84 | String key = keyTextField.getText(); 85 | Path path = Path.of(filePathTextField.getText()); 86 | PackEncryptor.encrypt(path, appendToFileName(path, "_encrypted"), key); 87 | } 88 | }); 89 | 90 | decryptButton.addMouseListener(new MouseAdapter() { 91 | @Override 92 | public void mouseClicked(MouseEvent e) { 93 | if (e.getButton() != MouseEvent.BUTTON1) { 94 | return; 95 | } 96 | 97 | String key = keyTextField.getText(); 98 | Path path = Path.of(filePathTextField.getText()); 99 | PackEncryptor.decrypt(path, appendToFileName(path, "_decrypted"), key); 100 | } 101 | }); 102 | 103 | // Show the frame 104 | frame.setVisible(true); 105 | } 106 | 107 | private static Path appendToFileName(Path path, String suffix) { 108 | Path parent = path.getParent(); 109 | String fileName = path.getFileName().toString(); 110 | 111 | int dotIndex = fileName.lastIndexOf('.'); 112 | String name; 113 | String extension; 114 | 115 | if (dotIndex == -1) { 116 | name = fileName; 117 | extension = ""; 118 | } else { 119 | name = fileName.substring(0, dotIndex); 120 | extension = fileName.substring(dotIndex); 121 | } 122 | 123 | String newFileName = name + suffix + extension; 124 | return (parent != null) ? parent.resolve(newFileName) : Paths.get(newFileName); 125 | } 126 | 127 | private void wrapSystemOutputStreams() { 128 | var proxyOutputStream = createProxyOutputStream(); 129 | // Override the system output streams 130 | System.setOut(new PrintStream(proxyOutputStream, true)); 131 | System.setErr(new PrintStream(proxyOutputStream, true)); 132 | } 133 | 134 | private OutputStream createProxyOutputStream() { 135 | var originalOutputStream = System.out; 136 | return new OutputStream() { 137 | @Override 138 | public void write(int i) { 139 | originalOutputStream.write(i); 140 | appendTextToConsole(String.valueOf((char) i)); 141 | } 142 | 143 | @Override 144 | public void write(byte[] b) { 145 | write(b, 0, b.length); 146 | } 147 | 148 | @Override 149 | public void write(byte[] b, int off, int len) { 150 | originalOutputStream.write(b, off, len); 151 | appendTextToConsole(new String(b, off, len)); 152 | } 153 | }; 154 | } 155 | 156 | public void appendTextToConsole(final String text) { 157 | SwingUtilities.invokeLater(() -> { 158 | consolePanel.appendANSI(text); 159 | Document doc = consolePanel.getDocument(); 160 | consolePanel.setCaretPosition(doc.getLength()); 161 | }); 162 | } 163 | 164 | private void createUIComponents() { 165 | // Init the console 166 | consolePanel = new ConsolePanel(); 167 | consolePanel.setBackground(new Color(0x131313)); 168 | consolePanel.setEditable(false); 169 | } 170 | 171 | /** 172 | * Method generated by IntelliJ IDEA GUI Designer 173 | * >>> IMPORTANT!! <<< 174 | * DO NOT edit this method OR call it in your code! 175 | * 176 | * @noinspection ALL 177 | */ 178 | private void $$$setupUI$$$() { 179 | createUIComponents(); 180 | rootPanel = new JPanel(); 181 | rootPanel.setLayout(new GridBagLayout()); 182 | rootPanel.setPreferredSize(new Dimension(700, 700)); 183 | scrollPane = new JScrollPane(); 184 | GridBagConstraints gbc; 185 | gbc = new GridBagConstraints(); 186 | gbc.gridx = 0; 187 | gbc.gridy = 0; 188 | gbc.weightx = 1.0; 189 | gbc.weighty = 1.0; 190 | gbc.fill = GridBagConstraints.BOTH; 191 | rootPanel.add(scrollPane, gbc); 192 | scrollPane.setViewportView(consolePanel); 193 | final JPanel panel1 = new JPanel(); 194 | panel1.setLayout(new GridBagLayout()); 195 | gbc = new GridBagConstraints(); 196 | gbc.gridx = 0; 197 | gbc.gridy = 2; 198 | gbc.weightx = 1.0; 199 | gbc.fill = GridBagConstraints.BOTH; 200 | rootPanel.add(panel1, gbc); 201 | keyTextField = new JTextField(); 202 | gbc = new GridBagConstraints(); 203 | gbc.gridx = 0; 204 | gbc.gridy = 0; 205 | gbc.weightx = 1.0; 206 | gbc.weighty = 1.0; 207 | gbc.fill = GridBagConstraints.BOTH; 208 | panel1.add(keyTextField, gbc); 209 | generateKeyButton = new JButton(); 210 | generateKeyButton.setText("GenKey"); 211 | gbc = new GridBagConstraints(); 212 | gbc.gridx = 1; 213 | gbc.gridy = 0; 214 | gbc.weighty = 1.0; 215 | gbc.fill = GridBagConstraints.HORIZONTAL; 216 | panel1.add(generateKeyButton, gbc); 217 | final JPanel panel2 = new JPanel(); 218 | panel2.setLayout(new GridBagLayout()); 219 | gbc = new GridBagConstraints(); 220 | gbc.gridx = 0; 221 | gbc.gridy = 1; 222 | gbc.weightx = 1.0; 223 | gbc.fill = GridBagConstraints.BOTH; 224 | rootPanel.add(panel2, gbc); 225 | filePathTextField = new JTextField(); 226 | gbc = new GridBagConstraints(); 227 | gbc.gridx = 0; 228 | gbc.gridy = 0; 229 | gbc.weightx = 1.0; 230 | gbc.weighty = 1.0; 231 | gbc.anchor = GridBagConstraints.WEST; 232 | gbc.fill = GridBagConstraints.HORIZONTAL; 233 | panel2.add(filePathTextField, gbc); 234 | chooseFileButton = new JButton(); 235 | chooseFileButton.setText("Choose"); 236 | gbc = new GridBagConstraints(); 237 | gbc.gridx = 1; 238 | gbc.gridy = 0; 239 | gbc.weighty = 1.0; 240 | gbc.fill = GridBagConstraints.HORIZONTAL; 241 | panel2.add(chooseFileButton, gbc); 242 | final JPanel panel3 = new JPanel(); 243 | panel3.setLayout(new GridBagLayout()); 244 | gbc = new GridBagConstraints(); 245 | gbc.gridx = 0; 246 | gbc.gridy = 3; 247 | gbc.fill = GridBagConstraints.BOTH; 248 | rootPanel.add(panel3, gbc); 249 | encryptButton = new JButton(); 250 | encryptButton.setText("Encrypt"); 251 | gbc = new GridBagConstraints(); 252 | gbc.gridx = 0; 253 | gbc.gridy = 0; 254 | gbc.weightx = 1.0; 255 | gbc.weighty = 1.0; 256 | gbc.fill = GridBagConstraints.HORIZONTAL; 257 | panel3.add(encryptButton, gbc); 258 | decryptButton = new JButton(); 259 | decryptButton.setActionCommand("Button"); 260 | decryptButton.setText("Decrypt"); 261 | gbc = new GridBagConstraints(); 262 | gbc.gridx = 1; 263 | gbc.gridy = 0; 264 | gbc.weightx = 1.0; 265 | gbc.weighty = 1.0; 266 | gbc.fill = GridBagConstraints.HORIZONTAL; 267 | panel3.add(decryptButton, gbc); 268 | } 269 | 270 | /** 271 | * @noinspection ALL 272 | */ 273 | public JComponent $$$getRootComponent$$$() { 274 | return rootPanel; 275 | } 276 | 277 | } 278 | -------------------------------------------------------------------------------- /src/main/java/org/allaymc/encryptmypack/PackEncryptor.java: -------------------------------------------------------------------------------- 1 | package org.allaymc.encryptmypack; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.google.gson.stream.JsonReader; 6 | import lombok.SneakyThrows; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.apache.commons.lang3.RandomStringUtils; 9 | 10 | import javax.crypto.BadPaddingException; 11 | import javax.crypto.Cipher; 12 | import javax.crypto.IllegalBlockSizeException; 13 | import javax.crypto.NoSuchPaddingException; 14 | import javax.crypto.spec.IvParameterSpec; 15 | import javax.crypto.spec.SecretKeySpec; 16 | import java.io.ByteArrayOutputStream; 17 | import java.io.FileOutputStream; 18 | import java.io.IOException; 19 | import java.io.InputStreamReader; 20 | import java.nio.charset.StandardCharsets; 21 | import java.nio.file.Files; 22 | import java.nio.file.Path; 23 | import java.security.InvalidAlgorithmParameterException; 24 | import java.security.InvalidKeyException; 25 | import java.security.NoSuchAlgorithmException; 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | import java.util.zip.ZipEntry; 29 | import java.util.zip.ZipFile; 30 | import java.util.zip.ZipOutputStream; 31 | 32 | 33 | /** 34 | * @author daoge_cmd 35 | */ 36 | @Slf4j 37 | public final class PackEncryptor { 38 | 39 | private static final Gson GSON = new GsonBuilder() 40 | .disableHtmlEscaping() 41 | .serializeNulls() 42 | .setLenient() 43 | .create(); 44 | private static final int KEY_LENGTH = 32; 45 | private static final byte[] VERSION = new byte[]{(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}; 46 | private static final byte[] MAGIC = new byte[]{(byte) 0xFC, (byte) 0xB9, (byte) 0xCF, (byte) 0x9B}; 47 | private static final List EXCLUDED_FILES = List.of("manifest.json", "pack_icon.png", "bug_pack_icon.png"); 48 | 49 | public static String generateRandomKey() { 50 | return RandomStringUtils.randomAlphanumeric(KEY_LENGTH); 51 | } 52 | 53 | public static void encrypt(Path inputPath, Path outputPath, String key) { 54 | if (!checkArgs(inputPath, outputPath, key)) { 55 | return; 56 | } 57 | 58 | try (var inputZip = new ZipFile(inputPath.toString())) { 59 | encrypt0(inputZip, outputPath, key); 60 | } catch (Exception e) { 61 | log.error("Failed to encrypt pack", e); 62 | } 63 | } 64 | 65 | public static void decrypt(Path inputPath, Path outputPath, String key) { 66 | if (!checkArgs(inputPath, outputPath, key)) { 67 | return; 68 | } 69 | 70 | try (var inputZip = new ZipFile(inputPath.toString())) { 71 | decrypt0(inputZip, outputPath, key); 72 | } catch (Exception e) { 73 | log.error("Failed to decrypt pack", e); 74 | } 75 | } 76 | 77 | @SneakyThrows 78 | private static void encrypt0(ZipFile inputZip, Path outputPath, String key) { 79 | // Find content id 80 | var uuid = findPackUUID(inputZip); 81 | log.info("ContentId: {}", uuid); 82 | 83 | var contentEntries = new ArrayList(); 84 | 85 | // Delete old output 86 | Files.deleteIfExists(outputPath); 87 | var outputStream = new ZipOutputStream(new FileOutputStream(outputPath.toFile()), StandardCharsets.UTF_8); 88 | // Encrypt files 89 | inputZip.stream().forEach(zipEntry -> { 90 | if (zipEntry.isDirectory()) { 91 | createDirectoryRoot(zipEntry, outputStream); 92 | if (isSubPackRoot(zipEntry)) { 93 | // Handle sub pack 94 | encryptSubPack(inputZip, outputStream, zipEntry.getName(), key, uuid); 95 | } 96 | 97 | return; 98 | } 99 | // Sub pack files will be handled in encryptSubPack() 100 | if (isSubPackFile(zipEntry)) { 101 | return; 102 | } 103 | 104 | String entryKey = null; 105 | // Check if file is excluded 106 | if (EXCLUDED_FILES.contains(zipEntry.getName())) { 107 | encryptExcludedFile(inputZip, outputStream, zipEntry); 108 | // Excluded file does not have entry key 109 | } else { 110 | // Encrypt file 111 | entryKey = encryptFile(inputZip, outputStream, zipEntry); 112 | } 113 | log.info("File: {}, entryKey: {}", zipEntry.getName(), entryKey); 114 | contentEntries.add(new ContentEntry(zipEntry.getName(), entryKey)); 115 | }); 116 | 117 | generateContentsJson("contents.json", outputStream, uuid, key, contentEntries); 118 | outputStream.close(); 119 | log.info("Encryption finish. Key: {}. Output file: {}", key, outputPath); 120 | } 121 | 122 | @SneakyThrows 123 | private static void createDirectoryRoot(ZipEntry zipEntry, ZipOutputStream outputStream) { 124 | outputStream.putNextEntry(copyZipEntry(zipEntry)); 125 | outputStream.closeEntry(); 126 | } 127 | 128 | @SneakyThrows 129 | private static void encryptSubPack(ZipFile inputZip, ZipOutputStream zos, String subPackPath, String key, String contentId) { 130 | log.info("Encrypting sub pack: {}", subPackPath); 131 | var subPackContentEntries = new ArrayList(); 132 | 133 | // Encrypt files 134 | inputZip.stream().forEach(zipEntry -> { 135 | if (zipEntry.isDirectory() || !zipEntry.getName().startsWith(subPackPath)) { 136 | return; 137 | } 138 | 139 | String entryKey = encryptFile(inputZip, zos, zipEntry); 140 | log.info("Sub pack file: {}, entryKey: {}", zipEntry.getName(), entryKey); 141 | subPackContentEntries.add(new ContentEntry(zipEntry.getName().substring(subPackPath.length()), entryKey)); 142 | }); 143 | 144 | generateContentsJson(subPackPath + "contents.json", zos, contentId, key, subPackContentEntries); 145 | } 146 | 147 | private static void generateContentsJson(String name, ZipOutputStream outputStream, String contentId, String key, ArrayList contentEntries) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { 148 | outputStream.putNextEntry(new ZipEntry(name)); 149 | try (var stream = new ByteArrayOutputStream()) { 150 | stream.write(VERSION); 151 | stream.write(MAGIC); 152 | paddingTo(stream, 0x10); 153 | var contentIdBytes = contentId.getBytes(StandardCharsets.UTF_8); 154 | // Write content id length 155 | stream.write(contentIdBytes.length); 156 | // Write content id 157 | stream.write(contentIdBytes); 158 | // Init contents.json encryptor 159 | var secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"); 160 | var cipher = Cipher.getInstance("AES/CFB8/NoPadding"); 161 | cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(key.substring(0, 16).getBytes(StandardCharsets.UTF_8))); 162 | // Write contents.json 163 | var contentJson = GSON.toJson(new Content(contentEntries)); 164 | paddingTo(stream, 0x100); 165 | stream.write(cipher.doFinal(contentJson.getBytes(StandardCharsets.UTF_8))); 166 | outputStream.write(stream.toByteArray()); 167 | } 168 | outputStream.closeEntry(); 169 | log.info("Successfully create contents.json"); 170 | } 171 | 172 | @SneakyThrows 173 | private static void encryptExcludedFile(ZipFile inputZip, ZipOutputStream outputStream, ZipEntry zipEntry) { 174 | log.info("Excluded file: {}, copy directly", zipEntry.getName()); 175 | outputStream.putNextEntry(copyZipEntry(zipEntry)); 176 | outputStream.write(inputZip.getInputStream(zipEntry).readAllBytes()); 177 | outputStream.closeEntry(); 178 | } 179 | 180 | @SneakyThrows 181 | private static String encryptFile(ZipFile inputZip, ZipOutputStream outputStream, ZipEntry zipEntry) { 182 | byte[] bytes; 183 | bytes = inputZip.getInputStream(zipEntry).readAllBytes(); 184 | // Init encryptor 185 | var key = RandomStringUtils.randomAlphanumeric(KEY_LENGTH); 186 | var secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"); 187 | var cipher = Cipher.getInstance("AES/CFB8/NoPadding"); 188 | cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(key.substring(0, 16).getBytes(StandardCharsets.UTF_8))); 189 | // Encrypt the file 190 | var encryptedBytes = cipher.doFinal(bytes); 191 | // Write bytes 192 | outputStream.putNextEntry(copyZipEntry(zipEntry)); 193 | outputStream.write(encryptedBytes); 194 | outputStream.closeEntry(); 195 | return key; 196 | } 197 | 198 | @SneakyThrows 199 | private static void decrypt0(ZipFile inputZip, Path outputPath, String key) { 200 | Content content = decryptContentsJson(inputZip, "contents.json", key); 201 | 202 | // Delete old output 203 | Files.deleteIfExists(outputPath); 204 | var outputStream = new ZipOutputStream(new FileOutputStream(outputPath.toFile())); 205 | // Decrypt files 206 | for (var contentEntry : content.content) { 207 | if (contentEntry.key == null) { 208 | continue; 209 | } 210 | 211 | var entryPath = contentEntry.path; 212 | var zipEntry = inputZip.getEntry(entryPath); 213 | if (zipEntry == null) { 214 | log.error("Zip entry not exists: {}", entryPath); 215 | continue; 216 | } 217 | 218 | log.info("Decrypting file: {}", entryPath); 219 | outputStream.putNextEntry(copyZipEntry(zipEntry)); 220 | decryptFile(outputStream, inputZip.getInputStream(zipEntry).readAllBytes(), contentEntry.key); 221 | outputStream.closeEntry(); 222 | } 223 | // Copy excluded files 224 | for (var excluded : EXCLUDED_FILES) { 225 | // manifest.json, pack_icon.png, bug_pack_icon.png etc... 226 | // Just copy it to output folder as they are not encrypted 227 | var zipEntry = inputZip.getEntry(excluded); 228 | if (zipEntry == null) continue; 229 | 230 | log.info("Copying file: {}", excluded); 231 | outputStream.putNextEntry(copyZipEntry(zipEntry)); 232 | outputStream.write(inputZip.getInputStream(zipEntry).readAllBytes()); 233 | outputStream.closeEntry(); 234 | } 235 | 236 | // Handle sub packs (if exist) 237 | inputZip.stream().filter(PackEncryptor::isSubPackRoot).forEach(zipEntry -> decryptSubPack(inputZip, outputStream, zipEntry.getName(), key)); 238 | 239 | outputStream.close(); 240 | log.info("Decrypted file {} with key {} successfully. Output file: {}", inputZip.getName(), key, outputPath); 241 | } 242 | 243 | @SneakyThrows 244 | private static void decryptSubPack(ZipFile inputZip, ZipOutputStream zos, String subPackPath, String key) { 245 | log.info("Decrypting sub pack: {}", subPackPath); 246 | Content content = decryptContentsJson(inputZip, subPackPath + "contents.json", key); 247 | 248 | for (var contentEntry : content.content) { 249 | var entryPath = subPackPath + contentEntry.path; 250 | var zipEntry = inputZip.getEntry(entryPath); 251 | if (zipEntry == null) { 252 | log.error("Zip entry not exists: {}", entryPath); 253 | continue; 254 | } 255 | zos.putNextEntry(copyZipEntry(zipEntry)); 256 | var bytes = inputZip.getInputStream(zipEntry).readAllBytes(); 257 | log.info("Decrypting sub pack file: {}", entryPath); 258 | decryptFile(zos, bytes, contentEntry.key); 259 | zos.closeEntry(); 260 | } 261 | } 262 | 263 | @SneakyThrows 264 | private static void decryptFile(ZipOutputStream zos, byte[] bytes, String entryKey) { 265 | var entryKeyBytes = entryKey.getBytes(StandardCharsets.UTF_8); 266 | if (entryKeyBytes.length != KEY_LENGTH) { 267 | log.error("Invalid key length (length should be {}): {}", KEY_LENGTH, entryKey); 268 | return; 269 | } 270 | var secretKey = new SecretKeySpec(entryKeyBytes, "AES"); 271 | var cipher = Cipher.getInstance("AES/CFB8/NoPadding"); 272 | cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(entryKey.substring(0, 16).getBytes(StandardCharsets.UTF_8))); 273 | var decryptedBytes = cipher.doFinal(bytes); 274 | zos.write(decryptedBytes); 275 | } 276 | 277 | @SneakyThrows 278 | private static Content decryptContentsJson(ZipFile inputZip, String subPackPath, String key) { 279 | var entry = inputZip.getEntry(subPackPath); 280 | if (entry == null) { 281 | log.error("Cannot find {}, it seems that this file is not encrypted", subPackPath); 282 | throw new IllegalArgumentException(); 283 | } 284 | 285 | try (var stream = inputZip.getInputStream(entry)) { 286 | stream.skip(0x100); 287 | var bytes = stream.readAllBytes(); 288 | var secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"); 289 | var cipher = Cipher.getInstance("AES/CFB8/NoPadding"); 290 | cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(key.substring(0, 16).getBytes(StandardCharsets.UTF_8))); 291 | var decryptedBytes = cipher.doFinal(bytes); 292 | Content content = GSON.fromJson(new String(decryptedBytes), Content.class); 293 | log.info("Decrypted content json: {}", content); 294 | return content; 295 | } 296 | } 297 | 298 | private static boolean isSubPackFile(ZipEntry zipEntry) { 299 | return zipEntry.getName().startsWith("subpacks/"); 300 | } 301 | 302 | private static boolean isSubPackRoot(ZipEntry zipEntry) { 303 | return zipEntry.isDirectory() && 304 | zipEntry.getName().startsWith("subpacks/") && 305 | calculateCharCount(zipEntry.getName(), '/') == 2; 306 | } 307 | 308 | private static boolean checkArgs(Path inputPath, Path outputPath, String key) { 309 | if (key.length() != KEY_LENGTH) { 310 | log.error("key length must be 32"); 311 | return false; 312 | } 313 | 314 | if (!Files.isRegularFile(inputPath)) { 315 | log.error("Input file is not exists"); 316 | return false; 317 | } 318 | 319 | if (inputPath.equals(outputPath)) { 320 | log.error("input and output file cannot be the same"); 321 | return false; 322 | } 323 | 324 | return true; 325 | } 326 | 327 | private static void paddingTo(ByteArrayOutputStream stream, int pos) { 328 | if (pos <= stream.size()) { 329 | throw new IllegalArgumentException("pos must be bigger than stream size"); 330 | } 331 | 332 | var need = pos - stream.size(); 333 | for (int i = 0; i < need; i++) { 334 | stream.write(0); 335 | } 336 | } 337 | 338 | @SneakyThrows 339 | private static String findPackUUID(ZipFile zip) { 340 | var manifestEntry = zip.getEntry("manifest.json"); 341 | if (manifestEntry == null) { 342 | throw new IllegalArgumentException("manifest file not exists"); 343 | } 344 | 345 | Manifest manifest = GSON.fromJson(new JsonReader(new InputStreamReader(zip.getInputStream(manifestEntry), StandardCharsets.UTF_8)), Manifest.class); 346 | return manifest.header.uuid; 347 | } 348 | 349 | private static int calculateCharCount(String str, char target) { 350 | int count = 0; 351 | for (char c : str.toCharArray()) { 352 | if (c == target) { 353 | count++; 354 | } 355 | } 356 | 357 | return count; 358 | } 359 | 360 | private static ZipEntry copyZipEntry(ZipEntry entry) { 361 | var newEntry = new ZipEntry(entry); 362 | // Explicitly set method to DEFLATED to avoid invalid crc-32 error 363 | newEntry.setMethod(ZipEntry.DEFLATED); 364 | return newEntry; 365 | } 366 | 367 | protected record Content(List content) {} 368 | 369 | protected record ContentEntry(String path, String key) {} 370 | 371 | protected static class Manifest { 372 | 373 | protected Header header; 374 | 375 | protected static class Header { 376 | private String uuid; 377 | } 378 | } 379 | } -------------------------------------------------------------------------------- /src/main/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AllayMC/EncryptMyPack/54d0a0b25ffdc5dc6a34e215732608f111d326ec/src/main/resources/icon.png -------------------------------------------------------------------------------- /src/main/resources/log4j2.component.properties: -------------------------------------------------------------------------------- 1 | log4j2.enableThreadlocals=true 2 | log4j2.enableDirectEncoders=true 3 | log4j2.garbagefreeThreadContextMap=true 4 | terminal.ansi=true -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | --------------------------------------------------------------------------------