diff --git a/.gitignore b/.gitignore
index a1c2a23..53bd413 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,4 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
+app/.DS_Store
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..9fcf0b3
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,45 @@
+plugins {
+ id 'com.android.application'
+ id 'top.niunaijun.blackobfuscator'
+}
+
+android {
+ compileSdkVersion 30
+ buildToolsVersion "30.0.0"
+
+ defaultConfig {
+ applicationId "top.niunaijun.blackobfuscator.asplugin"
+ minSdkVersion 21
+ targetSdkVersion 30
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+BlackObfuscator {
+ enabled true
+ obfClass = ["MainActivity", "com.bbb"]
+}
+
+dependencies {
+
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'com.google.android.material:material:1.4.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
+ testImplementation 'junit:junit:4.+'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/top/niunaijun/blackobfuscator/asplugin/ExampleInstrumentedTest.java b/app/src/androidTest/java/top/niunaijun/blackobfuscator/asplugin/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..fe0ba4c
--- /dev/null
+++ b/app/src/androidTest/java/top/niunaijun/blackobfuscator/asplugin/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package top.niunaijun.blackobfuscator.asplugin;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertEquals("top.niunaijun.blackobfuscator.asplugin", appContext.getPackageName());
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f96d65e
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/top/niunaijun/blackobfuscator/asplugin/MainActivity.java b/app/src/main/java/top/niunaijun/blackobfuscator/asplugin/MainActivity.java
new file mode 100644
index 0000000..800774f
--- /dev/null
+++ b/app/src/main/java/top/niunaijun/blackobfuscator/asplugin/MainActivity.java
@@ -0,0 +1,20 @@
+package top.niunaijun.blackobfuscator.asplugin;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.os.Bundle;
+import android.util.Log;
+
+public class MainActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ if (System.currentTimeMillis() > 3) {
+ Log.d("123", "onCreate: sdfasdffakea");
+ } else {
+ Log.d("123", "onCreate: aax1xaaa");
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..4fc2444
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..a571e60
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..61da551
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c41dd28
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..db5080a
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..6dba46d
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..da31a87
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..15ac681
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..b216f2d
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..f25a419
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..e96783c
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..eba0b55
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..f8c6127
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..eb4b874
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ BlackObfuscator-ASPlugin
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..2e9414b
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/top/niunaijun/blackobfuscator/asplugin/ExampleUnitTest.java b/app/src/test/java/top/niunaijun/blackobfuscator/asplugin/ExampleUnitTest.java
new file mode 100644
index 0000000..0b88696
--- /dev/null
+++ b/app/src/test/java/top/niunaijun/blackobfuscator/asplugin/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package top.niunaijun.blackobfuscator.asplugin;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..6378090
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,35 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ maven { url 'https://jitpack.io' }
+ maven {
+ url uri('./localRepo')
+ }
+ }
+ dependencies {
+ classpath "com.android.tools.build:gradle:4.2.0"
+ classpath "top.niunaijun.blackobfuscator:plugin:1.0.0"
+ classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ jcenter() // Warning: this repository is going to shut down soon
+ maven {
+ url uri('./localRepo')
+ }
+ maven { url 'https://jitpack.io' }
+ }
+}
+
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..6826e61
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,17 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..8cadffd
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Dec 16 21:12:52 CST 2021
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$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=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# 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
+ ;;
+ 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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@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 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=
+
+@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 init
+
+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 init
+
+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
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+: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 %CMD_LINE_ARGS%
+
+: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/local.properties b/local.properties
new file mode 100644
index 0000000..9fffe5f
--- /dev/null
+++ b/local.properties
@@ -0,0 +1,10 @@
+## This file is automatically generated by Android Studio.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file should *NOT* be checked into Version Control Systems,
+# as it contains information specific to your local configuration.
+#
+# Location of the SDK. This is only used by Gradle.
+# For customization when using a Version Control System, please read the
+# header note.
+sdk.dir=/Users/milk/Library/Android/sdk
\ No newline at end of file
diff --git a/localRepo/top/niunaijun/blackobfuscator/plugin/1.0.0/plugin-1.0.0.jar.md5 b/localRepo/top/niunaijun/blackobfuscator/plugin/1.0.0/plugin-1.0.0.jar.md5
new file mode 100644
index 0000000..6b20fc3
--- /dev/null
+++ b/localRepo/top/niunaijun/blackobfuscator/plugin/1.0.0/plugin-1.0.0.jar.md5
@@ -0,0 +1 @@
+eb5248e2716058acae2a9dd02383c2b9
\ No newline at end of file
diff --git a/localRepo/top/niunaijun/blackobfuscator/plugin/1.0.0/plugin-1.0.0.jar.sha1 b/localRepo/top/niunaijun/blackobfuscator/plugin/1.0.0/plugin-1.0.0.jar.sha1
new file mode 100644
index 0000000..8d0e816
--- /dev/null
+++ b/localRepo/top/niunaijun/blackobfuscator/plugin/1.0.0/plugin-1.0.0.jar.sha1
@@ -0,0 +1 @@
+ed2863a8212fcb1c0613c75573fc455c62ae2174
\ No newline at end of file
diff --git a/localRepo/top/niunaijun/blackobfuscator/plugin/1.0.0/plugin-1.0.0.pom b/localRepo/top/niunaijun/blackobfuscator/plugin/1.0.0/plugin-1.0.0.pom
new file mode 100644
index 0000000..e5429d4
--- /dev/null
+++ b/localRepo/top/niunaijun/blackobfuscator/plugin/1.0.0/plugin-1.0.0.pom
@@ -0,0 +1,34 @@
+
+
+ 4.0.0
+ top.niunaijun.blackobfuscator
+ plugin
+ 1.0.0
+
+
+ com.android.tools.build
+ gradle
+ 3.3.1
+ runtime
+
+
+ com.android.tools.build
+ transform-api
+ 1.5.0
+ runtime
+
+
+ commons-io
+ commons-io
+ 2.5
+ runtime
+
+
+ com.github.CodingGay.BlackObfuscator
+ dex-tools
+ 1.0.3
+ runtime
+
+
+
diff --git a/localRepo/top/niunaijun/blackobfuscator/plugin/1.0.0/plugin-1.0.0.pom.md5 b/localRepo/top/niunaijun/blackobfuscator/plugin/1.0.0/plugin-1.0.0.pom.md5
new file mode 100644
index 0000000..d892484
--- /dev/null
+++ b/localRepo/top/niunaijun/blackobfuscator/plugin/1.0.0/plugin-1.0.0.pom.md5
@@ -0,0 +1 @@
+58be27900eade6fbad79a9328c336ad6
\ No newline at end of file
diff --git a/localRepo/top/niunaijun/blackobfuscator/plugin/1.0.0/plugin-1.0.0.pom.sha1 b/localRepo/top/niunaijun/blackobfuscator/plugin/1.0.0/plugin-1.0.0.pom.sha1
new file mode 100644
index 0000000..d4baeba
--- /dev/null
+++ b/localRepo/top/niunaijun/blackobfuscator/plugin/1.0.0/plugin-1.0.0.pom.sha1
@@ -0,0 +1 @@
+4d286fbaa7f641cab7e669d781b564c0db4cf0fd
\ No newline at end of file
diff --git a/localRepo/top/niunaijun/blackobfuscator/plugin/maven-metadata.xml b/localRepo/top/niunaijun/blackobfuscator/plugin/maven-metadata.xml
new file mode 100644
index 0000000..9fcb19b
--- /dev/null
+++ b/localRepo/top/niunaijun/blackobfuscator/plugin/maven-metadata.xml
@@ -0,0 +1,12 @@
+
+
+ top.niunaijun.blackobfuscator
+ plugin
+
+ 1.0.0
+
+ 1.0.0
+
+ 20211217093825
+
+
diff --git a/localRepo/top/niunaijun/blackobfuscator/plugin/maven-metadata.xml.md5 b/localRepo/top/niunaijun/blackobfuscator/plugin/maven-metadata.xml.md5
new file mode 100644
index 0000000..c5d81df
--- /dev/null
+++ b/localRepo/top/niunaijun/blackobfuscator/plugin/maven-metadata.xml.md5
@@ -0,0 +1 @@
+5767fd64899b22b1d9fbfae502f7bfbb
\ No newline at end of file
diff --git a/localRepo/top/niunaijun/blackobfuscator/plugin/maven-metadata.xml.sha1 b/localRepo/top/niunaijun/blackobfuscator/plugin/maven-metadata.xml.sha1
new file mode 100644
index 0000000..9393ba9
--- /dev/null
+++ b/localRepo/top/niunaijun/blackobfuscator/plugin/maven-metadata.xml.sha1
@@ -0,0 +1 @@
+28c12c615c6034b1d384ef8a5c5a7282dfc2b942
\ No newline at end of file
diff --git a/plugin/.gitignore b/plugin/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/plugin/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/plugin/build.gradle b/plugin/build.gradle
new file mode 100644
index 0000000..5779aef
--- /dev/null
+++ b/plugin/build.gradle
@@ -0,0 +1,37 @@
+apply plugin: 'groovy'
+//apply plugin: 'maven'
+apply plugin: 'com.github.dcendents.android-maven'
+
+group='top.niunaijun.blackobfuscator'
+dependencies {
+ implementation "com.android.tools.build:gradle:3.3.1"
+ implementation "com.android.tools.build:transform-api:1.5.0"
+ implementation "commons-io:commons-io:2.5"
+ //gradle sdk
+ implementation gradleApi()
+ //groovy sdk
+ implementation localGroovy()
+ implementation "com.github.CodingGay.BlackObfuscator:dex-tools:1.0.3"
+}
+
+repositories {
+ mavenCentral()
+ maven {
+ url uri('localRepo')
+ }
+}
+
+group='top.niunaijun.blackobfuscator'
+version='1.0.0'
+//uploadArchives {
+// repositories {
+// mavenDeployer {
+// //提交到远程服务器:
+// // repository(url: "http://www.xxx.com/repos") {
+// // authentication(userName: "admin", password: "admin")
+// // }
+// //本地的Maven地址设置为E:/Maven
+// repository(url: uri('../localRepo'))
+// }
+// }
+//}
\ No newline at end of file
diff --git a/plugin/src/main/groovy/top/niunaijun/blackobfuscator/BlackObfuscatorExtension.groovy b/plugin/src/main/groovy/top/niunaijun/blackobfuscator/BlackObfuscatorExtension.groovy
new file mode 100644
index 0000000..52b7910
--- /dev/null
+++ b/plugin/src/main/groovy/top/niunaijun/blackobfuscator/BlackObfuscatorExtension.groovy
@@ -0,0 +1,22 @@
+package top.niunaijun.blackobfuscator
+import org.gradle.api.Project
+
+class BlackObfuscatorExtension {
+ boolean enabled = false
+ int depth = 1
+ String[] obfClass = []
+
+ BlackObfuscatorExtension(Project project) {
+
+ }
+
+
+ @Override
+ public String toString() {
+ return "BlackObfuscatorExtension{" +
+ "enabled=" + enabled +
+ ", depth=" + depth +
+ ", obfClass=" + Arrays.toString(obfClass) +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/plugin/src/main/groovy/top/niunaijun/blackobfuscator/ObfInject.groovy b/plugin/src/main/groovy/top/niunaijun/blackobfuscator/ObfInject.groovy
new file mode 100644
index 0000000..ba4b017
--- /dev/null
+++ b/plugin/src/main/groovy/top/niunaijun/blackobfuscator/ObfInject.groovy
@@ -0,0 +1,219 @@
+package top.niunaijun.blackobfuscator
+
+import com.android.dex.util.FileUtils
+import com.android.dx.command.Main
+import com.googlecode.d2j.converter.IR2JConverter
+import com.googlecode.d2j.converter.J2IRConverter
+import com.googlecode.dex2jar.ir.IrMethod
+import com.googlecode.dex2jar.ir.ts.AggTransformer
+import com.googlecode.dex2jar.ir.ts.CleanLabel
+import com.googlecode.dex2jar.ir.ts.DeadCodeTransformer
+import com.googlecode.dex2jar.ir.ts.ExceptionHandlerTrim
+import com.googlecode.dex2jar.ir.ts.Ir2JRegAssignTransformer
+import com.googlecode.dex2jar.ir.ts.NewTransformer
+import com.googlecode.dex2jar.ir.ts.NpeTransformer
+import com.googlecode.dex2jar.ir.ts.RemoveConstantFromSSA
+import com.googlecode.dex2jar.ir.ts.RemoveLocalFromSSA
+import com.googlecode.dex2jar.ir.ts.TypeTransformer
+import com.googlecode.dex2jar.ir.ts.UnSSATransformer
+import com.googlecode.dex2jar.ir.ts.VoidInvokeTransformer
+import com.googlecode.dex2jar.ir.ts.ZeroTransformer
+import com.googlecode.dex2jar.ir.ts.array.FillArrayTransformer
+import com.googlecode.dex2jar.tools.Dex2jarCmd
+import org.gradle.api.Project
+import org.jf.CloseUtils
+import org.jf.DexLib2Utils
+import org.objectweb.asm2.ClassReader
+import org.objectweb.asm2.ClassWriter
+import org.objectweb.asm2.tree.ClassNode
+import org.objectweb.asm2.tree.MethodNode
+import top.niunaijun.blackobfuscator.core.utils.zip.UtilsZip
+import top.niunaijun.obfuscator.ObfuscatorConfiguration
+
+class ObfInject {
+ private Project mProject
+ private BlackObfuscatorExtension mObfuscatorExtension
+
+ protected final CleanLabel T_cleanLabel = new CleanLabel();
+ protected final Ir2JRegAssignTransformer T_ir2jRegAssign = new Ir2JRegAssignTransformer();
+ protected final NewTransformer T_new = new NewTransformer();
+ protected final RemoveConstantFromSSA T_removeConst = new RemoveConstantFromSSA();
+ protected final RemoveLocalFromSSA T_removeLocal = new RemoveLocalFromSSA();
+ protected final ExceptionHandlerTrim T_trimEx = new ExceptionHandlerTrim();
+ protected final TypeTransformer T_type = new TypeTransformer();
+ protected final DeadCodeTransformer T_deadCode = new DeadCodeTransformer();
+ protected final FillArrayTransformer T_fillArray = new FillArrayTransformer();
+ protected final AggTransformer T_agg = new AggTransformer();
+ protected final UnSSATransformer T_unssa = new UnSSATransformer();
+ protected final ZeroTransformer T_zero = new ZeroTransformer();
+ protected final VoidInvokeTransformer T_voidInvoke = new VoidInvokeTransformer();
+ protected final NpeTransformer T_npe = new NpeTransformer();
+
+ ObfInject(Project project) {
+ mProject = project
+ mObfuscatorExtension = ObfPlugin.sObfuscatorExtension
+ }
+
+ void addJar(String jar) {
+ }
+
+ void inject(String path) {
+ if (!mObfuscatorExtension.enabled) {
+ return;
+ }
+
+ File dir = new File(path)
+
+ if (dir.isDirectory()) {
+ dir.eachFileRecurse { File file ->
+ if (file.isFile() && file.getAbsolutePath().endsWith(".class")) {
+ byte[] data = FileUtils.readFile(file)
+ ClassNode cn = readClassNode(data)
+ reBuildClass(cn)
+ createFile(file.getAbsolutePath(), toByteArray(cn))
+ }
+ }
+ fixClass(dir)
+ }
+ }
+
+ private void fixClass(File dir) {
+ File outDex = new File(dir, "obf.dex")
+ File fixDex = new File(dir, "obf_fix.dex")
+ File outJar = new File(dir, "obf.jar")
+ outDex.delete()
+ fixDex.delete()
+ outJar.delete()
+ Main.main("--dex", "--output=" + outDex.getAbsolutePath(), dir.getAbsolutePath())
+ if (outDex.exists()) {
+ DexLib2Utils.saveDex(outDex, fixDex)
+ }
+
+ if (fixDex.exists()) {
+ new Dex2jarCmd(null).doMain(
+ "-f",
+ "-o", outJar.getAbsolutePath(),
+ fixDex.getAbsolutePath());
+ }
+ if (outJar.exists()) {
+ UtilsZip.release(outJar.getAbsolutePath(), dir.getAbsolutePath(), new UtilsZip.UnzipCallback() {
+ @Override
+ boolean accept(String name) {
+ def newName = name.replace("/", ".")
+ for (String accept : mObfuscatorExtension.obfClass) {
+ if (newName.contains(accept)) {
+ return true
+ }
+ }
+ return false
+ }
+ })
+ }
+ outDex.delete()
+ fixDex.delete()
+ outJar.delete()
+ }
+
+ private void reBuildClass(ClassNode cn) {
+ for (Iterator it = cn.methods.iterator(); it.hasNext(); ) {
+ MethodNode m = it.next();
+ try {
+ IrMethod irMethod = J2IRConverter.convert(cn.name, m);
+ opt(irMethod);
+ // convert ir to m3
+ MethodNode m3 = new MethodNode();
+ m3.tryCatchBlocks = new ArrayList<>();
+ new IR2JConverter()
+ .ir(irMethod)
+ .asm(m3)
+ .obf(new ObfuscatorConfiguration() {
+ @Override
+ protected int getObfDepth() {
+ return mObfuscatorExtension.depth;
+ }
+
+ @Override
+ protected boolean accept(String className, String methodName) {
+ for (String accept : mObfuscatorExtension.obfClass) {
+ if (className.contains(accept)) {
+ return true
+ }
+ }
+ return false
+ }
+ })
+ .convert();
+
+ // copy back m3 to m
+ m.maxLocals = -1;
+ m.maxLocals = -1;
+ m.instructions = m3.instructions;
+ m.tryCatchBlocks = m3.tryCatchBlocks;
+ m.localVariables = null;
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ void opt(IrMethod irMethod) {
+ T_deadCode.transform(irMethod);
+ T_cleanLabel.transform(irMethod);
+ T_removeLocal.transform(irMethod);
+ T_removeConst.transform(irMethod);
+ T_zero.transform(irMethod);
+ if (T_npe.transformReportChanged(irMethod)) {
+ T_deadCode.transform(irMethod);
+ T_removeLocal.transform(irMethod);
+ T_removeConst.transform(irMethod);
+ }
+ T_new.transform(irMethod);
+ T_fillArray.transform(irMethod);
+ T_agg.transform(irMethod);
+ T_voidInvoke.transform(irMethod);
+
+ T_type.transform(irMethod);
+ T_unssa.transform(irMethod);
+ T_trimEx.transform(irMethod);
+ T_ir2jRegAssign.transform(irMethod);
+ }
+
+ private byte[] toByteArray(ClassNode cn) {
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+ cn.accept(cw);
+ return cw.toByteArray();
+ }
+
+
+ private ClassNode readClassNode(byte[] data) {
+ ClassReader cr = new ClassReader(data);
+ ClassNode cn = new ClassNode();
+ cr.accept(cn, ClassReader.EXPAND_FRAMES | ClassReader.SKIP_FRAMES);
+ return cn
+ }
+
+ private static boolean createFile(String path, byte[] content) {
+ FileOutputStream fos = null;
+ try {
+ File file = new File(path);
+ if (file.exists()) {
+ boolean del = file.delete();
+ if (!del) {
+ return false;
+ }
+ }
+ if (!file.getParentFile().exists()) {
+ file.getParentFile().mkdirs();
+ }
+ fos = new FileOutputStream(path);
+ fos.write(content);
+ fos.flush();
+ return file.exists();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ } finally {
+ CloseUtils.close(fos);
+ }
+ }
+}
diff --git a/plugin/src/main/groovy/top/niunaijun/blackobfuscator/ObfPlugin.groovy b/plugin/src/main/groovy/top/niunaijun/blackobfuscator/ObfPlugin.groovy
new file mode 100644
index 0000000..7159c61
--- /dev/null
+++ b/plugin/src/main/groovy/top/niunaijun/blackobfuscator/ObfPlugin.groovy
@@ -0,0 +1,29 @@
+package top.niunaijun.blackobfuscator
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+import com.android.build.gradle.AppExtension
+
+public class ObfPlugin implements Plugin {
+ private String PLUGIN_NAME = "BlackObfuscator"
+ private Project mProject
+ public static BlackObfuscatorExtension sObfuscatorExtension
+
+ void apply(Project project) {
+ this.mProject = project
+ def android = project.extensions.getByType(AppExtension)
+ project.configurations.create(PLUGIN_NAME).extendsFrom(project.configurations.compile)
+ sObfuscatorExtension = project.extensions.create(PLUGIN_NAME, BlackObfuscatorExtension, project)
+
+ project.afterEvaluate {
+ System.out.println("=====BlackObfuscator=====")
+ System.out.println(sObfuscatorExtension.toString())
+ System.out.println("=========================")
+ }
+
+ //注册一个Transform
+ def classTransform = new ObfTransform(project)
+ android.registerTransform(classTransform)
+ }
+}
\ No newline at end of file
diff --git a/plugin/src/main/groovy/top/niunaijun/blackobfuscator/ObfTransform.java b/plugin/src/main/groovy/top/niunaijun/blackobfuscator/ObfTransform.java
new file mode 100644
index 0000000..a2bce93
--- /dev/null
+++ b/plugin/src/main/groovy/top/niunaijun/blackobfuscator/ObfTransform.java
@@ -0,0 +1,83 @@
+package top.niunaijun.blackobfuscator;
+
+
+import com.android.build.api.transform.Context;
+import com.android.build.api.transform.DirectoryInput;
+import com.android.build.api.transform.Format;
+import com.android.build.api.transform.JarInput;
+import com.android.build.api.transform.QualifiedContent;
+import com.android.build.api.transform.Transform;
+import com.android.build.api.transform.TransformException;
+import com.android.build.api.transform.TransformInput;
+import com.android.build.api.transform.TransformOutputProvider;
+import com.android.build.gradle.internal.pipeline.TransformManager;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.io.FileUtils;
+import org.gradle.api.Project;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Set;
+
+public class ObfTransform extends Transform {
+
+ private Project mProject;
+ private BlackObfuscatorExtension mObfuscatorExtension;
+
+ public ObfTransform(Project p) {
+ this.mProject = p;
+ }
+
+ @Override
+ public String getName() {
+ return "BlackObfuscatorTransform";
+ }
+
+ @Override
+ public Set getInputTypes() {
+ return TransformManager.CONTENT_CLASS;
+ }
+
+ @Override
+ public Set super QualifiedContent.Scope> getScopes() {
+ return TransformManager.SCOPE_FULL_PROJECT;
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return false;
+ }
+
+ @Override
+ public void transform(Context context,
+ Collection inputs,
+ Collection referencedInputs,
+ TransformOutputProvider outputProvider,
+ boolean isIncremental) throws IOException, TransformException, InterruptedException {
+// System.out.println("in aop transform");
+ ObfInject inject = new ObfInject(mProject);
+ for (TransformInput input : inputs) {
+ for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
+ inject.inject(directoryInput.getFile().getAbsolutePath());
+ File name = outputProvider.getContentLocation(directoryInput.getName(), directoryInput.getContentTypes(), directoryInput.getScopes(), Format.DIRECTORY);
+ FileUtils.copyDirectory(directoryInput.getFile(), name);
+// FileUtils.deleteDirectory(directoryInput.getFile());
+// System.out.println("transform dir: " + name.getAbsolutePath());
+ }
+
+ for (JarInput jarInput : input.getJarInputs()) {
+ String jarName = jarInput.getName();
+ String md5Name = DigestUtils.md5Hex(jarInput.getFile().getAbsolutePath());
+ if (jarName.endsWith(".jar")) {
+ jarName = jarName.substring(0, jarName.length() - 4);
+ }
+ File name = outputProvider.getContentLocation(jarName + md5Name, jarInput.getContentTypes(), jarInput.getScopes(), Format.JAR);
+ FileUtils.copyFile(jarInput.getFile(), name);
+// System.out.println("transform jar: " + jarInput.getFile());
+ inject.addJar(jarInput.getFile().getAbsolutePath());
+ }
+ }
+ }
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/MyClass.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/MyClass.java
new file mode 100644
index 0000000..d15acf5
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/MyClass.java
@@ -0,0 +1,4 @@
+package top.niunaijun.blackobfuscator.core;
+
+public class MyClass {
+}
\ No newline at end of file
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/CopyEntryCallback.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/CopyEntryCallback.java
new file mode 100644
index 0000000..1dd153c
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/CopyEntryCallback.java
@@ -0,0 +1,18 @@
+package top.niunaijun.blackobfuscator.core.utils.zip;
+
+public interface CopyEntryCallback {
+ /**
+ * @param current 当前zipEntry序号
+ * @param total ZipEntry总个数
+ * @return Null不进行复制 否则进行复制
+ */
+ ZipEntry filter(ZipEntry zipEntry, int current, int total);
+
+ /**
+ * @param current 当前已解压字节数
+ * @param total 总字节数
+ */
+ void onProgress(long current, long total);
+
+ void done(ZipEntry zipEntry);
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ExtractCallback.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ExtractCallback.java
new file mode 100644
index 0000000..685fc9d
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ExtractCallback.java
@@ -0,0 +1,20 @@
+package top.niunaijun.blackobfuscator.core.utils.zip;
+
+import java.io.File;
+
+public interface ExtractCallback {
+ /**
+ * @param current 当前Entry序号
+ * @param total 总Entry个数
+ * @return Null不进行解压 否则进行解压
+ */
+ File filter(ZipEntry zipEntry, int current, int total);
+
+ /**
+ * @param current 当前文件已解压字节数
+ * @param total 当前文件总字节数
+ */
+ void onProgress(long current, long total);
+
+ void done(ZipEntry zipEntry, File file);
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/JarMarker.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/JarMarker.java
new file mode 100644
index 0000000..6a5b8e4
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/JarMarker.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.
+ *
+ */
+
+package top.niunaijun.blackobfuscator.core.utils.zip;
+
+import top.niunaijun.blackobfuscator.core.utils.zip.extrafield.ZipExtraField;
+
+import java.util.zip.ZipException;
+
+
+/**
+ * If this extra field is added as the very first extra field of the
+ * archive, Solaris will consider it an executable jar file.
+ *
+ * @since Ant 1.6.3
+ */
+public final class JarMarker implements ZipExtraField {
+
+ private static final ZipShort ID = new ZipShort(0xCAFE);
+ private static final ZipShort NULL = new ZipShort(0);
+ private static final byte[] NO_BYTES = new byte[0];
+ private static final JarMarker DEFAULT = new JarMarker();
+
+ /**
+ * No-arg constructor
+ */
+ public JarMarker() {
+ // empty
+ }
+
+ /**
+ * Since JarMarker is stateless we can always use the same instance.
+ *
+ * @return the DEFAULT jarmaker.
+ */
+ public static JarMarker getInstance() {
+ return DEFAULT;
+ }
+
+ /**
+ * The Header-ID.
+ *
+ * @return the header id
+ */
+ public ZipShort getHeaderId() {
+ return ID;
+ }
+
+ /**
+ * Length of the extra field in the local file data - without
+ * Header-ID or length specifier.
+ *
+ * @return 0
+ */
+ public ZipShort getLocalFileDataLength() {
+ return NULL;
+ }
+
+ /**
+ * Length of the extra field in the central directory - without
+ * Header-ID or length specifier.
+ *
+ * @return 0
+ */
+ public ZipShort getCentralDirectoryLength() {
+ return NULL;
+ }
+
+ /**
+ * The actual data to put into local file data - without Header-ID
+ * or length specifier.
+ *
+ * @return the data
+ * @since 1.1
+ */
+ public byte[] getLocalFileDataData() {
+ return NO_BYTES;
+ }
+
+ /**
+ * The actual data to put central directory - without Header-ID or
+ * length specifier.
+ *
+ * @return the data
+ */
+ public byte[] getCentralDirectoryData() {
+ return NO_BYTES;
+ }
+
+ /**
+ * Populate data from this array as if it was in local file data.
+ *
+ * @param data an array of bytes
+ * @param offset the start offset
+ * @param length the number of bytes in the array from offset
+ * @throws ZipException on error
+ */
+ public void parseFromLocalFileData(byte[] data, int offset, int length)
+ throws ZipException {
+ if (length != 0) {
+ throw new ZipException("JarMarker doesn't expect any data");
+ }
+ }
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/UnixStat.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/UnixStat.java
new file mode 100644
index 0000000..2592fc0
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/UnixStat.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.
+ *
+ */
+
+package top.niunaijun.blackobfuscator.core.utils.zip;
+
+/**
+ * Constants from stat.h on Unix systems.
+ */
+// CheckStyle:InterfaceIsTypeCheck OFF - backward compatible
+@SuppressWarnings("OctalInteger")
+public interface UnixStat {
+
+ /**
+ * Bits used for permissions (and sticky bit)
+ *
+ * @since 1.1
+ */
+ int PERM_MASK = 07777;
+ /**
+ * Indicates symbolic links.
+ *
+ * @since 1.1
+ */
+ int LINK_FLAG = 0120000;
+ /**
+ * Indicates plain files.
+ *
+ * @since 1.1
+ */
+ int FILE_FLAG = 0100000;
+ /**
+ * Indicates directories.
+ *
+ * @since 1.1
+ */
+ int DIR_FLAG = 040000;
+
+ // ----------------------------------------------------------
+ // somewhat arbitrary choices that are quite common for shared
+ // installations
+ // -----------------------------------------------------------
+
+ /**
+ * Default permissions for symbolic links.
+ *
+ * @since 1.1
+ */
+ int DEFAULT_LINK_PERM = 0777;
+ /**
+ * Default permissions for directories.
+ *
+ * @since 1.1
+ */
+ int DEFAULT_DIR_PERM = 0755;
+ /**
+ * Default permissions for plain files.
+ *
+ * @since 1.1
+ */
+ int DEFAULT_FILE_PERM = 0644;
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/UtilsZip.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/UtilsZip.java
new file mode 100644
index 0000000..ec2a053
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/UtilsZip.java
@@ -0,0 +1,111 @@
+package top.niunaijun.blackobfuscator.core.utils.zip;
+
+
+import org.apache.commons.io.FileUtils;
+import org.jf.CloseUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+
+/**
+ * Created by sunwanquan on 2019/6/18.
+ * * ∧_∧
+ * (`・ω・∥
+ * 丶 つ0
+ * しーJ
+ * 此处无Bug
+ */
+public class UtilsZip {
+
+ /**
+ * 解压ZIP
+ *
+ * @param zipPath zip路径
+ * @param outPath 输出目录
+ * @return 是否成功
+ */
+ public static boolean release(String zipPath, String outPath, UnzipCallback callback) {
+ InputStream inputStream = null;
+ OutputStream outputStream = null;
+ try {
+ File out = new File(outPath);
+ if (out.exists()) {
+ if (out.isFile())
+ throw new RuntimeException("outPath is a file!");
+ } else {
+ out.mkdirs();
+ }
+
+ ZipFile zipFile = new ZipFile(zipPath);
+ Enumeration entries = zipFile.getEntries();
+ while (entries.hasMoreElements()) {
+ try {
+ ZipEntry zipEntry = (ZipEntry) entries.nextElement();
+ if (callback != null) {
+ if (!callback.accept(zipEntry.getName())) {
+ continue;
+ }
+ }
+ File outFile = new File(outPath + File.separator + zipEntry.getName());
+
+ if (zipEntry.isDirectory()) {
+ outFile.mkdirs();
+ } else {
+ if (!outFile.getParentFile().exists()) {
+ outFile.getParentFile().mkdirs();
+ }
+ outFile.createNewFile();
+ inputStream = zipFile.getInputStream(zipEntry);
+ outputStream = new FileOutputStream(outFile);
+ byte[] bytes = new byte[1024 * 5];
+ int read;
+ while ((read = inputStream.read(bytes)) != -1) {
+ outputStream.write(bytes, 0, read);
+ }
+ }
+ } finally {
+ CloseUtils.close(inputStream, outputStream);
+ }
+ }
+ return true;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ } finally {
+ CloseUtils.close(inputStream, outputStream);
+ }
+ }
+
+ public interface UnzipCallback {
+ boolean accept(String name);
+ }
+
+ private static boolean isExist(List strings, String s) {
+ for (String str : strings) {
+ if (s.startsWith(str)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static InputStream getZipEntryInputStream(String zipFile, String entryName) {
+ ZipFile zipFile1 = null;
+ try {
+ zipFile1 = new ZipFile(zipFile);
+ return zipFile1.getInputStream(zipFile1.getEntry(entryName));
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+// CloseUtils.close(zipFile1);
+ }
+ return null;
+ }
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipCallback.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipCallback.java
new file mode 100644
index 0000000..f705c5d
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipCallback.java
@@ -0,0 +1,5 @@
+package top.niunaijun.blackobfuscator.core.utils.zip;
+
+public interface ZipCallback {
+ void onProgress(long current, long total);
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipEntry.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipEntry.java
new file mode 100644
index 0000000..cd18150
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipEntry.java
@@ -0,0 +1,601 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.
+ *
+ */
+
+package top.niunaijun.blackobfuscator.core.utils.zip;
+
+
+import top.niunaijun.blackobfuscator.core.utils.zip.extrafield.CentralDirectoryParsingZipExtraField;
+import top.niunaijun.blackobfuscator.core.utils.zip.extrafield.ExtraFieldUtils;
+import top.niunaijun.blackobfuscator.core.utils.zip.extrafield.UnparseableExtraFieldData;
+import top.niunaijun.blackobfuscator.core.utils.zip.extrafield.ZipExtraField;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.zip.ZipException;
+
+/**
+ * Extension that adds better handling of extra fields and provides
+ * access to the internal and external file attributes.
+ *
+ *
The extra data is expected to follow the recommendation of
+ * the .ZIP File Format Specification created by PKWARE Inc. :
+ *
+ * - the extra byte array consists of a sequence of extra fields
+ * - each extra fields starts by a two byte header id followed by
+ * a two byte sequence holding the length of the remainder of
+ * data.
+ *
+ *
+ *
Any extra data that cannot be parsed by the rules above will be
+ * consumed as "unparseable" extra data and treated differently by the
+ * methods of this class. Versions prior to Apache Commons Compress
+ * 1.1 would have thrown an exception if any attempt was made to read
+ * or write extra data not conforming to the recommendation.
+ *
+ * @see
+ * .ZIP File Format Specification
+ */
+public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable {
+
+ public static final int PLATFORM_UNIX = 3;
+ public static final int PLATFORM_FAT = 0;
+ private static final int SHORT_MASK = 0xFFFF;
+ private static final int SHORT_SHIFT = 16;
+
+ private int internalAttributes = 0;
+ private int platform = PLATFORM_FAT;
+ private long externalAttributes = 0;
+ private LinkedHashMap extraFields = null;
+ private UnparseableExtraFieldData unparseableExtra = null;
+
+ private String name = null;
+ private String parent = null;
+ private String simpleName = null;
+
+ /**
+ * Creates a new zip entry with the specified name.
+ *
+ * @param name the name of the entry
+ * @since 1.1
+ */
+ public ZipEntry(String name) {
+ super(name);
+ setName(name);
+ }
+
+ /**
+ * Creates a new zip entry with fields taken from the specified zip entry.
+ *
+ * @param entry the entry to get fields from
+ * @throws ZipException on error
+ * @since 1.1
+ */
+ public ZipEntry(java.util.zip.ZipEntry entry) throws ZipException {
+ super(entry);
+ setName(entry.getName());
+ byte[] extra = entry.getExtra();
+ if (extra != null) {
+ setExtraFields(ExtraFieldUtils.parse(extra, true,
+ ExtraFieldUtils
+ .UnparseableExtraField.READ));
+ } else {
+ // initializes extra data to an empty byte array
+ setExtra();
+ }
+ }
+
+ /**
+ * Creates a new zip entry with fields taken from the specified zip entry.
+ *
+ * @param entry the entry to get fields from
+ * @throws ZipException on error
+ * @since 1.1
+ */
+ public ZipEntry(ZipEntry entry) throws ZipException {
+ this((java.util.zip.ZipEntry) entry);
+ setInternalAttributes(entry.getInternalAttributes());
+ setExternalAttributes(entry.getExternalAttributes());
+ setExtraFields(entry.getExtraFields(true));
+ }
+
+ /**
+ * @since 1.9
+ */
+ protected ZipEntry() {
+ super("");
+ }
+
+ /**
+ * Overwrite clone.
+ *
+ * @return a cloned copy of this ZipEntry
+ * @since 1.1
+ */
+ public Object clone() {
+ ZipEntry e = (ZipEntry) super.clone();
+
+ e.setInternalAttributes(getInternalAttributes());
+ e.setExternalAttributes(getExternalAttributes());
+ e.setExtraFields(getExtraFields(true));
+ return e;
+ }
+
+ /**
+ * Retrieves the internal file attributes.
+ *
+ * @return the internal file attributes
+ * @since 1.1
+ */
+ public int getInternalAttributes() {
+ return internalAttributes;
+ }
+
+ /**
+ * Sets the internal file attributes.
+ *
+ * @param value an int
value
+ * @since 1.1
+ */
+ public void setInternalAttributes(int value) {
+ internalAttributes = value;
+ }
+
+ /**
+ * Retrieves the external file attributes.
+ *
+ * @return the external file attributes
+ * @since 1.1
+ */
+ public long getExternalAttributes() {
+ return externalAttributes;
+ }
+
+ /**
+ * Sets the external file attributes.
+ *
+ * @param value an long
value
+ * @since 1.1
+ */
+ public void setExternalAttributes(long value) {
+ externalAttributes = value;
+ }
+
+ /**
+ * Sets Unix permissions in a way that is understood by Info-Zip's
+ * unzip command.
+ *
+ * @param mode an int
value
+ * @since Ant 1.5.2
+ */
+ @SuppressWarnings("OctalInteger")
+ public void setUnixMode(int mode) {
+ // CheckStyle:MagicNumberCheck OFF - no point
+ setExternalAttributes((mode << SHORT_SHIFT)
+ // MS-DOS read-only attribute
+ | ((mode & 0200) == 0 ? 1 : 0)
+ // MS-DOS directory flag
+ | (isDirectory() ? 0x10 : 0));
+ // CheckStyle:MagicNumberCheck ON
+ platform = PLATFORM_UNIX;
+ }
+
+ /**
+ * Unix permission.
+ *
+ * @return the unix permissions
+ * @since Ant 1.6
+ */
+ public int getUnixMode() {
+ return platform != PLATFORM_UNIX ? 0 :
+ (int) ((getExternalAttributes() >> SHORT_SHIFT) & SHORT_MASK);
+ }
+
+ /**
+ * Platform specification to put into the "version made
+ * by" part of the central file header.
+ *
+ * @return PLATFORM_FAT unless {@link #setUnixMode setUnixMode}
+ * has been called, in which case PLATORM_UNIX will be returned.
+ * @since Ant 1.5.2
+ */
+ public int getPlatform() {
+ return platform;
+ }
+
+ /**
+ * Set the platform (UNIX or FAT).
+ *
+ * @param platform an int
value - 0 is FAT, 3 is UNIX
+ * @since 1.9
+ */
+ protected void setPlatform(int platform) {
+ this.platform = platform;
+ }
+
+ /**
+ * Replaces all currently attached extra fields with the new array.
+ *
+ * @param fields an array of extra fields
+ * @since 1.1
+ */
+ public void setExtraFields(ZipExtraField[] fields) {
+ extraFields = new LinkedHashMap<>();
+ for (ZipExtraField field : fields) {
+ if (field instanceof UnparseableExtraFieldData) {
+ unparseableExtra = (UnparseableExtraFieldData) field;
+ } else {
+ extraFields.put(field.getHeaderId(), field);
+ }
+ }
+ setExtra();
+ }
+
+ /**
+ * Retrieves all extra fields that have been parsed successfully.
+ *
+ * @return an array of the extra fields
+ */
+ public ZipExtraField[] getExtraFields() {
+ return getExtraFields(false);
+ }
+
+ /**
+ * Retrieves extra fields.
+ *
+ * @param includeUnparseable whether to also return unparseable
+ * extra fields as {@link UnparseableExtraFieldData} if such data
+ * exists.
+ * @return an array of the extra fields
+ * @since 1.1
+ */
+ public ZipExtraField[] getExtraFields(boolean includeUnparseable) {
+ if (extraFields == null) {
+ return !includeUnparseable || unparseableExtra == null
+ ? new ZipExtraField[0]
+ : new ZipExtraField[]{unparseableExtra};
+ }
+ List result = new ArrayList<>(extraFields.values());
+ if (includeUnparseable && unparseableExtra != null) {
+ result.add(unparseableExtra);
+ }
+ return result.toArray(new ZipExtraField[0]);
+ }
+
+ /**
+ * Adds an extra field - replacing an already present extra field
+ * of the same type.
+ *
+ *
If no extra field of the same type exists, the field will be
+ * added as last field.
+ *
+ * @param ze an extra field
+ * @since 1.1
+ */
+ public void addExtraField(ZipExtraField ze) {
+ if (ze instanceof UnparseableExtraFieldData) {
+ unparseableExtra = (UnparseableExtraFieldData) ze;
+ } else {
+ if (extraFields == null) {
+ extraFields = new LinkedHashMap<>();
+ }
+ extraFields.put(ze.getHeaderId(), ze);
+ }
+ setExtra();
+ }
+
+ /**
+ * Adds an extra field - replacing an already present extra field
+ * of the same type.
+ *
+ *
The new extra field will be the first one.
+ *
+ * @param ze an extra field
+ * @since 1.1
+ */
+ public void addAsFirstExtraField(ZipExtraField ze) {
+ if (ze instanceof UnparseableExtraFieldData) {
+ unparseableExtra = (UnparseableExtraFieldData) ze;
+ } else {
+ LinkedHashMap copy = extraFields;
+ extraFields = new LinkedHashMap<>();
+ extraFields.put(ze.getHeaderId(), ze);
+ if (copy != null) {
+ copy.remove(ze.getHeaderId());
+ extraFields.putAll(copy);
+ }
+ }
+ setExtra();
+ }
+
+ /**
+ * Remove an extra field.
+ *
+ * @param type the type of extra field to remove
+ * @since 1.1
+ */
+ public void removeExtraField(ZipShort type) {
+ if (extraFields == null) {
+ throw new java.util.NoSuchElementException();
+ }
+ if (extraFields.remove(type) == null) {
+ throw new java.util.NoSuchElementException();
+ }
+ setExtra();
+ }
+
+ /**
+ * Removes unparseable extra field data.
+ */
+ public void removeUnparseableExtraFieldData() {
+ if (unparseableExtra == null) {
+ throw new java.util.NoSuchElementException();
+ }
+ unparseableExtra = null;
+ setExtra();
+ }
+
+ /**
+ * Looks up an extra field by its header id.
+ *
+ * @return null if no such field exists.
+ */
+ public ZipExtraField getExtraField(ZipShort type) {
+ if (extraFields != null) {
+ return extraFields.get(type);
+ }
+ return null;
+ }
+
+ /**
+ * Looks up extra field data that couldn't be parsed correctly.
+ *
+ * @return null if no such field exists.
+ */
+ public UnparseableExtraFieldData getUnparseableExtraFieldData() {
+ return unparseableExtra;
+ }
+
+ /**
+ * Parses the given bytes as extra field data and consumes any
+ * unparseable data as an {@link UnparseableExtraFieldData}
+ * instance.
+ *
+ * @param extra an array of bytes to be parsed into extra fields
+ * @throws RuntimeException if the bytes cannot be parsed
+ * @throws RuntimeException on error
+ * @since 1.1
+ */
+ public void setExtra(byte[] extra) throws RuntimeException {
+ try {
+ ZipExtraField[] local =
+ ExtraFieldUtils.parse(extra, true,
+ ExtraFieldUtils.UnparseableExtraField.READ);
+ mergeExtraFields(local, true);
+ } catch (Exception e) {
+ // actually this is not be possible as of Ant 1.8.1
+ throw new RuntimeException("Error parsing extra fields for entry: "
+ + getName() + " - " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Unfortunately {@link java.util.zip.ZipOutputStream
+ * java.util.zip.ZipOutputStream} seems to access the extra data
+ * directly, so overriding getExtra doesn't help - we need to
+ * modify super's data directly.
+ *
+ * @since 1.1
+ */
+ protected void setExtra() {
+ super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getExtraFields(true)));
+ }
+
+ /**
+ * Sets the central directory part of extra fields.
+ */
+ public void setCentralDirectoryExtra(byte[] b) {
+ try {
+ ZipExtraField[] central =
+ ExtraFieldUtils.parse(b, false,
+ ExtraFieldUtils.UnparseableExtraField.READ);
+ mergeExtraFields(central, false);
+ } catch (Exception e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Retrieves the extra data for the local file data.
+ *
+ * @return the extra data for local file
+ * @since 1.1
+ */
+ public byte[] getLocalFileDataExtra() {
+ byte[] extra = getExtra();
+ return extra != null ? extra : new byte[0];
+ }
+
+ public Date getLastModifiedDate() {
+ return new Date(getTime());
+ }
+
+ /**
+ * Retrieves the extra data for the central directory.
+ *
+ * @return the central directory extra data
+ * @since 1.1
+ */
+ public byte[] getCentralDirectoryExtra() {
+ return ExtraFieldUtils.mergeCentralDirectoryData(getExtraFields(true));
+ }
+
+ /**
+ * Make this class work in JDK 1.1 like a 1.2 class.
+ *
+ *
This either stores the size for later usage or invokes
+ * setCompressedSize via reflection.
+ *
+ * @param size the size to use
+ * @since 1.2
+ * Use setCompressedSize directly.
+ */
+ public void setComprSize(long size) {
+ setCompressedSize(size);
+ }
+
+ /**
+ * Get the name of the entry.
+ *
+ * @return the entry name
+ * @since 1.9
+ */
+ public String getName() {
+ return name == null ? super.getName() : name;
+ }
+
+ public String getParent() {
+ return parent;
+ }
+
+ public String getSimpleName() {
+ return simpleName;
+ }
+
+ /**
+ * Is this entry a directory?
+ *
+ * @return true if the entry is a directory
+ * @since 1.10
+ */
+ public boolean isDirectory() {
+ return getName().endsWith("/");
+ }
+
+ /**
+ * Set the name of the entry.
+ *
+ * @param name the name to use
+ */
+ public void setName(String name) {
+ if (name != null && getPlatform() == PLATFORM_FAT
+ && !name.contains("/")) {
+ name = name.replace('\\', '/');
+ }
+ this.name = name;
+ setParentAndSimpleName();
+ }
+
+ private void setParentAndSimpleName() {
+ if (name == null || name.length() == 0)
+ return;
+ if (name.charAt(name.length() - 1) == '/') {
+ int index = name.lastIndexOf('/', name.length() - 2);
+ if (index == -1) {
+ simpleName = name.substring(0, name.length() - 1);
+ parent = null;
+ } else {
+ simpleName = name.substring(index + 1, name.length() - 1);
+ parent = name.substring(0, index + 1);
+ }
+ } else {
+ int index = name.lastIndexOf('/');
+ if (index == -1) {
+ simpleName = name;
+ parent = null;
+ } else {
+ simpleName = name.substring(index + 1);
+ parent = name.substring(0, index + 1);
+ }
+ }
+ }
+
+ private int hash = 0;
+ /**
+ * Get the hashCode of the entry.
+ * This uses the name as the hashcode.
+ *
+ * @return a hashcode.
+ * @since Ant 1.7
+ */
+ public int hashCode() {
+ // this method has severe consequences on performance. We cannot rely
+ // on the super.hashCode() method since super.getName() always return
+ // the empty string in the current implemention (there's no setter)
+ // so it is basically draining the performance of a hashmap lookup
+ int h = hash;
+ if (h == 0) {
+ h = getName().hashCode();
+ hash = h;
+ }
+ return h;
+ }
+
+ /**
+ * The equality method. In this case, the implementation returns 'this == o'
+ * which is basically the equals method of the Object class.
+ *
+ * @param obj the object to compare to
+ * @return true if this object is the same as o
+ * @since Ant 1.7
+ */
+ public boolean equals(Object obj) {
+ return this == obj;
+ }
+
+ /**
+ * If there are no extra fields, use the given fields as new extra
+ * data - otherwise merge the fields assuming the existing fields
+ * and the new fields stem from different locations inside the
+ * archive.
+ *
+ * @param f the extra fields to merge
+ * @param local whether the new fields originate from local data
+ */
+ private void mergeExtraFields(ZipExtraField[] f, boolean local)
+ throws ZipException {
+ if (extraFields == null) {
+ setExtraFields(f);
+ } else {
+ for (ZipExtraField aF : f) {
+ ZipExtraField existing;
+ if (aF instanceof UnparseableExtraFieldData) {
+ existing = unparseableExtra;
+ } else {
+ existing = getExtraField(aF.getHeaderId());
+ }
+ if (existing == null) {
+ addExtraField(aF);
+ } else {
+ if (local
+ || !(existing
+ instanceof CentralDirectoryParsingZipExtraField)) {
+ byte[] b = aF.getLocalFileDataData();
+ existing.parseFromLocalFileData(b, 0, b.length);
+ } else {
+ byte[] b = aF.getCentralDirectoryData();
+ ((CentralDirectoryParsingZipExtraField) existing)
+ .parseFromCentralDirectoryData(b, 0, b.length);
+ }
+ }
+ }
+ setExtra();
+ }
+ }
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipFile.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipFile.java
new file mode 100644
index 0000000..f588077
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipFile.java
@@ -0,0 +1,747 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.
+ *
+ */
+
+package top.niunaijun.blackobfuscator.core.utils.zip;
+
+
+import top.niunaijun.blackobfuscator.core.utils.zip.encoding.ZipEncoding;
+import top.niunaijun.blackobfuscator.core.utils.zip.encoding.ZipEncodingHelper;
+import top.niunaijun.blackobfuscator.core.utils.zip.extrafield.AbstractUnicodeExtraField;
+import top.niunaijun.blackobfuscator.core.utils.zip.extrafield.UnicodeCommentExtraField;
+import top.niunaijun.blackobfuscator.core.utils.zip.extrafield.UnicodePathExtraField;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.zip.CRC32;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+import java.util.zip.ZipException;
+
+/**
+ * Replacement for java.util.ZipFile
.
+ *
+ *
This class adds support for file name encodings other than UTF-8
+ * (which is required to work on ZIP files created by native zip tools
+ * and is able to skip a preamble like the one found in self
+ * extracting archives. Furthermore it returns instances of
+ * org.apache.tools.zip.ZipEntry
instead of
+ * java.util.zip.ZipEntry
.
+ *
+ *
It doesn't extend java.util.zip.ZipFile
as it would
+ * have to reimplement all methods anyway. Like
+ * java.util.ZipFile
, it uses RandomAccessFile under the
+ * covers and supports compressed and uncompressed entries.
+ *
+ *
The method signatures mimic the ones of
+ * java.util.zip.ZipFile
, with a couple of exceptions:
+ *
+ *
+ * - There is no getName method.
+ * - entries has been renamed to getEntries.
+ * - getEntries and getEntry return
+ *
org.apache.tools.zip.ZipEntry
instances.
+ * - close is allowed to throw IOException.
+ *
+ */
+public class ZipFile implements Closeable {
+ private static final int HASH_SIZE = 509;
+ private static final int SHORT = 2;
+ private static final int WORD = 4;
+ private static final int NIBLET_MASK = 0x0f;
+ private static final int BYTE_SHIFT = 8;
+ private static final int POS_0 = 0;
+ private static final int POS_1 = 1;
+ private static final int POS_2 = 2;
+ private static final int POS_3 = 3;
+
+ /**
+ * Maps ZipEntrys to Longs, recording the offsets of the local
+ * file headers.
+ */
+ private final Map entries = new HashMap<>(HASH_SIZE);
+
+ /**
+ * Maps String to ZipEntrys, name -> actual entry.
+ */
+ private final Map nameMap = new HashMap<>(HASH_SIZE);
+
+ private static final class OffsetEntry {
+ private long headerOffset = -1;
+ private long dataOffset = -1;
+ }
+
+ /**
+ * The zip encoding to use for filenames and the file comment.
+ */
+ private final ZipEncoding zipEncoding;
+
+ /**
+ * The actual data source.
+ */
+ private final RandomAccessFile archive;
+
+ /**
+ * Whether to look for and use Unicode extra fields.
+ */
+ private final boolean useUnicodeExtraFields;
+
+ /**
+ * Opens the given file for reading, assuming the platform's
+ * native encoding for file names.
+ *
+ * @param f the archive.
+ * @throws IOException if an error occurs while reading the file.
+ */
+ public ZipFile(File f) throws IOException {
+ this(f, null);
+ }
+
+ /**
+ * Opens the given file for reading, assuming the platform's
+ * native encoding for file names.
+ *
+ * @param name name of the archive.
+ * @throws IOException if an error occurs while reading the file.
+ */
+ public ZipFile(String name) throws IOException {
+ this(new File(name), null);
+ }
+
+ /**
+ * Opens the given file for reading, assuming the specified
+ * encoding for file names, scanning unicode extra fields.
+ *
+ * @param name name of the archive.
+ * @param encoding the encoding to use for file names
+ * @throws IOException if an error occurs while reading the file.
+ */
+ public ZipFile(String name, String encoding) throws IOException {
+ this(new File(name), encoding, true);
+ }
+
+ /**
+ * Opens the given file for reading, assuming the specified
+ * encoding for file names and scanning for unicode extra fields.
+ *
+ * @param f the archive.
+ * @param encoding the encoding to use for file names, use null
+ * for the platform's default encoding
+ * @throws IOException if an error occurs while reading the file.
+ */
+ public ZipFile(File f, String encoding) throws IOException {
+ this(f, encoding, true);
+ }
+
+ /**
+ * Opens the given file for reading, assuming the specified
+ * encoding for file names.
+ *
+ * @param f the archive.
+ * @param encoding the encoding to use for file names, use null
+ * for the platform's default encoding
+ * @param useUnicodeExtraFields whether to use InfoZIP Unicode
+ * Extra Fields (if present) to set the file names.
+ * @throws IOException if an error occurs while reading the file.
+ */
+ public ZipFile(File f, String encoding, boolean useUnicodeExtraFields)
+ throws IOException {
+ this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
+ this.useUnicodeExtraFields = useUnicodeExtraFields;
+ archive = new RandomAccessFile(f, "r");
+ boolean success = false;
+ try {
+ Map entriesWithoutUTF8Flag = populateFromCentralDirectory();
+ resolveLocalFileHeaderData(entriesWithoutUTF8Flag);
+ success = true;
+ } finally {
+ if (!success) {
+ try {
+ archive.close();
+ } catch (IOException e2) {
+ // swallow, throw the original exception instead
+ }
+ }
+ }
+ }
+
+ public ZipEncoding getZipEncoding() {
+ return zipEncoding;
+ }
+
+ public int getEntrySize() {
+ return entries.size();
+ }
+
+
+ /**
+ * The encoding to use for filenames and the file comment.
+ *
+ * @return null if using the platform's default character encoding.
+ */
+ public String getEncoding() {
+ return zipEncoding.getEncoding();
+ }
+
+ /**
+ * Closes the archive.
+ *
+ * @throws IOException if an error occurs closing the archive.
+ */
+ @Override
+ public void close() throws IOException {
+ archive.close();
+ }
+
+ /**
+ * close a zipfile quietly; throw no io fault, do nothing
+ * on a null parameter
+ *
+ * @param zipfile file to close, can be null
+ */
+ public static void closeQuietly(ZipFile zipfile) {
+ if (zipfile != null) {
+ try {
+ zipfile.close();
+ } catch (IOException e) {
+ //ignore
+ }
+ }
+ }
+
+ /**
+ * Returns all entries.
+ *
+ * @return all entries as {@link ZipEntry} instances
+ */
+ public Enumeration getEntries() {
+ return Collections.enumeration(entries.keySet());
+ }
+
+ /**
+ * Returns a named entry - or null
if no entry by
+ * that name exists.
+ *
+ * @param name name of the entry.
+ * @return the ZipEntry corresponding to the given name - or
+ * null
if not present.
+ */
+ public ZipEntry getEntry(String name) {
+ return nameMap.get(name);
+ }
+
+ /**
+ * Returns an InputStream for reading the contents of the given entry.
+ *
+ * @param ze the entry to get the stream for.
+ * @return a stream to read the entry from.
+ * @throws IOException if unable to getInstance an input stream from the zipenty
+ * @throws ZipException if the zipentry has an unsupported
+ * compression method
+ */
+ public InputStream getInputStream(ZipEntry ze)
+ throws IOException {
+ OffsetEntry offsetEntry = entries.get(ze);
+ if (offsetEntry == null) {
+ return null;
+ }
+ long start = offsetEntry.dataOffset;
+ BoundedInputStream bis =
+ new BoundedInputStream(start, ze.getCompressedSize());
+ switch (ze.getMethod()) {
+ case ZipEntry.STORED:
+ return bis;
+ case ZipEntry.DEFLATED:
+ bis.addDummy();
+ final Inflater inflater = new Inflater(true);
+ return new InflaterInputStream(bis, inflater) {
+ public void close() throws IOException {
+ super.close();
+ inflater.end();
+ }
+ };
+ default:
+ throw new ZipException("Found unsupported compression method "
+ + ze.getMethod());
+ }
+ }
+
+ public InputStream getRawInputStream(ZipEntry ze)
+ throws IOException {
+ OffsetEntry offsetEntry = entries.get(ze);
+ if (offsetEntry == null)
+ return null;
+ return new BoundedInputStream(offsetEntry.dataOffset, ze.getCompressedSize());
+ }
+
+ private static final int CFH_LEN =
+ /* version made by */ SHORT
+ /* version needed to extract */ + SHORT
+ /* general purpose bit flag */ + SHORT
+ /* compression method */ + SHORT
+ /* last mod file time */ + SHORT
+ /* last mod file date */ + SHORT
+ /* crc-32 */ + WORD
+ /* compressed size */ + WORD
+ /* uncompressed size */ + WORD
+ /* filename length */ + SHORT
+ /* extra field length */ + SHORT
+ /* file comment length */ + SHORT
+ /* disk number start */ + SHORT
+ /* internal file attributes */ + SHORT
+ /* external file attributes */ + WORD
+ /* relative offset of local header */ + WORD;
+
+ /**
+ * Reads the central directory of the given archive and populates
+ * the internal tables with ZipEntry instances.
+ *
+ *
The ZipEntrys will know all data that can be obtained from
+ * the central directory alone, but not the data that requires the
+ * local file header or additional data to be read.
+ *
+ * @return a Map<ZipEntry, NameAndComment>> of
+ * zipentries that didn't have the language encoding flag set when
+ * read.
+ */
+ private Map populateFromCentralDirectory()
+ throws IOException {
+ HashMap noUTF8Flag = new HashMap<>();
+
+ positionAtCentralDirectory();
+
+ byte[] cfh = new byte[CFH_LEN];
+
+ byte[] signatureBytes = new byte[WORD];
+ archive.readFully(signatureBytes);
+ long sig = ZipLong.getValue(signatureBytes);
+ final long cfhSig = ZipLong.getValue(ZipOutputStream.CFH_SIG);
+ if (sig != cfhSig && startsWithLocalFileHeader()) {
+ throw new IOException("central directory is empty, can't expand"
+ + " corrupt archive.");
+ }
+ while (sig == cfhSig) {
+ archive.readFully(cfh);
+ int off = 0;
+ ZipEntry ze = new ZipEntry();
+
+ int versionMadeBy = ZipShort.getValue(cfh, off);
+ off += SHORT;
+ ze.setPlatform((versionMadeBy >> BYTE_SHIFT) & NIBLET_MASK);
+
+ off += SHORT; // skip version info
+
+ final int generalPurposeFlag = ZipShort.getValue(cfh, off);
+ final boolean hasUTF8Flag =
+ (generalPurposeFlag & ZipOutputStream.UFT8_NAMES_FLAG) != 0;
+ final ZipEncoding entryEncoding =
+ hasUTF8Flag ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding;
+
+ off += SHORT;
+
+ //noinspection MagicConstant
+ ze.setMethod(ZipShort.getValue(cfh, off));
+ off += SHORT;
+
+ // FIXME this is actually not very cpu cycles friendly as we are converting from
+ // dos to java while the underlying Sun implementation will convert
+ // from java to dos time for internal storage...
+ long time = dosToJavaTime(ZipLong.getValue(cfh, off));
+ ze.setTime(time);
+ off += WORD;
+
+ ze.setCrc(ZipLong.getValue(cfh, off));
+ off += WORD;
+
+ ze.setCompressedSize(ZipLong.getValue(cfh, off));
+ off += WORD;
+
+ ze.setSize(ZipLong.getValue(cfh, off));
+ off += WORD;
+
+ int fileNameLen = ZipShort.getValue(cfh, off);
+ off += SHORT;
+
+ int extraLen = ZipShort.getValue(cfh, off);
+ off += SHORT;
+
+ int commentLen = ZipShort.getValue(cfh, off);
+ off += SHORT;
+
+ off += SHORT; // disk number
+
+ ze.setInternalAttributes(ZipShort.getValue(cfh, off));
+ off += SHORT;
+
+ ze.setExternalAttributes(ZipLong.getValue(cfh, off));
+ off += WORD;
+
+ byte[] fileName = new byte[fileNameLen];
+ archive.readFully(fileName);
+ ze.setName(entryEncoding.decode(fileName));
+
+ // LFH offset,
+ OffsetEntry offset = new OffsetEntry();
+ offset.headerOffset = ZipLong.getValue(cfh, off);
+ // data offset will be filled later
+ entries.put(ze, offset);
+
+ nameMap.put(ze.getName(), ze);
+
+ byte[] cdExtraData = new byte[extraLen];
+ archive.readFully(cdExtraData);
+ ze.setCentralDirectoryExtra(cdExtraData);
+
+ byte[] comment = new byte[commentLen];
+ archive.readFully(comment);
+ ze.setComment(entryEncoding.decode(comment));
+
+ archive.readFully(signatureBytes);
+ sig = ZipLong.getValue(signatureBytes);
+
+ if (!hasUTF8Flag && useUnicodeExtraFields) {
+ noUTF8Flag.put(ze, new NameAndComment(fileName, comment));
+ }
+ }
+ return noUTF8Flag;
+ }
+
+ private static final int MIN_EOCD_SIZE =
+ /* end of central dir signature */ WORD
+ /* number of this disk */ + SHORT
+ /* number of the disk with the */
+ /* start of the central directory */ + SHORT
+ /* total number of entries in */
+ /* the central dir on this disk */ + SHORT
+ /* total number of entries in */
+ /* the central dir */ + SHORT
+ /* size of the central directory */ + WORD
+ /* offset of start of central */
+ /* directory with respect to */
+ /* the starting disk number */ + WORD
+ /* zipfile comment length */ + SHORT;
+
+ private static final int MAX_EOCD_SIZE = MIN_EOCD_SIZE
+ /* maximum length of zipfile comment */ + 0xFFFF;
+
+ private static final int CFD_LOCATOR_OFFSET =
+ /* end of central dir signature */ WORD
+ /* number of this disk */ + SHORT
+ /* number of the disk with the */
+ /* start of the central directory */ + SHORT
+ /* total number of entries in */
+ /* the central dir on this disk */ + SHORT
+ /* total number of entries in */
+ /* the central dir */ + SHORT
+ /* size of the central directory */ + WORD;
+
+ /**
+ * Searches for the "End of central dir record", parses
+ * it and positions the stream at the first central directory
+ * record.
+ */
+ private void positionAtCentralDirectory()
+ throws IOException {
+ boolean found = false;
+ long off = archive.length() - MIN_EOCD_SIZE;
+ final long stopSearching =
+ Math.max(0L, archive.length() - MAX_EOCD_SIZE);
+ if (off >= 0) {
+ final byte[] sig = ZipOutputStream.EOCD_SIG;
+ for (; off >= stopSearching; off--) {
+ archive.seek(off);
+ int curr = archive.read();
+ if (curr == -1) {
+ break;
+ }
+ if (curr == sig[POS_0]) {
+ curr = archive.read();
+ if (curr == sig[POS_1]) {
+ curr = archive.read();
+ if (curr == sig[POS_2]) {
+ curr = archive.read();
+ if (curr == sig[POS_3]) {
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (!found) {
+ throw new ZipException("archive is not a ZIP archive");
+ }
+ archive.seek(off + CFD_LOCATOR_OFFSET);
+ byte[] cfdOffset = new byte[WORD];
+ archive.readFully(cfdOffset);
+ archive.seek(ZipLong.getValue(cfdOffset));
+ }
+
+ /**
+ * Number of bytes in local file header up to the "length of
+ * filename" entry.
+ */
+ private static final long LFH_OFFSET_FOR_FILENAME_LENGTH =
+ /* local file header signature */ WORD
+ /* version needed to extract */ + SHORT
+ /* general purpose bit flag */ + SHORT
+ /* compression method */ + SHORT
+ /* last mod file time */ + SHORT
+ /* last mod file date */ + SHORT
+ /* crc-32 */ + WORD
+ /* compressed size */ + WORD
+ /* uncompressed size */ + WORD;
+
+ /**
+ * Walks through all recorded entries and adds the data available
+ * from the local file header.
+ *
+ *
Also records the offsets for the data to read from the
+ * entries.
+ */
+ private void resolveLocalFileHeaderData(Map entriesWithoutUTF8Flag)
+ throws IOException {
+ Enumeration e = Collections.enumeration(new HashSet<>(entries.keySet()));
+ while (e.hasMoreElements()) {
+ ZipEntry ze = e.nextElement();
+ OffsetEntry offsetEntry = entries.get(ze);
+ long offset = offsetEntry.headerOffset;
+ archive.seek(offset + LFH_OFFSET_FOR_FILENAME_LENGTH);
+ byte[] b = new byte[SHORT];
+ archive.readFully(b);
+ int fileNameLen = ZipShort.getValue(b);
+ archive.readFully(b);
+ int extraFieldLen = ZipShort.getValue(b);
+ int lenToSkip = fileNameLen;
+ while (lenToSkip > 0) {
+ int skipped = archive.skipBytes(lenToSkip);
+ if (skipped <= 0) {
+ throw new RuntimeException("failed to skip file name in"
+ + " local file header");
+ }
+ lenToSkip -= skipped;
+ }
+ byte[] localExtraData = new byte[extraFieldLen];
+ archive.readFully(localExtraData);
+ ze.setExtra(localExtraData);
+ /*dataOffsets.put(ze,
+ new Long(offset + LFH_OFFSET_FOR_FILENAME_LENGTH
+ + SHORT + SHORT + fileNameLen + extraFieldLen));
+ */
+ offsetEntry.dataOffset = offset + LFH_OFFSET_FOR_FILENAME_LENGTH
+ + SHORT + SHORT + fileNameLen + extraFieldLen;
+
+ if (entriesWithoutUTF8Flag.containsKey(ze)) {
+ // changing the name of a ZipEntry is going to change
+ // the hashcode
+ // - see https://issues.apache.org/jira/browse/COMPRESS-164
+ entries.remove(ze);
+ setNameAndCommentFromExtraFields(ze, entriesWithoutUTF8Flag.get(ze));
+ entries.put(ze, offsetEntry);
+ }
+ }
+ }
+
+ /**
+ * Convert a DOS date/time field to a Date object.
+ *
+ * @param zipDosTime contains the stored DOS time.
+ * @return a Date instance corresponding to the given time.
+ */
+ protected static Date fromDosTime(ZipLong zipDosTime) {
+ long dosTime = zipDosTime.getValue();
+ return new Date(dosToJavaTime(dosTime));
+ }
+
+ /*
+ * Converts DOS time to Java time (number of milliseconds since epoch).
+ */
+ private static long dosToJavaTime(long dosTime) {
+ Calendar cal = Calendar.getInstance();
+ // CheckStyle:MagicNumberCheck OFF - no point
+ cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980);
+ cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1);
+ cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f);
+ cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f);
+ cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f);
+ cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e);
+ // CheckStyle:MagicNumberCheck ON
+ return cal.getTime().getTime();
+ }
+
+ /**
+ * Checks whether the archive starts with a LFH. If it doesn't,
+ * it may be an empty archive.
+ */
+ private boolean startsWithLocalFileHeader() throws IOException {
+ archive.seek(0);
+ final byte[] start = new byte[WORD];
+ archive.readFully(start);
+ for (int i = 0; i < start.length; i++) {
+ if (start[i] != ZipOutputStream.LFH_SIG[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * If the entry has Unicode*ExtraFields and the CRCs of the
+ * names/comments match those of the extra fields, transfer the
+ * known Unicode values from the extra field.
+ */
+ private void setNameAndCommentFromExtraFields(ZipEntry ze,
+ NameAndComment nc) {
+ UnicodePathExtraField name = (UnicodePathExtraField)
+ ze.getExtraField(UnicodePathExtraField.UPATH_ID);
+ String originalName = ze.getName();
+ String newName = getUnicodeStringIfOriginalMatches(name, nc.name);
+ if (newName != null && !originalName.equals(newName)) {
+ ze.setName(newName);
+ nameMap.remove(originalName);
+ nameMap.put(newName, ze);
+ }
+
+ if (nc.comment != null && nc.comment.length > 0) {
+ UnicodeCommentExtraField cmt = (UnicodeCommentExtraField)
+ ze.getExtraField(UnicodeCommentExtraField.UCOM_ID);
+ String newComment =
+ getUnicodeStringIfOriginalMatches(cmt, nc.comment);
+ if (newComment != null) {
+ ze.setComment(newComment);
+ }
+ }
+ }
+
+ /**
+ * If the stored CRC matches the one of the given name, return the
+ * Unicode name of the given field.
+ *
+ *
If the field is null or the CRCs don't match, return null
+ * instead.
+ */
+ private String getUnicodeStringIfOriginalMatches(AbstractUnicodeExtraField f,
+ byte[] orig) {
+ if (f != null) {
+ CRC32 crc32 = new CRC32();
+ crc32.update(orig);
+ long origCRC32 = crc32.getValue();
+
+ if (origCRC32 == f.getNameCRC32()) {
+ try {
+ return ZipEncodingHelper
+ .UTF8_ZIP_ENCODING.decode(f.getUnicodeName());
+ } catch (IOException ex) {
+ // UTF-8 unsupported? should be impossible the
+ // Unicode*ExtraField must contain some bad bytes
+
+ // TODO log this anywhere?
+ return null;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * InputStream that delegates requests to the underlying
+ * RandomAccessFile, making sure that only bytes from a certain
+ * range can be read.
+ */
+ private class BoundedInputStream extends InputStream {
+ private long remaining;
+ private long loc;
+ private boolean addDummyByte = false;
+
+ BoundedInputStream(long start, long remaining) {
+ this.remaining = remaining;
+ loc = start;
+ }
+
+ public int read() throws IOException {
+ if (remaining-- <= 0) {
+ if (addDummyByte) {
+ addDummyByte = false;
+ return 0;
+ }
+ return -1;
+ }
+ synchronized (archive) {
+ archive.seek(loc++);
+ return archive.read();
+ }
+ }
+
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (remaining <= 0) {
+ if (addDummyByte) {
+ addDummyByte = false;
+ b[off] = 0;
+ return 1;
+ }
+ return -1;
+ }
+
+ if (len <= 0) {
+ return 0;
+ }
+
+ if (len > remaining) {
+ len = (int) remaining;
+ }
+ int ret;
+ synchronized (archive) {
+ archive.seek(loc);
+ ret = archive.read(b, off, len);
+ }
+ if (ret > 0) {
+ loc += ret;
+ remaining -= ret;
+ }
+ return ret;
+ }
+
+ /**
+ * Inflater needs an extra dummy byte for nowrap - see
+ * Inflater's javadocs.
+ */
+ void addDummy() {
+ addDummyByte = true;
+ }
+ }
+
+ private static final class NameAndComment {
+ private final byte[] name;
+ private final byte[] comment;
+
+ private NameAndComment(byte[] name, byte[] comment) {
+ this.name = name;
+ this.comment = comment;
+ }
+ }
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipItem.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipItem.java
new file mode 100644
index 0000000..b51bd9e
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipItem.java
@@ -0,0 +1,40 @@
+package top.niunaijun.blackobfuscator.core.utils.zip;
+
+import java.io.InputStream;
+
+/**
+ * Created by sunwanquan on 2019/6/18.
+ * * ∧_∧
+ * (`・ω・∥
+ * 丶 つ0
+ * しーJ
+ * 此处无Bug
+ */
+public class ZipItem {
+ private InputStream stream;
+ private String zipEntryName;
+
+ public ZipItem() {
+ }
+
+ public ZipItem(InputStream stream, String zipEntryName) {
+ this.stream = stream;
+ this.zipEntryName = zipEntryName;
+ }
+
+ public InputStream getStream() {
+ return stream;
+ }
+
+ public void setStream(InputStream stream) {
+ this.stream = stream;
+ }
+
+ public String getZipEntryName() {
+ return zipEntryName;
+ }
+
+ public void setZipEntryName(String zipEntryName) {
+ this.zipEntryName = zipEntryName;
+ }
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipLong.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipLong.java
new file mode 100644
index 0000000..762c1e7
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipLong.java
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.
+ *
+ */
+
+package top.niunaijun.blackobfuscator.core.utils.zip;
+
+/**
+ * Utility class that represents a four byte integer with conversion
+ * rules for the big endian byte order of ZIP files.
+ */
+public final class ZipLong implements Cloneable {
+
+ private static final int WORD = 4;
+ private static final int BYTE_MASK = 0xFF;
+
+ private static final int BYTE_1 = 1;
+ private static final int BYTE_1_MASK = 0xFF00;
+ private static final int BYTE_1_SHIFT = 8;
+
+ private static final int BYTE_2 = 2;
+ private static final int BYTE_2_MASK = 0xFF0000;
+ private static final int BYTE_2_SHIFT = 16;
+
+ private static final int BYTE_3 = 3;
+ private static final long BYTE_3_MASK = 0xFF000000L;
+ private static final int BYTE_3_SHIFT = 24;
+
+ private long value;
+
+ /**
+ * Create instance from a number.
+ *
+ * @param value the long to store as a ZipLong
+ * @since 1.1
+ */
+ public ZipLong(long value) {
+ this.value = value;
+ }
+
+ /**
+ * Create instance from bytes.
+ *
+ * @param bytes the bytes to store as a ZipLong
+ * @since 1.1
+ */
+ public ZipLong(byte[] bytes) {
+ this(bytes, 0);
+ }
+
+ /**
+ * Create instance from the four bytes starting at offset.
+ *
+ * @param bytes the bytes to store as a ZipLong
+ * @param offset the offset to start
+ * @since 1.1
+ */
+ public ZipLong(byte[] bytes, int offset) {
+ value = ZipLong.getValue(bytes, offset);
+ }
+
+ /**
+ * Get value as four bytes in big endian byte order.
+ *
+ * @return value as four bytes in big endian order
+ * @since 1.1
+ */
+ public byte[] getBytes() {
+ return ZipLong.getBytes(value);
+ }
+
+ /**
+ * Get value as Java long.
+ *
+ * @return value as a long
+ * @since 1.1
+ */
+ public long getValue() {
+ return value;
+ }
+
+ /**
+ * put the value as four bytes in big endian byte order.
+ *
+ * @param value the Java long to convert to bytes
+ * @param buf the output buffer
+ * @param offset The offset within the output buffer of the first byte to be written.
+ * must be non-negative and no larger than buf.length-4
+ */
+ public static void putLong(long value, byte[] buf, int offset) {
+ buf[offset++] = (byte) ((value & BYTE_MASK));
+ buf[offset++] = (byte) ((value & BYTE_1_MASK) >> BYTE_1_SHIFT);
+ buf[offset++] = (byte) ((value & BYTE_2_MASK) >> BYTE_2_SHIFT);
+ buf[offset] = (byte) ((value & BYTE_3_MASK) >> BYTE_3_SHIFT);
+ }
+
+ public void putLong(byte[] buf, int offset) {
+ putLong(value, buf, offset);
+ }
+
+ /**
+ * Get value as four bytes in big endian byte order.
+ *
+ * @param value the value to convert
+ * @return value as four bytes in big endian byte order
+ */
+ public static byte[] getBytes(long value) {
+ byte[] result = new byte[WORD];
+ result[0] = (byte) ((value & BYTE_MASK));
+ result[BYTE_1] = (byte) ((value & BYTE_1_MASK) >> BYTE_1_SHIFT);
+ result[BYTE_2] = (byte) ((value & BYTE_2_MASK) >> BYTE_2_SHIFT);
+ result[BYTE_3] = (byte) ((value & BYTE_3_MASK) >> BYTE_3_SHIFT);
+ return result;
+ }
+
+ /**
+ * Helper method to get the value as a Java long from four bytes starting at given array offset
+ *
+ * @param bytes the array of bytes
+ * @param offset the offset to start
+ * @return the correspondanding Java long value
+ */
+ public static long getValue(byte[] bytes, int offset) {
+ long value = (bytes[offset + BYTE_3] << BYTE_3_SHIFT) & BYTE_3_MASK;
+ value += (bytes[offset + BYTE_2] << BYTE_2_SHIFT) & BYTE_2_MASK;
+ value += (bytes[offset + BYTE_1] << BYTE_1_SHIFT) & BYTE_1_MASK;
+ value += (bytes[offset] & BYTE_MASK);
+ return value;
+ }
+
+ /**
+ * Helper method to get the value as a Java long from a four-byte array
+ *
+ * @param bytes the array of bytes
+ * @return the correspondanding Java long value
+ */
+ public static long getValue(byte[] bytes) {
+ return getValue(bytes, 0);
+ }
+
+ /**
+ * Override to make two instances with same value equal.
+ *
+ * @param o an object to compare
+ * @return true if the objects are equal
+ * @since 1.1
+ */
+ public boolean equals(Object o) {
+ if (o == null || !(o instanceof ZipLong)) {
+ return false;
+ }
+ return value == ((ZipLong) o).getValue();
+ }
+
+ /**
+ * Override to make two instances with same value equal.
+ *
+ * @return the value stored in the ZipLong
+ * @since 1.1
+ */
+ public int hashCode() {
+ return (int) value;
+ }
+
+ public Object clone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException cnfe) {
+ // impossible
+ throw new RuntimeException(cnfe);
+ }
+ }
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipManager.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipManager.java
new file mode 100644
index 0000000..91b5cac
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipManager.java
@@ -0,0 +1,352 @@
+package top.niunaijun.blackobfuscator.core.utils.zip;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.HashMap;
+
+public class ZipManager {
+ public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yy-MM-dd HH:mm");
+ private final File file;
+ public final ZipFile zipFile;
+ private final ZipEntry[] ze;
+ private ZipOutputStream zos;
+ public File tmp;
+
+// public static void main(String[] args) {
+// test1();
+// }
+// private static void extractTest() {
+// try {
+// ZipManager zipManager = new ZipManager(new File("C:\\b.zip"));
+// zipManager.extract(new ExtractCallback() {
+//
+// @Override
+// public File filter(ZipEntry zipEntry, int current, int total) {
+// System.out.print("正在解压文件(" + current + "/" + total + ").." + zipEntry.getName() + "\n");
+// File file = new File("C:\\b\\" + zipEntry.getName());
+// if (!file.isDirectory() && file.exists())
+// //noinspection ResultOfMethodCallIgnored
+// file.delete();
+// return file;
+// }
+//
+// @Override
+// public void onProgress(long current, long total) {
+// System.out.print((100 * current / total) + "%\n");
+// }
+//
+// @Override
+// public void done(ZipEntry zipEntry, File file) {
+//
+// }
+// });
+// System.out.print("done\n");
+// } catch (IOException e) {
+// e.printStackTrace();
+// }
+// }
+
+// private static void test2() {
+// try {
+// ZipManager zipManager = new ZipManager(new File("C:\\b.zip"));
+// for (ZipEntry ze : zipManager.ze) {
+// System.out.print(ze.getName() + " " + ze.getParent() + " " + ze.getSimpleName() + "\n");
+// }
+// System.out.print("===================\n");
+// ArrayList al = zipManager.list("新建文件夹/");
+// for (ZipEntry ze : al) {
+// System.out.print(ze.getName() + " " + getEntryTime(ze) + "\n");
+// }
+//
+// } catch (IOException e) {
+// e.printStackTrace();
+// }
+//
+// }
+
+// private static void test1() {
+// try {
+// ZipFile zipFile = new ZipFile(new File("D:\\a\\a.zip"));
+// System.out.print(zipFile.getEncoding() + "\n");
+// ZipOutputStream zos = new ZipOutputStream (new File("D:\\a\\b.zip"));
+// zos.setLevel(ZipOutputStream.LEVEL_BEST);
+// zos.setZipEncoding(zipFile.getZipEncoding());
+//
+// Enumeration zes = zipFile.getEntries();
+// while(zes.hasMoreElements()){
+// ZipEntry zipEntry = zes.nextElement();
+// System.out.print(zipEntry.getName() + "\n");
+//
+// if(zipEntry.getName().endsWith("/"))
+// continue;
+//
+// if (zipEntry.getName().equalsIgnoreCase("a.txt"))
+// zipEntry.setName("我爱蛋蛋.txt");
+//
+// zos.copyZipEntry(zipEntry, zipFile);
+// }
+// zos.putNextEntry("a.txt");
+// zos.writeFully(new FileInputStream(new File("D:\\a\\a.txt")));
+//
+// zos.putNextEntry("文件夹/");
+//
+// zipFile.close();
+// zos.close();
+// } catch (IOException e) {
+// e.printStackTrace();
+// }
+// }
+
+ public long getTotalCompressedSize() {
+ long size = 0;
+ for (ZipEntry zipEntry : ze) {
+ if (!zipEntry.isDirectory())
+ size += zipEntry.getCompressedSize();
+ }
+ return size;
+ }
+
+ public long getTotalSize() {
+ long size = 0;
+ for (ZipEntry zipEntry : ze) {
+ if (!zipEntry.isDirectory())
+ size += zipEntry.getSize();
+ }
+ return size;
+ }
+
+ public ZipEntry[] getZipEntries() {
+ return ze;
+ }
+
+ public void zip(File file, long size, String entryName, ZipCallback callback) throws IOException {
+ if (size > 0)
+ callback.onProgress(0, size);
+ InputStream is = new FileInputStream(file);
+ ZipEntry zipEntry = new ZipEntry(entryName);
+ zipEntry.setTime(file.lastModified());
+ zos.putNextEntry(zipEntry);
+ byte[] bytes = new byte[ZipOutputStream.BUFFER_SIZE];
+ int len;
+ long current = 0;
+ while ((len = is.read(bytes)) > 0) {
+ zos.write(bytes, 0, len);
+ current += len;
+ if (size > 0)
+ callback.onProgress(current, size);
+ }
+ is.close();
+ zos.closeEntry();
+ }
+
+ public HashMap getZipFileEntryMap() {
+ HashMap map = new HashMap<>();
+ for (ZipEntry zipEntry : ze) {
+ if (!zipEntry.isDirectory())
+ map.put(zipEntry.getName().toLowerCase(), zipEntry);
+ }
+ return map;
+ }
+
+ public void copyEntries(CopyEntryCallback callback) throws IOException {
+ for (int i = 0; i < ze.length; i++) {
+ ZipEntry zipEntry = callback.filter(ze[i], i + 1, ze.length);
+ if (zipEntry == null)
+ continue;
+ if (zipEntry.isDirectory())
+ zos.putNextEntry(zipEntry);
+ else {
+ InputStream rawInputStream = zipFile.getRawInputStream(zipEntry);
+ zos.putNextRawEntry(zipEntry);
+ int len;
+ final long total = zipEntry.getCompressedSize();
+ long current = 0;
+ byte[] bytes = new byte[ZipOutputStream.BUFFER_SIZE];
+ while ((len = rawInputStream.read(bytes)) > 0) {
+ zos.writeRaw(bytes, 0, len);
+ current += len;
+ callback.onProgress(current, total);
+ }
+ }
+ // rawInputStream.close()是个空方法,是否调用不影响
+ zos.closeEntry();
+ callback.done(zipEntry);
+ }
+ }
+
+ public void copyOtherZipManagerEntries(ZipManager zipManager, CopyEntryCallback callback) throws IOException {
+ for (int i = 0; i < zipManager.ze.length; i++) {
+ ZipEntry zipEntry = callback.filter(zipManager.ze[i], i + 1, zipManager.ze.length);
+ if (zipEntry == null)
+ continue;
+ if (zipEntry.isDirectory())
+ zos.putNextEntry(zipEntry);
+ else {
+ InputStream rawInputStream = zipManager.zipFile.getRawInputStream(zipManager.ze[i]);
+ zos.putNextRawEntry(zipEntry);
+ int len;
+ final long total = zipEntry.getCompressedSize();
+ long current = 0;
+ byte[] bytes = new byte[ZipOutputStream.BUFFER_SIZE];
+ while ((len = rawInputStream.read(bytes)) > 0) {
+ zos.writeRaw(bytes, 0, len);
+ current += len;
+ callback.onProgress(current, total);
+ }
+ // rawInputStream.close()是个空方法,是否调用不影响
+ }
+ zos.closeEntry();
+ callback.done(zipEntry);
+ }
+ }
+
+ public void extractZipEntry(ZipEntry zipEntry, File file) throws IOException {
+ InputStream is = zipFile.getInputStream(zipEntry);
+ BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(file));
+ byte[] bytes = new byte[ZipOutputStream.BUFFER_SIZE];
+ int len;
+ while ((len = is.read(bytes)) > 0) {
+ os.write(bytes, 0, len);
+ }
+ is.close();
+ os.close();
+ }
+
+ public void extract(ExtractCallback callback) throws IOException {
+ for (int i = 0; i < ze.length; i++) {
+ ZipEntry zipEntry = ze[i];
+ File file = callback.filter(zipEntry, i + 1, ze.length);
+ if (file == null)
+ continue;
+ if (zipEntry.isDirectory()) {
+ if (!file.exists() && !file.mkdirs())
+ throw new IOException("mkdir \"" + file.getPath() + "\" failed");
+ } else {
+ InputStream is = zipFile.getInputStream(zipEntry);
+ BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(file));
+ byte[] bytes = new byte[ZipOutputStream.BUFFER_SIZE];
+ int len;
+ long total = zipEntry.getSize();
+ long current = 0;
+ while ((len = is.read(bytes)) > 0) {
+ os.write(bytes, 0, len);
+ current += len;
+ if (total > 0)
+ callback.onProgress(current, total);
+ }
+ is.close();
+ os.close();
+ }
+ //noinspection ResultOfMethodCallIgnored
+ file.setLastModified(zipEntry.getTime());
+ callback.done(zipEntry, file);
+ }
+ }
+
+ public ZipOutputStream createTempZipOutputStream() throws IOException {
+ String s = "";
+ do {
+ s += System.currentTimeMillis() % 1000000;
+ tmp = new File(file.getParentFile(), s + ".tmp");
+ } while (tmp.exists());
+ zos = new ZipOutputStream(tmp);
+ zos.setZipEncoding(zipFile.getZipEncoding());
+ return zos;
+ }
+
+ public ZipManager(File file) throws IOException {
+ zipFile = new ZipFile(this.file = file);
+ ArrayList al = new ArrayList<>(zipFile.getEntrySize());
+ Enumeration entryEnumeration = zipFile.getEntries();
+ HashMap map = new HashMap<>();
+ while (entryEnumeration.hasMoreElements()) {
+ ZipEntry z = entryEnumeration.nextElement();
+ if (!z.isDirectory())
+ al.add(z);
+ else {
+ if (!map.containsKey(z.getName()))
+ map.put(z.getName(), z);
+ else {
+ //该文件夹已处理过父目录,无需再处理
+ continue;
+ }
+ }
+ String parent = z.getParent();
+ if (parent != null) {
+ long timeZ = z.getTime();
+ if (!map.containsKey(parent)) {
+ // 父目录不存在,自动创建
+ do {
+ ZipEntry dir = new ZipEntry(parent);
+ dir.setTime(timeZ);
+ map.put(parent, dir);
+ parent = dir.getParent();
+ } while (parent != null && !map.containsKey(parent));
+ } else {
+ // 父目录存在,更新日期
+ do {
+ ZipEntry dir = map.get(parent);
+ if (dir.getTime() < timeZ) {
+ dir.setTime(timeZ);
+ parent = dir.getParent();
+ } else
+ break;
+ } while (parent != null);
+ }
+ }
+ }
+ al.addAll(map.values());
+ map.clear();
+ Collections.sort(al, new Comparator() {
+ @Override
+ public int compare(ZipEntry o1, ZipEntry o2) {
+ return o1.getName().compareToIgnoreCase(o2.getName());
+ }
+ });
+ ze = new ZipEntry[al.size()];
+ al.toArray(ze);
+ al.clear();
+ }
+
+ public static String getEntryTime(ZipEntry zipEntry) {
+ return DATE_FORMAT.format(zipEntry.getLastModifiedDate());
+ }
+
+ public int getEntrySize() {
+ return ze.length;
+ }
+
+ public void close() throws IOException {
+ zipFile.close();
+ }
+
+ public ArrayList list(String path) {
+ final String p;
+ if (path == null || path.length() <= 1)
+ p = null;
+ else if (path.charAt(path.length() - 1) != '/')
+ p = path + "/";
+ else
+ p = path;
+ final ArrayList l = new ArrayList<>(ze.length);
+ for (ZipEntry zipEntry : ze) {
+ String parent = zipEntry.getParent();
+ if (parent == null) {
+ if (p == null)
+ l.add(zipEntry);
+ } else if (parent.equals(p))
+ l.add(zipEntry);
+ }
+ l.trimToSize();
+ return l;
+ }
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipOutputStream.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipOutputStream.java
new file mode 100644
index 0000000..c95bb15
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipOutputStream.java
@@ -0,0 +1,1236 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.
+ *
+ */
+
+package top.niunaijun.blackobfuscator.core.utils.zip;
+
+
+import top.niunaijun.blackobfuscator.core.utils.zip.encoding.ZipEncoding;
+import top.niunaijun.blackobfuscator.core.utils.zip.encoding.ZipEncodingHelper;
+import top.niunaijun.blackobfuscator.core.utils.zip.extrafield.UnicodeCommentExtraField;
+import top.niunaijun.blackobfuscator.core.utils.zip.extrafield.UnicodePathExtraField;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.CRC32;
+import java.util.zip.Deflater;
+import java.util.zip.ZipException;
+
+import static top.niunaijun.blackobfuscator.core.utils.zip.ZipLong.putLong;
+import static top.niunaijun.blackobfuscator.core.utils.zip.ZipShort.putShort;
+import static top.niunaijun.blackobfuscator.core.utils.zip.encoding.ZipEncodingHelper.UTF8_ZIP_ENCODING;
+
+/**
+ * Reimplementation of {@link java.util.zip.ZipOutputStream
+ * java.util.zip.ZipOutputStream} that does handle the extended
+ * functionality of this package, especially internal/external file
+ * attributes and extra fields with different layouts for local file
+ * data and central directory entries.
+ *
+ *
This class will try to use {@link RandomAccessFile
+ * RandomAccessFile} when you know that the output is going to go to a
+ * file.
+ *
+ *
If RandomAccessFile cannot be used, this implementation will use
+ * a Data Descriptor to store size and CRC information for {@link
+ * #DEFLATED DEFLATED} entries, this means, you don't need to
+ * calculate them yourself. Unfortunately this is not possible for
+ * the {@link #STORED STORED} method, here setting the CRC and
+ * uncompressed size information is required before {@link
+ * #putNextEntry putNextEntry} can be called.
+ */
+public class ZipOutputStream extends FilterOutputStream {
+ public static final int LEVEL_BEST = Deflater.BEST_COMPRESSION;
+ public static final int LEVEL_BETTER = 7;
+ public static final int LEVEL_DEFAULT = Deflater.DEFAULT_COMPRESSION;
+ public static final int LEVEL_FASTER = 3;
+ public static final int LEVEL_FASTEST = Deflater.BEST_SPEED;
+
+ private static final int BYTE_MASK = 0xFF;
+ private static final int SHORT = 2;
+ private static final int WORD = 4;
+ public static final int BUFFER_SIZE = 10240;
+ /*
+ * Apparently Deflater.setInput gets slowed down a lot on Sun JVMs
+ * when it gets handed a really big buffer. See
+ * https://issues.apache.org/bugzilla/show_bug.cgi?id=45396
+ *
+ * Using a buffer size of 8 kB proved to be a good compromise
+ */
+ private static final int DEFLATER_BLOCK_SIZE = 8192;
+
+ /**
+ * Compression method for deflated entries.
+ *
+ * @since 1.1
+ */
+ public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED;
+
+ /**
+ * Default compression level for deflated entries.
+ *
+ * @since Ant 1.7
+ */
+ public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION;
+
+ /**
+ * Compression method for stored entries.
+ *
+ * @since 1.1
+ */
+ public static final int STORED = java.util.zip.ZipEntry.STORED;
+
+ /**
+ * default encoding for file names and comment.
+ */
+ static final String DEFAULT_ENCODING = null;
+
+ /**
+ * General purpose flag, which indicates that filenames are
+ * written in utf-8.
+ */
+ public static final int UFT8_NAMES_FLAG = 1 << 11;
+
+ /**
+ * General purpose flag, which indicates that filenames are
+ * written in utf-8.
+ *
+ */
+ public static final int EFS_FLAG = UFT8_NAMES_FLAG;
+
+ /**
+ * Current entry.
+ *
+ * @since 1.1
+ */
+ private ZipEntry entry;
+
+ /**
+ * The file comment.
+ *
+ * @since 1.1
+ */
+ private String comment = "";
+
+ /**
+ * Compression level for next entry.
+ *
+ * @since 1.1
+ */
+ private int level = DEFAULT_COMPRESSION;
+
+ /**
+ * Has the compression level changed when compared to the last
+ * entry?
+ *
+ * @since 1.5
+ */
+ private boolean hasCompressionLevelChanged = false;
+
+ /**
+ * Default compression method for next entry.
+ *
+ * @since 1.1
+ */
+ private int method = java.util.zip.ZipEntry.DEFLATED;
+
+ /**
+ * List of ZipEntries written so far.
+ *
+ * @since 1.1
+ */
+ private final List entries = new LinkedList<>();
+
+ /**
+ * CRC instance to avoid parsing DEFLATED data twice.
+ *
+ * @since 1.1
+ */
+ private final CRC32 crc = new CRC32();
+
+ /**
+ * Count the bytes written to out.
+ *
+ * @since 1.1
+ */
+ private long written = 0;
+
+ /**
+ * Data for local header data
+ *
+ * @since 1.1
+ */
+ private long dataStart = 0;
+
+ /**
+ * Offset for CRC entry in the local file header data for the
+ * current entry starts here.
+ *
+ * @since 1.15
+ */
+ private long localDataStart = 0;
+
+ /**
+ * Start of central directory.
+ *
+ * @since 1.1
+ */
+ private long cdOffset = 0;
+
+ /**
+ * Length of central directory.
+ *
+ * @since 1.1
+ */
+ private long cdLength = 0;
+
+ /**
+ * Helper, a 0 as ZipShort.
+ *
+ * @since 1.1
+ */
+ private static final byte[] ZERO = {0, 0};
+
+ /**
+ * Helper, a 0 as ZipLong.
+ *
+ * @since 1.1
+ */
+ private static final byte[] LZERO = {0, 0, 0, 0};
+
+ /**
+ * Holds the offsets of the LFH starts for each entry.
+ *
+ * @since 1.1
+ */
+ private final Map offsets = new HashMap<>();
+
+ /**
+ * The encoding to use for filenames and the file comment.
+ *
+ *
For a list of possible values see http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html.
+ * Defaults to the platform's default character encoding.
+ *
+ * @since 1.3
+ */
+ private String encoding = null;
+
+ /**
+ * The zip encoding to use for filenames and the file comment.
+ *
+ * This field is of internal use and will be set in {@link
+ * #setEncoding(String)}.
+ */
+ private ZipEncoding zipEncoding = UTF8_ZIP_ENCODING;
+
+ // CheckStyle:VisibilityModifier OFF - bc
+
+ /**
+ * This Deflater object is used for output.
+ *
+ *
This attribute is only protected to provide a level of API
+ * backwards compatibility. This class used to extend {@link
+ * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to
+ * Revision 1.13.
+ *
+ * @since 1.14
+ */
+ protected Deflater def = new Deflater(level, true);
+
+ /**
+ * This buffer servers as a Deflater.
+ *
+ *
This attribute is only protected to provide a level of API
+ * backwards compatibility. This class used to extend {@link
+ * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to
+ * Revision 1.13.
+ *
+ * @since 1.14
+ */
+ protected byte[] buf = new byte[512];
+
+ // CheckStyle:VisibilityModifier ON
+
+ /**
+ * Optional random access output.
+ *
+ * @since 1.14
+ */
+ private RandomAccessFile raf = null;
+
+ /**
+ * whether to use the general purpose bit flag when writing UTF-8
+ * filenames or not.
+ */
+ private boolean useUTF8Flag = true;
+
+ /**
+ * Whether to encode non-encodable file names as UTF-8.
+ */
+ private boolean fallbackToUTF8 = false;
+
+ /**
+ * whether to getInstance UnicodePathExtraField-s for each entry.
+ */
+ private UnicodeExtraFieldPolicy createUnicodeExtraFields =
+ UnicodeExtraFieldPolicy.NEVER;
+
+ /**
+ * Creates a new ZIP OutputStream filtering the underlying stream.
+ *
+ * @param out the outputstream to zip
+ * @since 1.1
+ */
+ public ZipOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ /**
+ * Creates a new ZIP OutputStream writing to a File. Will use
+ * random access if possible.
+ *
+ * @param file the file to zip to
+ * @throws IOException on error
+ * @since 1.14
+ */
+ public ZipOutputStream(File file) throws IOException {
+ //noinspection ConstantConditions
+ super(null);
+
+ try {
+ raf = new RandomAccessFile(file, "rw");
+ raf.setLength(0);
+ } catch (IOException e) {
+ if (raf != null) {
+ try {
+ raf.close();
+ } catch (IOException inner) {
+ // ignore
+ }
+ raf = null;
+ }
+ out = new FileOutputStream(file);
+ }
+ }
+
+ /**
+ * This method indicates whether this archive is writing to a
+ * seekable stream (i.e., to a random access file).
+ *
+ *
For seekable streams, you don't need to calculate the CRC or
+ * uncompressed size for {@link #STORED} entries before
+ * invoking {@link #putNextEntry}.
+ *
+ * @return true if seekable
+ * @since 1.17
+ */
+ public boolean isSeekable() {
+ return raf != null;
+ }
+
+ /**
+ * The encoding to use for filenames and the file comment.
+ *
+ *
For a list of possible values see http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html.
+ * Defaults to the platform's default character encoding.
+ *
+ * @param encoding the encoding value
+ * @since 1.3
+ */
+ public void setEncoding(final String encoding) {
+ this.encoding = encoding;
+ boolean isUTF8 = ZipEncodingHelper.isUTF8(encoding);
+ this.zipEncoding = isUTF8 ? UTF8_ZIP_ENCODING : ZipEncodingHelper.getZipEncoding(encoding);
+ if (useUTF8Flag && !isUTF8) {
+ useUTF8Flag = false;
+ }
+ }
+
+ public void setZipEncoding(final ZipEncoding zipEncoding) {
+ this.zipEncoding = zipEncoding;
+ this.encoding = zipEncoding.getEncoding();
+ if (useUTF8Flag && !ZipEncodingHelper.isUTF8(encoding)) {
+ useUTF8Flag = false;
+ }
+ }
+
+
+ /**
+ * The encoding to use for filenames and the file comment.
+ *
+ * @return null if using the platform's default character encoding.
+ * @since 1.3
+ */
+ public String getEncoding() {
+ return encoding;
+ }
+
+ /**
+ * Whether to set the language encoding flag if the file name
+ * encoding is UTF-8.
+ *
+ *
Defaults to true.
+ */
+ public void setUseLanguageEncodingFlag(boolean b) {
+ useUTF8Flag = b && ZipEncodingHelper.isUTF8(encoding);
+ }
+
+ /**
+ * Whether to getInstance Unicode Extra Fields.
+ *
+ *
Defaults to NEVER.
+ */
+ public void setCreateUnicodeExtraFields(UnicodeExtraFieldPolicy b) {
+ createUnicodeExtraFields = b;
+ }
+
+ /**
+ * Whether to fall back to UTF and the language encoding flag if
+ * the file name cannot be encoded using the specified encoding.
+ *
+ *
Defaults to false.
+ */
+ public void setFallbackToUTF8(boolean b) {
+ fallbackToUTF8 = b;
+ }
+
+ /**
+ * Finishs writing the contents and closes this as well as the
+ * underlying stream.
+ *
+ * @throws IOException on error
+ * @since 1.1
+ */
+ public void finish() throws IOException {
+ closeEntry();
+ cdOffset = written;
+ for (ZipEntry entry1 : entries) {
+ writeCentralFileHeader(entry1);
+ }
+ cdLength = written - cdOffset;
+ writeCentralDirectoryEnd();
+ offsets.clear();
+ entries.clear();
+ def.end();
+ }
+
+ /**
+ * Writes all necessary data for this entry.
+ *
+ * @throws IOException on error
+ * @since 1.1
+ */
+ public void closeEntry() throws IOException {
+ if (entry == null) {
+ return;
+ }
+
+ if (currentIsRawEntry)
+ crc.reset();
+ else {
+ long realCrc = crc.getValue();
+ crc.reset();
+
+ if (entry.getMethod() == DEFLATED) {
+ def.finish();
+ while (!def.finished()) {
+ deflate();
+ }
+
+ entry.setSize(adjustToLong(def.getTotalIn()));
+ entry.setCompressedSize(adjustToLong(def.getTotalOut()));
+ entry.setCrc(realCrc);
+
+ def.reset();
+
+ written += entry.getCompressedSize();
+ } else if (raf == null) {
+ if (entry.getCrc() != realCrc) {
+ throw new ZipException("bad CRC checksum for entry "
+ + entry.getName() + ": "
+ + Long.toHexString(entry.getCrc())
+ + " instead of "
+ + Long.toHexString(realCrc));
+ }
+
+ if (entry.getSize() != written - dataStart) {
+ throw new ZipException("bad size for entry "
+ + entry.getName() + ": "
+ + entry.getSize()
+ + " instead of "
+ + (written - dataStart));
+ }
+ } else { /* method is STORED and we used RandomAccessFile */
+ long size = written - dataStart;
+
+ entry.setSize(size);
+ entry.setCompressedSize(size);
+ entry.setCrc(realCrc);
+ }
+
+ // If random access output, write the local file header containing
+ // the correct CRC and compressed/uncompressed sizes
+ if (raf != null) {
+ long save = raf.getFilePointer();
+
+ raf.seek(localDataStart);
+ byte[] data = new byte[12];
+ putLong(entry.getCrc(), data, 0);
+ putLong(entry.getCompressedSize(), data, 4);
+ putLong(entry.getSize(), data, 8);
+ writeOut(data);
+ raf.seek(save);
+ }
+ }
+
+ writeDataDescriptor(entry);
+ entry = null;
+ }
+
+ public void writeFully(InputStream is) throws IOException {
+ int len;
+ byte[] b = new byte[BUFFER_SIZE];
+ while ((len = is.read(b)) > 0)
+ write(b, 0, len);
+ }
+
+ public void copyZipEntry(ZipEntry zipEntry, ZipFile zipFile) throws IOException {
+ InputStream rawInputStream = zipFile.getRawInputStream(zipEntry);
+ if (rawInputStream == null) {
+ }
+ putNextRawEntry(zipEntry);
+ int len;
+ byte[] b = new byte[BUFFER_SIZE];
+ while ((len = rawInputStream.read(b)) > 0) {
+ writeRaw(b, 0, len);
+ }
+ closeEntry();
+ }
+
+ private boolean currentIsRawEntry;
+
+ public void putNextEntry(String entryName) throws IOException {
+ putNextEntry(new ZipEntry(entryName));
+ }
+
+ /**
+ * Begin writing next entry.
+ *
+ * @param ze the entry to write
+ * @throws IOException on error
+ * @since 1.1
+ */
+ public void putNextEntry(ZipEntry ze) throws IOException {
+ closeEntry();
+
+ entry = ze;
+ entries.add(entry);
+
+ currentIsRawEntry = false;
+
+ //noinspection WrongConstant
+ if (entry.getMethod() == -1) { // not specified
+ entry.setMethod(method);
+ }
+
+ if (entry.getTime() == -1) { // not specified
+ entry.setTime(System.currentTimeMillis());
+ }
+
+ // Size/CRC not required if RandomAccessFile is used
+ if (entry.getMethod() == STORED && raf == null) {
+ if (entry.getSize() == -1) {
+ throw new ZipException("uncompressed size is required for"
+ + " STORED method when not writing to a"
+ + " file");
+ }
+ if (entry.getCrc() == -1) {
+ throw new ZipException("crc checksum is required for STORED"
+ + " method when not writing to a file");
+ }
+ entry.setCompressedSize(entry.getSize());
+ }
+
+ if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
+ def.setLevel(level);
+ hasCompressionLevelChanged = false;
+ }
+ writeLocalFileHeader(entry);
+ }
+
+ public void putNextRawEntry(ZipEntry ze) throws IOException {
+ closeEntry();
+
+ entry = ze;
+ entries.add(entry);
+
+ currentIsRawEntry = true;
+
+ // Size/CRC not required if RandomAccessFile is used
+ if (entry.getMethod() == STORED && raf == null) {
+ if (entry.getSize() == -1) {
+ throw new ZipException("uncompressed size is required for"
+ + " STORED method when not writing to a"
+ + " file");
+ }
+ if (entry.getCrc() == -1) {
+ throw new ZipException("crc checksum is required for STORED"
+ + " method when not writing to a file");
+ }
+ entry.setCompressedSize(entry.getSize());
+ }
+
+ if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
+ def.setLevel(level);
+ hasCompressionLevelChanged = false;
+ }
+ writeLocalFileHeader(entry);
+ }
+
+ /**
+ * Set the file comment.
+ *
+ * @param comment the comment
+ * @since 1.1
+ */
+ public void setComment(String comment) {
+ this.comment = comment;
+ }
+
+ /**
+ * Sets the compression level for subsequent entries.
+ *
+ *
Default is Deflater.DEFAULT_COMPRESSION.
+ *
+ * @param level the compression level.
+ * @throws IllegalArgumentException if an invalid compression
+ * level is specified.
+ * @since 1.1
+ */
+ public void setLevel(int level) {
+ if (level < Deflater.DEFAULT_COMPRESSION
+ || level > Deflater.BEST_COMPRESSION) {
+ throw new IllegalArgumentException("Invalid compression level: "
+ + level);
+ }
+ hasCompressionLevelChanged = (this.level != level);
+ this.level = level;
+ }
+
+ /**
+ * Sets the default compression method for subsequent entries.
+ *
+ *
Default is DEFLATED.
+ *
+ * @param method an int
from java.util.zip.ZipEntry
+ * @since 1.1
+ */
+ public void setMethod(int method) {
+ this.method = method;
+ }
+
+ /**
+ * Writes bytes to ZIP entry.
+ *
+ * @param b the byte array to write
+ * @param offset the start position to write from
+ * @param length the number of bytes to write
+ * @throws IOException on error
+ */
+ public void write(byte[] b, int offset, int length) throws IOException {
+ if (entry.getMethod() == DEFLATED) {
+ if (length > 0) {
+ if (!def.finished()) {
+ if (length <= DEFLATER_BLOCK_SIZE) {
+ def.setInput(b, offset, length);
+ deflateUntilInputIsNeeded();
+ } else {
+ final int fullblocks = length / DEFLATER_BLOCK_SIZE;
+ for (int i = 0; i < fullblocks; i++) {
+ def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE,
+ DEFLATER_BLOCK_SIZE);
+ deflateUntilInputIsNeeded();
+ }
+ final int done = fullblocks * DEFLATER_BLOCK_SIZE;
+ if (done < length) {
+ def.setInput(b, offset + done, length - done);
+ deflateUntilInputIsNeeded();
+ }
+ }
+ }
+ }
+ } else {
+ writeOut(b, offset, length);
+ written += length;
+ }
+ crc.update(b, offset, length);
+ }
+
+ public void writeRaw(byte[] b, int offset, int length) throws IOException {
+ writeOut(b, offset, length);
+ written += length;
+ }
+
+ public void writeRaw(byte[] b) throws IOException {
+ writeRaw(b, 0, b.length);
+ }
+
+ /**
+ * Writes a single byte to ZIP entry.
+ *
+ *
Delegates to the three arg method.
+ *
+ * @param b the byte to write
+ * @throws IOException on error
+ * @since 1.14
+ */
+ public void write(int b) throws IOException {
+ byte[] buff = new byte[1];
+ buff[0] = (byte) (b & BYTE_MASK);
+ write(buff, 0, 1);
+ }
+
+ /**
+ * Closes this output stream and releases any system resources
+ * associated with the stream.
+ *
+ * @throws IOException if an I/O error occurs.
+ * @since 1.14
+ */
+ public void close() throws IOException {
+ finish();
+
+ if (raf != null) {
+ raf.close();
+ }
+ if (out != null) {
+ out.close();
+ }
+ }
+
+ /**
+ * Flushes this output stream and forces any buffered output bytes
+ * to be written out to the stream.
+ *
+ * @throws IOException if an I/O error occurs.
+ * @since 1.14
+ */
+ public void flush() throws IOException {
+ if (out != null) {
+ out.flush();
+ }
+ }
+
+ /*
+ * Various ZIP constants
+ */
+ /**
+ * local file header signature
+ *
+ * @since 1.1
+ */
+ protected static final byte[] LFH_SIG = ZipLong.getBytes(0X04034B50L);
+ /**
+ * data descriptor signature
+ *
+ * @since 1.1
+ */
+ protected static final byte[] DD_SIG = ZipLong.getBytes(0X08074B50L);
+ /**
+ * central file header signature
+ *
+ * @since 1.1
+ */
+ protected static final byte[] CFH_SIG = ZipLong.getBytes(0X02014B50L);
+ /**
+ * end of central dir signature
+ *
+ * @since 1.1
+ */
+ protected static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L);
+
+ /**
+ * Writes next block of compressed data to the output stream.
+ *
+ * @throws IOException on error
+ * @since 1.14
+ */
+ protected final void deflate() throws IOException {
+ int len = def.deflate(buf, 0, buf.length);
+ if (len > 0) {
+ writeOut(buf, 0, len);
+ }
+ }
+
+ /**
+ * Writes the local file header entry
+ *
+ * @param ze the entry to write
+ * @throws IOException on error
+ * @since 1.1
+ */
+ protected void writeLocalFileHeader(ZipEntry ze) throws IOException {
+
+ boolean encodable = zipEncoding.canEncode(ze.getName());
+
+ final ZipEncoding entryEncoding;
+
+ if (!encodable && fallbackToUTF8) {
+ entryEncoding = UTF8_ZIP_ENCODING;
+ } else {
+ entryEncoding = zipEncoding;
+ }
+
+ ByteBuffer name = entryEncoding.encode(ze.getName());
+
+ if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) {
+
+ if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
+ || !encodable) {
+ ze.addExtraField(new UnicodePathExtraField(ze.getName(),
+ name.array(),
+ name.arrayOffset(),
+ name.limit()));
+ }
+
+ String comm = ze.getComment();
+ if (comm != null && !"".equals(comm)) {
+
+ boolean commentEncodable = this.zipEncoding.canEncode(comm);
+
+ if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
+ || !commentEncodable) {
+ ByteBuffer commentB = entryEncoding.encode(comm);
+ ze.addExtraField(new UnicodeCommentExtraField(comm,
+ commentB.array(),
+ commentB.arrayOffset(),
+ commentB.limit())
+ );
+ }
+ }
+ }
+
+ offsets.put(ze, written);
+
+ byte[] data = new byte[30 + name.limit()];
+
+ putBytes(LFH_SIG, data, 0);
+ written += WORD;
+
+ //store method in local variable to prevent multiple method calls
+ final int zipMethod = ze.getMethod();
+
+ writeVersionNeededToExtractAndGeneralPurposeBits(data, 4, zipMethod,
+ !encodable && fallbackToUTF8);
+ written += WORD;
+
+ // compression method
+ putShort(zipMethod, data, 8);
+ written += SHORT;
+
+ // last mod. time and date
+ putLong(toDosTime(ze.getTime()), data, 10);
+ written += WORD;
+
+ // CRC
+ // compressed length
+ // uncompressed length
+ localDataStart = written;
+ if (currentIsRawEntry) {
+ putLong(ze.getCrc(), data, 14);
+ putLong(ze.getCompressedSize(), data, 18);
+ putLong(ze.getSize(), data, 22);
+ } else if (zipMethod != DEFLATED && raf == null) {
+ putLong(ze.getCrc(), data, 14);
+ putLong(ze.getSize(), data, 18);
+ putLong(ze.getSize(), data, 22);
+ }
+// else {
+// writeOut(LZERO);
+// writeOut(LZERO);
+// writeOut(LZERO);
+// }
+ // CheckStyle:MagicNumber OFF
+ written += 12;
+ // CheckStyle:MagicNumber ON
+
+ // file name length
+ putShort(name.limit(), data, 26);
+ written += SHORT;
+
+ // extra field length
+ byte[] extra = ze.getLocalFileDataExtra();
+ if (ze.getMethod() == STORED) {
+ // ZipAlign对齐优化
+ long len = written + SHORT + name.limit() + extra.length;
+ if (len % 4 != 0) {
+ int extraLen = 4 - (int) ((written + SHORT + name.limit()) % 4);
+ extra = new byte[extraLen];
+ }
+ }
+ putShort(extra.length, data, 28);
+ written += SHORT;
+
+ // file name
+ putBytes(name.array(), name.arrayOffset(), name.limit(), data, 30);
+ written += name.limit();
+
+ writeOut(data);
+
+ // extra field
+ if (extra.length > 0) {
+ writeOut(extra);
+ written += extra.length;
+ }
+
+ dataStart = written;
+ }
+
+ private static void putBytes(byte[] value, int start, int length, byte[] buf, int offset) {
+ for (int i = 0; i < length; i++) {
+ buf[offset++] = value[start + i];
+ }
+ }
+
+ private static void putBytes(byte[] value, byte[] buf, int offset) {
+ putBytes(value, 0, value.length, buf, offset);
+ }
+
+
+ /**
+ * Writes the data descriptor entry.
+ *
+ * @param ze the entry to write
+ * @throws IOException on error
+ * @since 1.1
+ */
+ protected void writeDataDescriptor(ZipEntry ze) throws IOException {
+ if (ze.getMethod() != DEFLATED || raf != null) {
+ return;
+ }
+ byte[] data = new byte[16];
+ putBytes(DD_SIG, data, 0);
+ putLong(entry.getCrc(), data, 4);
+ putLong(entry.getCompressedSize(), data, 8);
+ putLong(entry.getSize(), data, 12);
+ writeOut(data);
+ // CheckStyle:MagicNumber OFF
+ written += 16;
+ // CheckStyle:MagicNumber ON
+ }
+
+ /**
+ * Writes the central file header entry.
+ *
+ * @param ze the entry to write
+ * @throws IOException on error
+ * @since 1.1
+ */
+ protected void writeCentralFileHeader(ZipEntry ze) throws IOException {
+ final int zipMethod = ze.getMethod();
+ final boolean encodable = zipEncoding.canEncode(ze.getName());
+
+ final ZipEncoding entryEncoding;
+
+ if (!encodable && fallbackToUTF8) {
+ entryEncoding = UTF8_ZIP_ENCODING;
+ } else {
+ entryEncoding = zipEncoding;
+ }
+ ByteBuffer name = entryEncoding.encode(ze.getName());
+
+ byte[] extra = ze.getCentralDirectoryExtra();
+
+ String comm = ze.getComment();
+ if (comm == null) {
+ comm = "";
+ }
+
+ ByteBuffer commentB = entryEncoding.encode(comm);
+
+
+ byte[] data = new byte[46 + name.limit() + extra.length + commentB.limit()];
+
+ putBytes(CFH_SIG, data, 0);
+ written += WORD;
+
+ // version made by
+ // CheckStyle:MagicNumber OFF
+ putShort((ze.getPlatform() << 8) | 20, data, 4);
+ written += SHORT;
+
+
+ writeVersionNeededToExtractAndGeneralPurposeBits(data, 6, zipMethod,
+ !encodable && fallbackToUTF8);
+ written += WORD;
+
+ // compression method
+ putShort(zipMethod, data, 10);
+ written += SHORT;
+
+ // last mod. time and date
+ putLong(toDosTime(ze.getTime()), data, 12);
+ written += WORD;
+
+ // CRC
+ // compressed length
+ // uncompressed length
+ putLong(ze.getCrc(), data, 16);
+ putLong(ze.getCompressedSize(), data, 20);
+ putLong(ze.getSize(), data, 24);
+ // CheckStyle:MagicNumber OFF
+ written += 12;
+ // CheckStyle:MagicNumber ON
+
+ // file name length
+
+ putShort(name.limit(), data, 28);
+ written += SHORT;
+
+ // extra field length
+ putShort(extra.length, data, 30);
+ written += SHORT;
+
+ // file comment length
+ putShort(commentB.limit(), data, 32);
+ written += SHORT;
+
+ // disk number start
+ written += SHORT;
+
+ // internal file attributes
+ putShort(ze.getInternalAttributes(), data, 36);
+ written += SHORT;
+
+ // external file attributes
+ putLong(ze.getExternalAttributes(), data, 38);
+ written += WORD;
+
+ // relative offset of LFH
+ putLong(offsets.get(ze), data, 42);
+ written += WORD;
+
+ // file name
+ putBytes(name.array(), name.arrayOffset(), name.limit(), data, 46);
+ written += name.limit();
+
+ // extra field
+ putBytes(extra, data, 46 + name.limit());
+ written += extra.length;
+
+ // file comment
+ putBytes(commentB.array(), commentB.arrayOffset(), commentB.limit(),
+ data, 46 + name.limit() + extra.length);
+ written += commentB.limit();
+ writeOut(data);
+ }
+
+ /**
+ * Writes the "End of central dir record".
+ *
+ * @throws IOException on error
+ * @since 1.1
+ */
+ protected void writeCentralDirectoryEnd() throws IOException {
+ ByteBuffer data = this.zipEncoding.encode(comment);
+
+ byte[] buf = new byte[22 + data.limit()];
+ putBytes(EOCD_SIG, buf, 0);
+// putBytes(ZERO, buf, 4);
+// putBytes(ZERO, buf, 6);
+ putShort(entries.size(), buf, 8);
+ putShort(entries.size(), buf, 10);
+ putLong(cdLength, buf, 12);
+ putLong(cdOffset, buf, 16);
+ putShort(data.limit(), buf, 20);
+ putBytes(data.array(), data.arrayOffset(), data.limit(), buf, 22);
+ writeOut(buf);
+// writeOut(EOCD_SIG);//4
+//
+// // disk numbers
+// writeOut(ZERO);//2
+// writeOut(ZERO);//2
+//
+// // number of entries
+// byte[] num = ZipShort.getBytes(entries.size());
+// writeOut(num);//2
+// writeOut(num);//2
+//
+// // length and location of CD
+// writeOut(ZipLong.getBytes(cdLength));//4
+// writeOut(ZipLong.getBytes(cdOffset));//4
+//
+// // ZIP file comment
+// writeOut(ZipShort.getBytes(data.limit()));
+// writeOut(data.array(), data.arrayOffset(), data.limit());
+ }
+
+ /**
+ * Smallest date/time ZIP can handle.
+ *
+ * @since 1.1
+ */
+// private static final byte[] DOS_TIME_MIN = ZipLong.getBytes(0x00002100L);
+
+ /**
+ * Convert a Date object to a DOS date/time field.
+ *
+ *
Stolen from InfoZip's fileio.c
+ *
+ * @param t number of milliseconds since the epoch
+ * @return the date as a byte array
+ * @since 1.26
+ */
+ @SuppressWarnings("deprecation")
+ protected static long toDosTime(long t) {
+ Date time = new Date(t);
+ // CheckStyle:MagicNumberCheck OFF - I do not think that using constants
+ // here will improve the readablity
+ int year = time.getYear() + 1900;
+ if (year < 1980) {
+ return 0x00002100L;
+ }
+ int month = time.getMonth() + 1;
+ return ((year - 1980) << 25)
+ | (month << 21)
+ | (time.getDate() << 16)
+ | (time.getHours() << 11)
+ | (time.getMinutes() << 5)
+ | (time.getSeconds() >> 1);
+ // CheckStyle:MagicNumberCheck ON
+ }
+
+ /**
+ * Write bytes to output or random access file.
+ *
+ * @param data the byte array to write
+ * @throws IOException on error
+ * @since 1.14
+ */
+ protected final void writeOut(byte[] data) throws IOException {
+ writeOut(data, 0, data.length);
+ }
+
+ /**
+ * Write bytes to output or random access file.
+ *
+ * @param data the byte array to write
+ * @param offset the start position to write from
+ * @param length the number of bytes to write
+ * @throws IOException on error
+ * @since 1.14
+ */
+ protected final void writeOut(byte[] data, int offset, int length)
+ throws IOException {
+ if (raf != null) {
+ raf.write(data, offset, length);
+ } else {
+ out.write(data, offset, length);
+ }
+ }
+
+ /**
+ * Assumes a negative integer really is a positive integer that
+ * has wrapped around and re-creates the original value.
+ *
+ * @param i the value to treat as unsigned int.
+ * @return the unsigned int as a long.
+ * @since 1.34
+ */
+ protected static long adjustToLong(int i) {
+ if (i < 0) {
+ return 2 * ((long) Integer.MAX_VALUE) + 2 + i;
+ } else {
+ return i;
+ }
+ }
+
+ private void deflateUntilInputIsNeeded() throws IOException {
+ while (!def.needsInput()) {
+ deflate();
+ }
+ }
+
+ private void writeVersionNeededToExtractAndGeneralPurposeBits(
+ final byte[] data, int offset,
+ final int zipMethod, final boolean utfFallback)
+ throws IOException {
+
+ // CheckStyle:MagicNumber OFF
+ int versionNeededToExtract = 10;
+ int generalPurposeFlag = (useUTF8Flag || utfFallback) ? UFT8_NAMES_FLAG : 0;
+ if (zipMethod == DEFLATED && raf == null) {
+ // requires version 2 as we are going to store length info
+ // in the data descriptor
+ versionNeededToExtract = 20;
+ // bit3 set to signal, we use a data descriptor
+ generalPurposeFlag |= 8;
+ }
+ // CheckStyle:MagicNumber ON
+
+ // version needed to extract
+ putShort(versionNeededToExtract, data, offset);
+ // general purpose bit flag
+ putShort(generalPurposeFlag, data, offset + SHORT);
+ }
+
+ /**
+ * enum that represents the possible policies for creating Unicode
+ * extra fields.
+ */
+ public static final class UnicodeExtraFieldPolicy {
+ /**
+ * Always getInstance Unicode extra fields.
+ */
+ public static final UnicodeExtraFieldPolicy ALWAYS =
+ new UnicodeExtraFieldPolicy("always");
+ /**
+ * Never getInstance Unicode extra fields.
+ */
+ public static final UnicodeExtraFieldPolicy NEVER =
+ new UnicodeExtraFieldPolicy("never");
+ /**
+ * Create Unicode extra fields for filenames that cannot be
+ * encoded using the specified encoding.
+ */
+ public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE =
+ new UnicodeExtraFieldPolicy("not encodeable");
+
+ private final String name;
+
+ private UnicodeExtraFieldPolicy(String n) {
+ name = n;
+ }
+
+ public String toString() {
+ return name;
+ }
+ }
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipShort.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipShort.java
new file mode 100644
index 0000000..eed8cef
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipShort.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.
+ *
+ */
+
+package top.niunaijun.blackobfuscator.core.utils.zip;
+
+/**
+ * Utility class that represents a two byte integer with conversion
+ * rules for the big endian byte order of ZIP files.
+ */
+public final class ZipShort implements Cloneable {
+ private static final int BYTE_MASK = 0xFF;
+ private static final int BYTE_1_MASK = 0xFF00;
+ private static final int BYTE_1_SHIFT = 8;
+
+ private int value;
+
+ /**
+ * Create instance from a number.
+ *
+ * @param value the int to store as a ZipShort
+ * @since 1.1
+ */
+ public ZipShort(int value) {
+ this.value = value;
+ }
+
+ /**
+ * Create instance from bytes.
+ *
+ * @param bytes the bytes to store as a ZipShort
+ * @since 1.1
+ */
+ public ZipShort(byte[] bytes) {
+ this(bytes, 0);
+ }
+
+ /**
+ * Create instance from the two bytes starting at offset.
+ *
+ * @param bytes the bytes to store as a ZipShort
+ * @param offset the offset to start
+ * @since 1.1
+ */
+ public ZipShort(byte[] bytes, int offset) {
+ value = ZipShort.getValue(bytes, offset);
+ }
+
+ /**
+ * Get value as two bytes in big endian byte order.
+ *
+ * @return the value as a a two byte array in big endian byte order
+ * @since 1.1
+ */
+ public byte[] getBytes() {
+ byte[] result = new byte[2];
+ result[0] = (byte) (value & BYTE_MASK);
+ result[1] = (byte) ((value & BYTE_1_MASK) >> BYTE_1_SHIFT);
+ return result;
+ }
+
+ /**
+ * put the value as two bytes in big endian byte order.
+ *
+ * @param value the Java int to convert to bytes
+ * @param buf the output buffer
+ * @param offset The offset within the output buffer of the first byte to be written.
+ * must be non-negative and no larger than buf.length-2
+ */
+ public static void putShort(int value, byte[] buf, int offset) {
+ buf[offset] = (byte) (value & BYTE_MASK);
+ buf[offset + 1] = (byte) ((value & BYTE_1_MASK) >> BYTE_1_SHIFT);
+ }
+
+ /**
+ * Get value as Java int.
+ *
+ * @return value as a Java int
+ * @since 1.1
+ */
+ public int getValue() {
+ return value;
+ }
+
+ /**
+ * Get value as two bytes in big endian byte order.
+ *
+ * @param value the Java int to convert to bytes
+ * @return the converted int as a byte array in big endian byte order
+ */
+ public static byte[] getBytes(int value) {
+ byte[] result = new byte[2];
+ result[0] = (byte) (value & BYTE_MASK);
+ result[1] = (byte) ((value & BYTE_1_MASK) >> BYTE_1_SHIFT);
+ return result;
+ }
+
+ /**
+ * Helper method to get the value as a java int from two bytes starting at given array offset
+ *
+ * @param bytes the array of bytes
+ * @param offset the offset to start
+ * @return the correspondanding java int value
+ */
+ public static int getValue(byte[] bytes, int offset) {
+ int value = (bytes[offset + 1] << BYTE_1_SHIFT) & BYTE_1_MASK;
+ value += (bytes[offset] & BYTE_MASK);
+ return value;
+ }
+
+ /**
+ * Helper method to get the value as a java int from a two-byte array
+ *
+ * @param bytes the array of bytes
+ * @return the correspondanding java int value
+ */
+ public static int getValue(byte[] bytes) {
+ return getValue(bytes, 0);
+ }
+
+ /**
+ * Override to make two instances with same value equal.
+ *
+ * @param o an object to compare
+ * @return true if the objects are equal
+ * @since 1.1
+ */
+ public boolean equals(Object o) {
+ if (o == null || !(o instanceof ZipShort)) {
+ return false;
+ }
+ return value == ((ZipShort) o).getValue();
+ }
+
+ /**
+ * Override to make two instances with same value equal.
+ *
+ * @return the value stored in the ZipShort
+ * @since 1.1
+ */
+ public int hashCode() {
+ return value;
+ }
+
+ public Object clone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException cnfe) {
+ // impossible
+ throw new RuntimeException(cnfe);
+ }
+ }
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipUtil.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipUtil.java
new file mode 100644
index 0000000..5b88914
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/ZipUtil.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.
+ *
+ */
+package top.niunaijun.blackobfuscator.core.utils.zip;
+
+/**
+ * Utility class for handling DOS and Java time conversions.
+ *
+ * @since Ant 1.8.1
+ */
+public abstract class ZipUtil {
+ /**
+ * Create a copy of the given array - or return null if the
+ * argument is null.
+ */
+ public static byte[] copy(byte[] from) {
+ if (from != null) {
+ byte[] to = new byte[from.length];
+ System.arraycopy(from, 0, to, 0, to.length);
+ return to;
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/encoding/DetectEncoding.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/encoding/DetectEncoding.java
new file mode 100644
index 0000000..c116b90
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/encoding/DetectEncoding.java
@@ -0,0 +1,2062 @@
+package top.niunaijun.blackobfuscator.core.utils.zip.encoding;
+
+import java.nio.charset.Charset;
+import java.nio.charset.UnsupportedCharsetException;
+
+@SuppressWarnings("SpellCheckingInspection")
+public class DetectEncoding {
+
+ private final static int UTF8 = 0;
+ private final static int GB2312 = 1;
+ private final static int GBK = 2;
+ private final static int UNICODE = 3;
+ private final static int ASCII = 4;
+ private final static int BIG5 = 5;
+ private final static int EUC_TW = 6;
+ private final static int OTHER = 7;
+
+ private final static int TOTAL_ENCODINGS = 8;
+
+ // Frequency tables to hold the GB, Big5, and EUC-TW character
+ // frequencies
+ private static final int GBFreq[][];
+ private static final int GBKFreq[][];
+ private static final int Big5Freq[][];
+ private static final int EUC_TWFreq[][];
+ // int UnicodeFreq[94][128];
+
+ public static String encodeString(byte[] data, boolean mustEncodable) {
+ DetectEncoding detectEncoding = new DetectEncoding();
+ detectEncoding.update(data);
+ if (mustEncodable && detectEncoding.encoding_guess == OTHER)
+ return null;
+ Charset charset = detectEncoding.getEncode();
+ return new String(data, charset);
+ }
+
+ private static final Charset[] encodeCharset;
+
+ public static final Charset CHARSET_UTF8;
+ public static final Charset CHARSET_GB2312;
+ public static final Charset CHARSET_GBK;
+ public static final Charset CHARSET_UNICODE;
+ public static final Charset CHARSET_BIG5;
+ public static final Charset CHARSET_EUC_TW;
+
+ // Initialize the Frequency Table for GB, Big5, EUC-TW
+ static {
+ GBFreq = new int[94][94];
+ GBKFreq = new int[126][191];
+ Big5Freq = new int[94][158];
+ EUC_TWFreq = new int[94][94];
+
+ CHARSET_UTF8 = Charset.forName("UTF-8");
+ CHARSET_GB2312 = Charset.forName("GB2312");
+ CHARSET_GBK = Charset.forName("GBK");
+ CHARSET_UNICODE = Charset.forName("Unicode");
+ CHARSET_BIG5 = Charset.forName("Big5");
+
+ Charset charset;
+ try {
+ charset = Charset.forName("EUC-TW");
+ } catch (UnsupportedCharsetException ignored) {
+ charset = CHARSET_UTF8;
+ }
+ CHARSET_EUC_TW = charset;
+
+ encodeCharset = new Charset[TOTAL_ENCODINGS];
+ encodeCharset[UTF8] = CHARSET_UTF8;
+ encodeCharset[GB2312] = CHARSET_GB2312;
+ encodeCharset[GBK] = CHARSET_GBK;
+ encodeCharset[UNICODE] = CHARSET_UNICODE;
+ encodeCharset[ASCII] = CHARSET_GBK;//Charset.forName("ASCII");
+ encodeCharset[BIG5] = CHARSET_BIG5;
+ encodeCharset[EUC_TW] = CHARSET_EUC_TW;
+ encodeCharset[OTHER] = CHARSET_UTF8;
+ initialize_frequencies();
+ }
+
+ public void update(byte[] rawtext) {
+ update(rawtext, 0, rawtext.length);
+ }
+
+ public void update(byte[] rawtext, int offset, int length) {
+ int[] scores;
+
+ scores = new int[TOTAL_ENCODINGS];
+
+ // Assign Scores
+ scores[UTF8] = utf8_probability(rawtext, offset, length);
+ scores[GB2312] = gb2312_probability(rawtext, offset, length);
+ scores[GBK] = gbk_probability(rawtext, offset, length);
+ scores[UNICODE] = utf16_probability(rawtext, offset, length);
+ scores[ASCII] = ascii_probability(rawtext, offset, length);
+ scores[BIG5] = big5_probability(rawtext, offset, length);
+ scores[EUC_TW] = euc_tw_probability(rawtext, offset, length);
+ // scores[OTHER] = 0;
+
+// Log.w("DetectEncoding", "UTF8:" + scores[UTF8] + " GBK:" + scores[GBK]);
+ int maxScore = 0;
+ for (int index = 0; index < TOTAL_ENCODINGS; index++) {
+ if (scores[index] > maxScore) {
+ encoding_guess = index;
+ maxScore = scores[index];
+ }
+ }
+
+ if (maxScore <= 50)
+ encoding_guess = OTHER;
+ }
+
+ int encoding_guess = OTHER;
+
+ public Charset getEncode() {
+ return encodeCharset[encoding_guess];
+ }
+
+ public static Charset getEncodeByIndex(int index) {
+ return encodeCharset[index];
+ }
+
+ public int getEncodeIndex() {
+ return encoding_guess;
+ }
+
+ public static String getEncodeName(int index) {
+ switch (index) {
+ case UTF8:
+ return "UTF-8";
+ case GB2312:
+ return "GB2312";
+ case GBK:
+ return "GBK";
+ case UNICODE:
+ return "Unicode";
+ case ASCII:
+ return "ASCII";
+ case BIG5:
+ return "Big5";
+ case EUC_TW:
+ return "EUC-TW";
+ case OTHER:
+ default:
+ return "UTF-8";
+ }
+ }
+
+ /*
+ * Function: gb2312_probability Argument: pointer to byte array Returns :
+ * number from 0 to 100 representing probability text in array uses GB-2312
+ * encoding
+ */
+ int gb2312_dbchars = 1, gb2312_chars = 1;
+ long gb2312_gbfreq = 0, gb2312_totalfreq = 1;
+
+ private int gb2312_probability(byte[] rawtext, int offset, int length) {
+ float rangeval, freqval;
+ int row, column;
+
+ // Stage 1: Check to see if characters fit into acceptable ranges
+ int rawLength = offset + length - 1;
+ for (int i = offset; i < rawLength; i++)
+ if (rawtext[i] < 0) {
+ gb2312_dbchars++;
+ if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xF7
+ && (byte) 0xA1 <= rawtext[i + 1]
+ && rawtext[i + 1] <= (byte) 0xFE) {
+ gb2312_chars++;
+ gb2312_totalfreq += 500;
+ row = rawtext[i] + 256 - 0xA1;
+ column = rawtext[i + 1] + 256 - 0xA1;
+ if (GBFreq[row][column] != 0)
+ gb2312_gbfreq += GBFreq[row][column];
+ else if (15 <= row && row < 55)
+ gb2312_gbfreq += 200;
+ }
+ i++;
+ }
+ rangeval = 50 * ((float) gb2312_chars / (float) gb2312_dbchars);
+ freqval = 50 * ((float) gb2312_gbfreq / (float) gb2312_totalfreq);
+ return (int) (rangeval + freqval);
+ }
+
+ /*
+ * Function: gb2312_probability Argument: pointer to byte array Returns :
+ * number from 0 to 100 representing probability text in array uses GB-2312
+ * encoding
+ */
+ int gbk_dbchars = 1, gbk_chars = 1;
+ long gbk_freq = 0, gbk_totalfreq = 1;
+
+ private int gbk_probability(byte[] rawtext, int offset, int length) {
+ float rangeval, freqval;
+ int row, column;
+
+ // Stage 1: Check to see if characters fit into acceptable ranges
+ int rawLength = offset + length - 1;
+ for (int i = offset; i < rawLength; i++)
+ // System.err.println(rawtext[i]);
+ if (rawtext[i] < 0) {
+ gbk_dbchars++;
+ if ((byte) 0xA1 <= rawtext[i] && rawtext[i] <= (byte) 0xF7
+ && // Original GB range
+ (byte) 0xA1 <= rawtext[i + 1]
+ && rawtext[i + 1] <= (byte) 0xFE) {
+ gbk_chars++;
+ gbk_totalfreq += 500;
+ row = rawtext[i] + 256 - 0xA1;
+ column = rawtext[i + 1] + 256 - 0xA1;
+
+ // System.out.println("original row " + row + " column " +
+ // column);
+ if (GBFreq[row][column] != 0)
+ gbk_freq += GBFreq[row][column];
+ else if (15 <= row && row < 55)
+ gbk_freq += 200;
+
+ } else if ((byte) 0x81 <= rawtext[i]
+ && rawtext[i] <= (byte) 0xFE && // Extended GB range
+ ((byte) 0x80 <= rawtext[i + 1]
+ && rawtext[i + 1] <= (byte) 0xFE || (byte) 0x40 <= rawtext[i + 1]
+ && rawtext[i + 1] <= (byte) 0x7E)) {
+ gbk_chars++;
+ gbk_totalfreq += 500;
+ row = rawtext[i] + 256 - 0x81;
+ if (0x40 <= rawtext[i + 1] && rawtext[i + 1] <= 0x7E)
+ column = rawtext[i + 1] - 0x40;
+ else
+ column = rawtext[i + 1] + 256 - 0x80;
+ // System.out.println("extended row " + row + " column " +
+ // column + " rawtext[i] " + rawtext[i]);
+ if (GBKFreq[row][column] != 0)
+ gbk_freq += GBKFreq[row][column];
+ }
+ i++;
+ }
+ rangeval = 50 * ((float) gbk_chars / (float) gbk_dbchars);
+ freqval = 50 * ((float) gbk_freq / (float) gbk_totalfreq);
+
+ // For regular GB files, this would give the same score, so I handicap
+ // it slightly
+ return (int) (rangeval + freqval) - 1;
+ }
+
+ /**
+ * Function: big5_probability Argument: byte array Returns : number from 0
+ * to 100 representing probability text in array uses Big5 encoding
+ */
+ int big5_dbchars = 1, big5_chars = 1;
+ long big5_freq = 0, big5_totalfreq = 1;
+
+ private int big5_probability(byte[] rawtext, int offset, int length) {
+ // int score = 0;
+ float rangeval, freqval;
+ int row, column;
+
+ // Check to see if characters fit into acceptable ranges
+ int rawLength = offset + length - 1;
+ for (int i = offset; i < rawLength; i++)
+ if (rawtext[i] < 0) {
+ big5_dbchars++;
+ if ((byte) 0xA1 <= rawtext[i]
+ && rawtext[i] <= (byte) 0xF9
+ && ((byte) 0x40 <= rawtext[i + 1]
+ && rawtext[i + 1] <= (byte) 0x7E || (byte) 0xA1 <= rawtext[i + 1]
+ && rawtext[i + 1] <= (byte) 0xFE)) {
+ big5_chars++;
+ big5_totalfreq += 500;
+ row = rawtext[i] + 256 - 0xA1;
+ if (0x40 <= rawtext[i + 1] && rawtext[i + 1] <= 0x7E)
+ column = rawtext[i + 1] - 0x40;
+ else
+ column = rawtext[i + 1] + 256 - 0x61;
+ if (Big5Freq[row][column] != 0)
+ big5_freq += Big5Freq[row][column];
+ else if (3 <= row && row <= 37)
+ big5_freq += 200;
+ }
+ i++;
+ }
+ rangeval = 50 * ((float) big5_chars / (float) big5_dbchars);
+ freqval = 50 * ((float) big5_freq / (float) big5_totalfreq);
+
+ return (int) (rangeval + freqval);
+ }
+
+ /*
+ * Function: euc_tw_probability Argument: byte array Returns : number from 0
+ * to 100 representing probability text in array uses EUC-TW (CNS 11643)
+ * encoding
+ */
+ int tw_dbchars = 1, tw_chars = 1;
+ long tw_freq = 0, tw_totalfreq = 1;
+
+ private int euc_tw_probability(byte[] rawtext, int offset, int length) {
+ float rangeval, freqval;
+ int row, column;
+
+ // Check to see if characters fit into acceptable ranges
+ // and have expected frequency of use
+ int rawLength = offset + length - 1;
+ for (int i = offset; i < rawLength; i++)
+ if (rawtext[i] < 0) {
+ tw_dbchars++;
+ if (i + 3 < offset + length && (byte) 0x8E == rawtext[i]
+ && (byte) 0xA1 <= rawtext[i + 1]
+ && rawtext[i + 1] <= (byte) 0xB0
+ && (byte) 0xA1 <= rawtext[i + 2]
+ && rawtext[i + 2] <= (byte) 0xFE
+ && (byte) 0xA1 <= rawtext[i + 3]
+ && rawtext[i + 3] <= (byte) 0xFE) { // Planes 1 - 16
+
+ tw_chars++;
+ // System.out.println("plane 2 or above CNS char");
+ // These are all less frequent chars so just ignore freq
+ i += 3;
+ } else if ((byte) 0xA1 <= rawtext[i]
+ && rawtext[i] <= (byte) 0xFE
+ && // Plane 1
+ (byte) 0xA1 <= rawtext[i + 1]
+ && rawtext[i + 1] <= (byte) 0xFE) {
+ tw_chars++;
+ tw_totalfreq += 500;
+ row = rawtext[i] + 256 - 0xA1;
+ column = rawtext[i + 1] + 256 - 0xA1;
+ if (EUC_TWFreq[row][column] != 0)
+ tw_freq += EUC_TWFreq[row][column];
+ else if (35 <= row && row <= 92)
+ tw_freq += 150;
+ i++;
+ }
+ }
+
+ rangeval = 50 * ((float) tw_chars / (float) tw_dbchars);
+ freqval = 50 * ((float) tw_freq / (float) tw_totalfreq);
+
+ return (int) (rangeval + freqval);
+ }
+
+ /*
+ * Function: iso_2022_cn_probability Argument: byte array Returns : number
+ * from 0 to 100 representing probability text in array uses ISO 2022-CN
+ * encoding WORKS FOR BASIC CASES, BUT STILL NEEDS MORE WORK
+ */
+// int iso_dbchars = 1, iso_chars = 1;
+// long iso_freq = 0, iso_totalfreq = 1;
+//
+// private int iso_2022_cn_probability(byte[] rawtext, int offset, int length) {
+// float rangeval = 0, freqval = 0;
+// int row, column;
+//
+// // Check to see if characters fit into acceptable ranges
+// // and have expected frequency of use
+//
+// int rawtextlen = offset + length;
+// int rawLength = offset + length - 1;
+// for (int i = offset; i < rawLength; i++)
+// if (rawtext[i] == (byte) 0x1B && i + 3 < rawtextlen) { // Escape
+// // char ESC
+// if (rawtext[i + 1] == (byte) 0x24 && rawtext[i + 2] == 0x29
+// && rawtext[i + 3] == (byte) 0x41) { // GB Escape $ ) A
+// i += 4;
+// while (rawtext[i] != (byte) 0x1B) {
+// iso_dbchars++;
+// if (0x21 <= rawtext[i] && rawtext[i] <= 0x77
+// && 0x21 <= rawtext[i + 1]
+// && rawtext[i + 1] <= 0x77) {
+// iso_chars++;
+// row = rawtext[i] - 0x21;
+// column = rawtext[i + 1] - 0x21;
+// iso_totalfreq += 500;
+// if (GBFreq[row][column] != 0)
+// iso_freq += GBFreq[row][column];
+// else if (15 <= row && row < 55)
+// iso_freq += 200;
+// i++;
+// }
+// i++;
+// }
+// } else if (i + 3 < rawtextlen && rawtext[i + 1] == (byte) 0x24
+// && rawtext[i + 2] == (byte) 0x29
+// && rawtext[i + 3] == (byte) 0x47) {
+// // CNS Escape $ ) G
+// i += 4;
+// while (rawtext[i] != (byte) 0x1B) {
+// iso_dbchars++;
+// if ((byte) 0x21 <= rawtext[i]
+// && rawtext[i] <= (byte) 0x7E
+// && (byte) 0x21 <= rawtext[i + 1]
+// && rawtext[i + 1] <= (byte) 0x7E) {
+// iso_chars++;
+// iso_totalfreq += 500;
+// row = rawtext[i] - 0x21;
+// column = rawtext[i + 1] - 0x21;
+// if (EUC_TWFreq[row][column] != 0)
+// iso_freq += EUC_TWFreq[row][column];
+// else if (35 <= row && row <= 92)
+// iso_freq += 150;
+// i++;
+// }
+// i++;
+// }
+// }
+// if (rawtext[i] == (byte) 0x1B && i + 2 < rawtextlen
+// && rawtext[i + 1] == (byte) 0x28
+// && rawtext[i + 2] == (byte) 0x42)
+// i += 2;
+// }
+// rangeval = 50 * ((float) iso_chars / (float) iso_dbchars);
+// freqval = 50 * ((float) iso_freq / (float) iso_totalfreq);
+//
+// // System.out.println("isochars dbchars isofreq totalfreq " + isochars +
+// // " " + dbchars + " " + isofreq + " " + totalfreq + " " + rangeval +
+// // " " + freqval);
+// return (int) (rangeval + freqval);
+// // return 0;
+// }
+
+ /*
+ * Function: utf8_probability Argument: byte array Returns : number from 0
+ * to 100 representing probability text in array uses UTF-8 encoding of
+ * Unicode
+ */
+ int goodbytes = 0, asciibytes = 0;
+ int utf8Length = 0;
+
+ private int utf8_probability(byte[] rawtext, int offset, int length) {
+ // Maybe also use UTF8 Byte Order Mark: EF BB BF
+
+ // Check to see if characters fit into acceptable ranges
+ int rawtextlen = offset + length;
+ for (int i = offset; i < rawtextlen; i++)
+ if ((rawtext[i] & (byte) 0x7F) == rawtext[i])
+ asciibytes++;
+ // Ignore ASCII, can throw off count
+ else if (-64 <= rawtext[i] && rawtext[i] <= -33
+ && // Two bytes
+ i + 1 < rawtextlen && -128 <= rawtext[i + 1]
+ && rawtext[i + 1] <= -65) {
+ goodbytes += 2;
+ i++;
+ } else if (-32 <= rawtext[i]
+ && rawtext[i] <= -17
+ && // Three bytes
+ i + 2 < rawtextlen && -128 <= rawtext[i + 1]
+ && rawtext[i + 1] <= -65 && -128 <= rawtext[i + 2]
+ && rawtext[i + 2] <= -65) {
+ goodbytes += 3;
+ i += 2;
+ }
+ if (asciibytes == length)
+ return 0;
+
+ utf8Length += length - asciibytes;
+ int score = (int) (100 * ((float) goodbytes / (float) (utf8Length)));
+
+ // If not above 98, reduce to zero to prevent coincidental matches
+ // Allows for some (few) bad formed sequences
+ if (score > 98)
+ return score;
+ else if (score > 95 && goodbytes > 30)
+ return score;
+ else {
+ goodbytes = 0;
+ return 0;
+ }
+
+ }
+
+ /*
+ * Function: utf16_probability Argument: byte array Returns : number from 0
+ * to 100 representing probability text in array uses UTF-16 encoding of
+ * Unicode, guess based on BOM // NOT VERY GENERAL, NEEDS MUCH MORE WORK
+ */
+ boolean impossibleUTF16 = false;
+
+ private int utf16_probability(byte[] rawtext, int offset, int length) {
+ if (impossibleUTF16 || length < 2)
+ return 0;
+ // int score = 0;
+ // int i, rawtextlen = 0;
+ // int goodbytes = 0, asciibytes = 0;
+
+ if ((byte) 0xFE == rawtext[offset] && (byte) 0xFF == rawtext[offset + 1] || // Big-endian
+ (byte) 0xFF == rawtext[offset] && (byte) 0xFE == rawtext[offset + 1])
+ return 100;
+
+ impossibleUTF16 = true;
+ return 0;
+
+ /*
+ * // Check to see if characters fit into acceptable ranges rawtextlen =
+ * rawtext.length; for (i = 0; i < rawtextlen; i++) { if ((rawtext[i] &
+ * (byte)0x7F) == rawtext[i]) { // One byte goodbytes += 1;
+ * asciibytes++; } else if ((rawtext[i] & (byte)0xDF) == rawtext[i]) {
+ * // Two bytes if (i+1 < rawtextlen && (rawtext[i+1] & (byte)0xBF) ==
+ * rawtext[i+1]) { goodbytes += 2; i++; } } else if ((rawtext[i] &
+ * (byte)0xEF) == rawtext[i]) { // Three bytes if (i+2 < rawtextlen &&
+ * (rawtext[i+1] & (byte)0xBF) == rawtext[i+1] && (rawtext[i+2] &
+ * (byte)0xBF) == rawtext[i+2]) { goodbytes += 3; i+=2; } } }
+ *
+ * score = (int)(100 * ((float)goodbytes/(float)rawtext.length));
+ *
+ * // An all ASCII file is also a good UTF8 file, but I'd rather it //
+ * get identified as ASCII. Can delete following 3 lines otherwise if
+ * (goodbytes == asciibytes) { score = 0; }
+ *
+ * // If not above 90, reduce to zero to prevent coincidental matches if
+ * (score > 90) { return score; } else { return 0; }
+ */
+
+ }
+
+ /*
+ * Function: ascii_probability Argument: byte array Returns : number from 0
+ * to 100 representing probability text in array uses all ASCII Description:
+ * Sees if array has any characters not in ASCII range, if so, score is
+ * reduced
+ */
+ int ascii_score = 70;
+
+ private int ascii_probability(byte[] rawtext, int offset, int length) {
+ for (int i = offset; i < length; i++) {
+ byte aRawtext = rawtext[i];
+ if (ascii_score <= 0) {
+ return 0;
+ }
+ if (aRawtext < 0)
+ ascii_score = ascii_score - 5;
+ else if (aRawtext == (byte) 0x1B)
+ ascii_score = ascii_score - 5;
+ }
+
+ return ascii_score;
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ static void initialize_frequencies() {
+ int i, j;
+
+ for (i = 0; i < 93; i++)
+ for (j = 0; j < 93; j++)
+ GBFreq[i][j] = 0;
+
+ for (i = 0; i < 126; i++)
+ for (j = 0; j < 191; j++)
+ GBKFreq[i][j] = 0;
+
+ for (i = 0; i < 93; i++)
+ for (j = 0; j < 157; j++)
+ Big5Freq[i][j] = 0;
+
+ for (i = 0; i < 93; i++)
+ for (j = 0; j < 93; j++)
+ EUC_TWFreq[i][j] = 0;
+
+ GBFreq[20][35] = 599;
+ GBFreq[49][26] = 598;
+ GBFreq[41][38] = 597;
+ GBFreq[17][26] = 596;
+ GBFreq[32][42] = 595;
+ GBFreq[39][42] = 594;
+ GBFreq[45][49] = 593;
+ GBFreq[51][57] = 592;
+ GBFreq[50][47] = 591;
+ GBFreq[42][90] = 590;
+ GBFreq[52][65] = 589;
+ GBFreq[53][47] = 588;
+ GBFreq[19][82] = 587;
+ GBFreq[31][19] = 586;
+ GBFreq[40][46] = 585;
+ GBFreq[24][89] = 584;
+ GBFreq[23][85] = 583;
+ GBFreq[20][28] = 582;
+ GBFreq[42][20] = 581;
+ GBFreq[34][38] = 580;
+ GBFreq[45][9] = 579;
+ GBFreq[54][50] = 578;
+ GBFreq[25][44] = 577;
+ GBFreq[35][66] = 576;
+ GBFreq[20][55] = 575;
+ GBFreq[18][85] = 574;
+ GBFreq[20][31] = 573;
+ GBFreq[49][17] = 572;
+ GBFreq[41][16] = 571;
+ GBFreq[35][73] = 570;
+ GBFreq[20][34] = 569;
+ GBFreq[29][44] = 568;
+ GBFreq[35][38] = 567;
+ GBFreq[49][9] = 566;
+ GBFreq[46][33] = 565;
+ GBFreq[49][51] = 564;
+ GBFreq[40][89] = 563;
+ GBFreq[26][64] = 562;
+ GBFreq[54][51] = 561;
+ GBFreq[54][36] = 560;
+ GBFreq[39][4] = 559;
+ GBFreq[53][13] = 558;
+ GBFreq[24][92] = 557;
+ GBFreq[27][49] = 556;
+ GBFreq[48][6] = 555;
+ GBFreq[21][51] = 554;
+ GBFreq[30][40] = 553;
+ GBFreq[42][92] = 552;
+ GBFreq[31][78] = 551;
+ GBFreq[25][82] = 550;
+ GBFreq[47][0] = 549;
+ GBFreq[34][19] = 548;
+ GBFreq[47][35] = 547;
+ GBFreq[21][63] = 546;
+ GBFreq[43][75] = 545;
+ GBFreq[21][87] = 544;
+ GBFreq[35][59] = 543;
+ GBFreq[25][34] = 542;
+ GBFreq[21][27] = 541;
+ GBFreq[39][26] = 540;
+ GBFreq[34][26] = 539;
+ GBFreq[39][52] = 538;
+ GBFreq[50][57] = 537;
+ GBFreq[37][79] = 536;
+ GBFreq[26][24] = 535;
+ GBFreq[22][1] = 534;
+ GBFreq[18][40] = 533;
+ GBFreq[41][33] = 532;
+ GBFreq[53][26] = 531;
+ GBFreq[54][86] = 530;
+ GBFreq[20][16] = 529;
+ GBFreq[46][74] = 528;
+ GBFreq[30][19] = 527;
+ GBFreq[45][35] = 526;
+ GBFreq[45][61] = 525;
+ GBFreq[30][9] = 524;
+ GBFreq[41][53] = 523;
+ GBFreq[41][13] = 522;
+ GBFreq[50][34] = 521;
+ GBFreq[53][86] = 520;
+ GBFreq[47][47] = 519;
+ GBFreq[22][28] = 518;
+ GBFreq[50][53] = 517;
+ GBFreq[39][70] = 516;
+ GBFreq[38][15] = 515;
+ GBFreq[42][88] = 514;
+ GBFreq[16][29] = 513;
+ GBFreq[27][90] = 512;
+ GBFreq[29][12] = 511;
+ GBFreq[44][22] = 510;
+ GBFreq[34][69] = 509;
+ GBFreq[24][10] = 508;
+ GBFreq[44][11] = 507;
+ GBFreq[39][92] = 506;
+ GBFreq[49][48] = 505;
+ GBFreq[31][46] = 504;
+ GBFreq[19][50] = 503;
+ GBFreq[21][14] = 502;
+ GBFreq[32][28] = 501;
+ GBFreq[18][3] = 500;
+ GBFreq[53][9] = 499;
+ GBFreq[34][80] = 498;
+ GBFreq[48][88] = 497;
+ GBFreq[46][53] = 496;
+ GBFreq[22][53] = 495;
+ GBFreq[28][10] = 494;
+ GBFreq[44][65] = 493;
+ GBFreq[20][10] = 492;
+ GBFreq[40][76] = 491;
+ GBFreq[47][8] = 490;
+ GBFreq[50][74] = 489;
+ GBFreq[23][62] = 488;
+ GBFreq[49][65] = 487;
+ GBFreq[28][87] = 486;
+ GBFreq[15][48] = 485;
+ GBFreq[22][7] = 484;
+ GBFreq[19][42] = 483;
+ GBFreq[41][20] = 482;
+ GBFreq[26][55] = 481;
+ GBFreq[21][93] = 480;
+ GBFreq[31][76] = 479;
+ GBFreq[34][31] = 478;
+ GBFreq[20][66] = 477;
+ GBFreq[51][33] = 476;
+ GBFreq[34][86] = 475;
+ GBFreq[37][67] = 474;
+ GBFreq[53][53] = 473;
+ GBFreq[40][88] = 472;
+ GBFreq[39][10] = 471;
+ GBFreq[24][3] = 470;
+ GBFreq[27][25] = 469;
+ GBFreq[26][15] = 468;
+ GBFreq[21][88] = 467;
+ GBFreq[52][62] = 466;
+ GBFreq[46][81] = 465;
+ GBFreq[38][72] = 464;
+ GBFreq[17][30] = 463;
+ GBFreq[52][92] = 462;
+ GBFreq[34][90] = 461;
+ GBFreq[21][7] = 460;
+ GBFreq[36][13] = 459;
+ GBFreq[45][41] = 458;
+ GBFreq[32][5] = 457;
+ GBFreq[26][89] = 456;
+ GBFreq[23][87] = 455;
+ GBFreq[20][39] = 454;
+ GBFreq[27][23] = 453;
+ GBFreq[25][59] = 452;
+ GBFreq[49][20] = 451;
+ GBFreq[54][77] = 450;
+ GBFreq[27][67] = 449;
+ GBFreq[47][33] = 448;
+ GBFreq[41][17] = 447;
+ GBFreq[19][81] = 446;
+ GBFreq[16][66] = 445;
+ GBFreq[45][26] = 444;
+ GBFreq[49][81] = 443;
+ GBFreq[53][55] = 442;
+ GBFreq[16][26] = 441;
+ GBFreq[54][62] = 440;
+ GBFreq[20][70] = 439;
+ GBFreq[42][35] = 438;
+ GBFreq[20][57] = 437;
+ GBFreq[34][36] = 436;
+ GBFreq[46][63] = 435;
+ GBFreq[19][45] = 434;
+ GBFreq[21][10] = 433;
+ GBFreq[52][93] = 432;
+ GBFreq[25][2] = 431;
+ GBFreq[30][57] = 430;
+ GBFreq[41][24] = 429;
+ GBFreq[28][43] = 428;
+ GBFreq[45][86] = 427;
+ GBFreq[51][56] = 426;
+ GBFreq[37][28] = 425;
+ GBFreq[52][69] = 424;
+ GBFreq[43][92] = 423;
+ GBFreq[41][31] = 422;
+ GBFreq[37][87] = 421;
+ GBFreq[47][36] = 420;
+ GBFreq[16][16] = 419;
+ GBFreq[40][56] = 418;
+ GBFreq[24][55] = 417;
+ GBFreq[17][1] = 416;
+ GBFreq[35][57] = 415;
+ GBFreq[27][50] = 414;
+ GBFreq[26][14] = 413;
+ GBFreq[50][40] = 412;
+ GBFreq[39][19] = 411;
+ GBFreq[19][89] = 410;
+ GBFreq[29][91] = 409;
+ GBFreq[17][89] = 408;
+ GBFreq[39][74] = 407;
+ GBFreq[46][39] = 406;
+ GBFreq[40][28] = 405;
+ GBFreq[45][68] = 404;
+ GBFreq[43][10] = 403;
+ GBFreq[42][13] = 402;
+ GBFreq[44][81] = 401;
+ GBFreq[41][47] = 400;
+ GBFreq[48][58] = 399;
+ GBFreq[43][68] = 398;
+ GBFreq[16][79] = 397;
+ GBFreq[19][5] = 396;
+ GBFreq[54][59] = 395;
+ GBFreq[17][36] = 394;
+ GBFreq[18][0] = 393;
+ GBFreq[41][5] = 392;
+ GBFreq[41][72] = 391;
+ GBFreq[16][39] = 390;
+ GBFreq[54][0] = 389;
+ GBFreq[51][16] = 388;
+ GBFreq[29][36] = 387;
+ GBFreq[47][5] = 386;
+ GBFreq[47][51] = 385;
+ GBFreq[44][7] = 384;
+ GBFreq[35][30] = 383;
+ GBFreq[26][9] = 382;
+ GBFreq[16][7] = 381;
+ GBFreq[32][1] = 380;
+ GBFreq[33][76] = 379;
+ GBFreq[34][91] = 378;
+ GBFreq[52][36] = 377;
+ GBFreq[26][77] = 376;
+ GBFreq[35][48] = 375;
+ GBFreq[40][80] = 374;
+ GBFreq[41][92] = 373;
+ GBFreq[27][93] = 372;
+ GBFreq[15][17] = 371;
+ GBFreq[16][76] = 370;
+ GBFreq[51][12] = 369;
+ GBFreq[18][20] = 368;
+ GBFreq[15][54] = 367;
+ GBFreq[50][5] = 366;
+ GBFreq[33][22] = 365;
+ GBFreq[37][57] = 364;
+ GBFreq[28][47] = 363;
+ GBFreq[42][31] = 362;
+ GBFreq[18][2] = 361;
+ GBFreq[43][64] = 360;
+ GBFreq[23][47] = 359;
+ GBFreq[28][79] = 358;
+ GBFreq[25][45] = 357;
+ GBFreq[23][91] = 356;
+ GBFreq[22][19] = 355;
+ GBFreq[25][46] = 354;
+ GBFreq[22][36] = 353;
+ GBFreq[54][85] = 352;
+ GBFreq[46][20] = 351;
+ GBFreq[27][37] = 350;
+ GBFreq[26][81] = 349;
+ GBFreq[42][29] = 348;
+ GBFreq[31][90] = 347;
+ GBFreq[41][59] = 346;
+ GBFreq[24][65] = 345;
+ GBFreq[44][84] = 344;
+ GBFreq[24][90] = 343;
+ GBFreq[38][54] = 342;
+ GBFreq[28][70] = 341;
+ GBFreq[27][15] = 340;
+ GBFreq[28][80] = 339;
+ GBFreq[29][8] = 338;
+ GBFreq[45][80] = 337;
+ GBFreq[53][37] = 336;
+ GBFreq[28][65] = 335;
+ GBFreq[23][86] = 334;
+ GBFreq[39][45] = 333;
+ GBFreq[53][32] = 332;
+ GBFreq[38][68] = 331;
+ GBFreq[45][78] = 330;
+ GBFreq[43][7] = 329;
+ GBFreq[46][82] = 328;
+ GBFreq[27][38] = 327;
+ GBFreq[16][62] = 326;
+ GBFreq[24][17] = 325;
+ GBFreq[22][70] = 324;
+ GBFreq[52][28] = 323;
+ GBFreq[23][40] = 322;
+ GBFreq[28][50] = 321;
+ GBFreq[42][91] = 320;
+ GBFreq[47][76] = 319;
+ GBFreq[15][42] = 318;
+ GBFreq[43][55] = 317;
+ GBFreq[29][84] = 316;
+ GBFreq[44][90] = 315;
+ GBFreq[53][16] = 314;
+ GBFreq[22][93] = 313;
+ GBFreq[34][10] = 312;
+ GBFreq[32][53] = 311;
+ GBFreq[43][65] = 310;
+ GBFreq[28][7] = 309;
+ GBFreq[35][46] = 308;
+ GBFreq[21][39] = 307;
+ GBFreq[44][18] = 306;
+ GBFreq[40][10] = 305;
+ GBFreq[54][53] = 304;
+ GBFreq[38][74] = 303;
+ GBFreq[28][26] = 302;
+ GBFreq[15][13] = 301;
+ GBFreq[39][34] = 300;
+ GBFreq[39][46] = 299;
+ GBFreq[42][66] = 298;
+ GBFreq[33][58] = 297;
+ GBFreq[15][56] = 296;
+ GBFreq[18][51] = 295;
+ GBFreq[49][68] = 294;
+ GBFreq[30][37] = 293;
+ GBFreq[51][84] = 292;
+ GBFreq[51][9] = 291;
+ GBFreq[40][70] = 290;
+ GBFreq[41][84] = 289;
+ GBFreq[28][64] = 288;
+ GBFreq[32][88] = 287;
+ GBFreq[24][5] = 286;
+ GBFreq[53][23] = 285;
+ GBFreq[42][27] = 284;
+ GBFreq[22][38] = 283;
+ GBFreq[32][86] = 282;
+ GBFreq[34][30] = 281;
+ GBFreq[38][63] = 280;
+ GBFreq[24][59] = 279;
+ GBFreq[22][81] = 278;
+ GBFreq[32][11] = 277;
+ GBFreq[51][21] = 276;
+ GBFreq[54][41] = 275;
+ GBFreq[21][50] = 274;
+ GBFreq[23][89] = 273;
+ GBFreq[19][87] = 272;
+ GBFreq[26][7] = 271;
+ GBFreq[30][75] = 270;
+ GBFreq[43][84] = 269;
+ GBFreq[51][25] = 268;
+ GBFreq[16][67] = 267;
+ GBFreq[32][9] = 266;
+ GBFreq[48][51] = 265;
+ GBFreq[39][7] = 264;
+ GBFreq[44][88] = 263;
+ GBFreq[52][24] = 262;
+ GBFreq[23][34] = 261;
+ GBFreq[32][75] = 260;
+ GBFreq[19][10] = 259;
+ GBFreq[28][91] = 258;
+ GBFreq[32][83] = 257;
+ GBFreq[25][75] = 256;
+ GBFreq[53][45] = 255;
+ GBFreq[29][85] = 254;
+ GBFreq[53][59] = 253;
+ GBFreq[16][2] = 252;
+ GBFreq[19][78] = 251;
+ GBFreq[15][75] = 250;
+ GBFreq[51][42] = 249;
+ GBFreq[45][67] = 248;
+ GBFreq[15][74] = 247;
+ GBFreq[25][81] = 246;
+ GBFreq[37][62] = 245;
+ GBFreq[16][55] = 244;
+ GBFreq[18][38] = 243;
+ GBFreq[23][23] = 242;
+ GBFreq[38][30] = 241;
+ GBFreq[17][28] = 240;
+ GBFreq[44][73] = 239;
+ GBFreq[23][78] = 238;
+ GBFreq[40][77] = 237;
+ GBFreq[38][87] = 236;
+ GBFreq[27][19] = 235;
+ GBFreq[38][82] = 234;
+ GBFreq[37][22] = 233;
+ GBFreq[41][30] = 232;
+ GBFreq[54][9] = 231;
+ GBFreq[32][30] = 230;
+ GBFreq[30][52] = 229;
+ GBFreq[40][84] = 228;
+ GBFreq[53][57] = 227;
+ GBFreq[27][27] = 226;
+ GBFreq[38][64] = 225;
+ GBFreq[18][43] = 224;
+ GBFreq[23][69] = 223;
+ GBFreq[28][12] = 222;
+ GBFreq[50][78] = 221;
+ GBFreq[50][1] = 220;
+ GBFreq[26][88] = 219;
+ GBFreq[36][40] = 218;
+ GBFreq[33][89] = 217;
+ GBFreq[41][28] = 216;
+ GBFreq[31][77] = 215;
+ GBFreq[46][1] = 214;
+ GBFreq[47][19] = 213;
+ GBFreq[35][55] = 212;
+ GBFreq[41][21] = 211;
+ GBFreq[27][10] = 210;
+ GBFreq[32][77] = 209;
+ GBFreq[26][37] = 208;
+ GBFreq[20][33] = 207;
+ GBFreq[41][52] = 206;
+ GBFreq[32][18] = 205;
+ GBFreq[38][13] = 204;
+ GBFreq[20][18] = 203;
+ GBFreq[20][24] = 202;
+ GBFreq[45][19] = 201;
+ GBFreq[18][53] = 200;
+
+ Big5Freq[9][89] = 600;
+ Big5Freq[11][15] = 599;
+ Big5Freq[3][66] = 598;
+ Big5Freq[6][121] = 597;
+ Big5Freq[3][0] = 596;
+ Big5Freq[5][82] = 595;
+ Big5Freq[3][42] = 594;
+ Big5Freq[5][34] = 593;
+ Big5Freq[3][8] = 592;
+ Big5Freq[3][6] = 591;
+ Big5Freq[3][67] = 590;
+ Big5Freq[7][139] = 589;
+ Big5Freq[23][137] = 588;
+ Big5Freq[12][46] = 587;
+ Big5Freq[4][8] = 586;
+ Big5Freq[4][41] = 585;
+ Big5Freq[18][47] = 584;
+ Big5Freq[12][114] = 583;
+ Big5Freq[6][1] = 582;
+ Big5Freq[22][60] = 581;
+ Big5Freq[5][46] = 580;
+ Big5Freq[11][79] = 579;
+ Big5Freq[3][23] = 578;
+ Big5Freq[7][114] = 577;
+ Big5Freq[29][102] = 576;
+ Big5Freq[19][14] = 575;
+ Big5Freq[4][133] = 574;
+ Big5Freq[3][29] = 573;
+ Big5Freq[4][109] = 572;
+ Big5Freq[14][127] = 571;
+ Big5Freq[5][48] = 570;
+ Big5Freq[13][104] = 569;
+ Big5Freq[3][132] = 568;
+ Big5Freq[26][64] = 567;
+ Big5Freq[7][19] = 566;
+ Big5Freq[4][12] = 565;
+ Big5Freq[11][124] = 564;
+ Big5Freq[7][89] = 563;
+ Big5Freq[15][124] = 562;
+ Big5Freq[4][108] = 561;
+ Big5Freq[19][66] = 560;
+ Big5Freq[3][21] = 559;
+ Big5Freq[24][12] = 558;
+ Big5Freq[28][111] = 557;
+ Big5Freq[12][107] = 556;
+ Big5Freq[3][112] = 555;
+ Big5Freq[8][113] = 554;
+ Big5Freq[5][40] = 553;
+ Big5Freq[26][145] = 552;
+ Big5Freq[3][48] = 551;
+ Big5Freq[3][70] = 550;
+ Big5Freq[22][17] = 549;
+ Big5Freq[16][47] = 548;
+ Big5Freq[3][53] = 547;
+ Big5Freq[4][24] = 546;
+ Big5Freq[32][120] = 545;
+ Big5Freq[24][49] = 544;
+ Big5Freq[24][142] = 543;
+ Big5Freq[18][66] = 542;
+ Big5Freq[29][150] = 541;
+ Big5Freq[5][122] = 540;
+ Big5Freq[5][114] = 539;
+ Big5Freq[3][44] = 538;
+ Big5Freq[10][128] = 537;
+ Big5Freq[15][20] = 536;
+ Big5Freq[13][33] = 535;
+ Big5Freq[14][87] = 534;
+ Big5Freq[3][126] = 533;
+ Big5Freq[4][53] = 532;
+ Big5Freq[4][40] = 531;
+ Big5Freq[9][93] = 530;
+ Big5Freq[15][137] = 529;
+ Big5Freq[10][123] = 528;
+ Big5Freq[4][56] = 527;
+ Big5Freq[5][71] = 526;
+ Big5Freq[10][8] = 525;
+ Big5Freq[5][16] = 524;
+ Big5Freq[5][146] = 523;
+ Big5Freq[18][88] = 522;
+ Big5Freq[24][4] = 521;
+ Big5Freq[20][47] = 520;
+ Big5Freq[5][33] = 519;
+ Big5Freq[9][43] = 518;
+ Big5Freq[20][12] = 517;
+ Big5Freq[20][13] = 516;
+ Big5Freq[5][156] = 515;
+ Big5Freq[22][140] = 514;
+ Big5Freq[8][146] = 513;
+ Big5Freq[21][123] = 512;
+ Big5Freq[4][90] = 511;
+ Big5Freq[5][62] = 510;
+ Big5Freq[17][59] = 509;
+ Big5Freq[10][37] = 508;
+ Big5Freq[18][107] = 507;
+ Big5Freq[14][53] = 506;
+ Big5Freq[22][51] = 505;
+ Big5Freq[8][13] = 504;
+ Big5Freq[5][29] = 503;
+ Big5Freq[9][7] = 502;
+ Big5Freq[22][14] = 501;
+ Big5Freq[8][55] = 500;
+ Big5Freq[33][9] = 499;
+ Big5Freq[16][64] = 498;
+ Big5Freq[7][131] = 497;
+ Big5Freq[34][4] = 496;
+ Big5Freq[7][101] = 495;
+ Big5Freq[11][139] = 494;
+ Big5Freq[3][135] = 493;
+ Big5Freq[7][102] = 492;
+ Big5Freq[17][13] = 491;
+ Big5Freq[3][20] = 490;
+ Big5Freq[27][106] = 489;
+ Big5Freq[5][88] = 488;
+ Big5Freq[6][33] = 487;
+ Big5Freq[5][139] = 486;
+ Big5Freq[6][0] = 485;
+ Big5Freq[17][58] = 484;
+ Big5Freq[5][133] = 483;
+ Big5Freq[9][107] = 482;
+ Big5Freq[23][39] = 481;
+ Big5Freq[5][23] = 480;
+ Big5Freq[3][79] = 479;
+ Big5Freq[32][97] = 478;
+ Big5Freq[3][136] = 477;
+ Big5Freq[4][94] = 476;
+ Big5Freq[21][61] = 475;
+ Big5Freq[23][123] = 474;
+ Big5Freq[26][16] = 473;
+ Big5Freq[24][137] = 472;
+ Big5Freq[22][18] = 471;
+ Big5Freq[5][1] = 470;
+ Big5Freq[20][119] = 469;
+ Big5Freq[3][7] = 468;
+ Big5Freq[10][79] = 467;
+ Big5Freq[15][105] = 466;
+ Big5Freq[3][144] = 465;
+ Big5Freq[12][80] = 464;
+ Big5Freq[15][73] = 463;
+ Big5Freq[3][19] = 462;
+ Big5Freq[8][109] = 461;
+ Big5Freq[3][15] = 460;
+ Big5Freq[31][82] = 459;
+ Big5Freq[3][43] = 458;
+ Big5Freq[25][119] = 457;
+ Big5Freq[16][111] = 456;
+ Big5Freq[7][77] = 455;
+ Big5Freq[3][95] = 454;
+ Big5Freq[24][82] = 453;
+ Big5Freq[7][52] = 452;
+ Big5Freq[9][151] = 451;
+ Big5Freq[3][129] = 450;
+ Big5Freq[5][87] = 449;
+ Big5Freq[3][55] = 448;
+ Big5Freq[8][153] = 447;
+ Big5Freq[4][83] = 446;
+ Big5Freq[3][114] = 445;
+ Big5Freq[23][147] = 444;
+ Big5Freq[15][31] = 443;
+ Big5Freq[3][54] = 442;
+ Big5Freq[11][122] = 441;
+ Big5Freq[4][4] = 440;
+ Big5Freq[34][149] = 439;
+ Big5Freq[3][17] = 438;
+ Big5Freq[21][64] = 437;
+ Big5Freq[26][144] = 436;
+ Big5Freq[4][62] = 435;
+ Big5Freq[8][15] = 434;
+ Big5Freq[35][80] = 433;
+ Big5Freq[7][110] = 432;
+ Big5Freq[23][114] = 431;
+ Big5Freq[3][108] = 430;
+ Big5Freq[3][62] = 429;
+ Big5Freq[21][41] = 428;
+ Big5Freq[15][99] = 427;
+ Big5Freq[5][47] = 426;
+ Big5Freq[4][96] = 425;
+ Big5Freq[20][122] = 424;
+ Big5Freq[5][21] = 423;
+ Big5Freq[4][157] = 422;
+ Big5Freq[16][14] = 421;
+ Big5Freq[3][117] = 420;
+ Big5Freq[7][129] = 419;
+ Big5Freq[4][27] = 418;
+ Big5Freq[5][30] = 417;
+ Big5Freq[22][16] = 416;
+ Big5Freq[5][64] = 415;
+ Big5Freq[17][99] = 414;
+ Big5Freq[17][57] = 413;
+ Big5Freq[8][105] = 412;
+ Big5Freq[5][112] = 411;
+ Big5Freq[20][59] = 410;
+ Big5Freq[6][129] = 409;
+ Big5Freq[18][17] = 408;
+ Big5Freq[3][92] = 407;
+ Big5Freq[28][118] = 406;
+ Big5Freq[3][109] = 405;
+ Big5Freq[31][51] = 404;
+ Big5Freq[13][116] = 403;
+ Big5Freq[6][15] = 402;
+ Big5Freq[36][136] = 401;
+ Big5Freq[12][74] = 400;
+ Big5Freq[20][88] = 399;
+ Big5Freq[36][68] = 398;
+ Big5Freq[3][147] = 397;
+ Big5Freq[15][84] = 396;
+ Big5Freq[16][32] = 395;
+ Big5Freq[16][58] = 394;
+ Big5Freq[7][66] = 393;
+ Big5Freq[23][107] = 392;
+ Big5Freq[9][6] = 391;
+ Big5Freq[12][86] = 390;
+ Big5Freq[23][112] = 389;
+ Big5Freq[37][23] = 388;
+ Big5Freq[3][138] = 387;
+ Big5Freq[20][68] = 386;
+ Big5Freq[15][116] = 385;
+ Big5Freq[18][64] = 384;
+ Big5Freq[12][139] = 383;
+ Big5Freq[11][155] = 382;
+ Big5Freq[4][156] = 381;
+ Big5Freq[12][84] = 380;
+ Big5Freq[18][49] = 379;
+ Big5Freq[25][125] = 378;
+ Big5Freq[25][147] = 377;
+ Big5Freq[15][110] = 376;
+ Big5Freq[19][96] = 375;
+ Big5Freq[30][152] = 374;
+ Big5Freq[6][31] = 373;
+ Big5Freq[27][117] = 372;
+ Big5Freq[3][10] = 371;
+ Big5Freq[6][131] = 370;
+ Big5Freq[13][112] = 369;
+ Big5Freq[36][156] = 368;
+ Big5Freq[4][60] = 367;
+ Big5Freq[15][121] = 366;
+ Big5Freq[4][112] = 365;
+ Big5Freq[30][142] = 364;
+ Big5Freq[23][154] = 363;
+ Big5Freq[27][101] = 362;
+ Big5Freq[9][140] = 361;
+ Big5Freq[3][89] = 360;
+ Big5Freq[18][148] = 359;
+ Big5Freq[4][69] = 358;
+ Big5Freq[16][49] = 357;
+ Big5Freq[6][117] = 356;
+ Big5Freq[36][55] = 355;
+ Big5Freq[5][123] = 354;
+ Big5Freq[4][126] = 353;
+ Big5Freq[4][119] = 352;
+ Big5Freq[9][95] = 351;
+ Big5Freq[5][24] = 350;
+ Big5Freq[16][133] = 349;
+ Big5Freq[10][134] = 348;
+ Big5Freq[26][59] = 347;
+ Big5Freq[6][41] = 346;
+ Big5Freq[6][146] = 345;
+ Big5Freq[19][24] = 344;
+ Big5Freq[5][113] = 343;
+ Big5Freq[10][118] = 342;
+ Big5Freq[34][151] = 341;
+ Big5Freq[9][72] = 340;
+ Big5Freq[31][25] = 339;
+ Big5Freq[18][126] = 338;
+ Big5Freq[18][28] = 337;
+ Big5Freq[4][153] = 336;
+ Big5Freq[3][84] = 335;
+ Big5Freq[21][18] = 334;
+ Big5Freq[25][129] = 333;
+ Big5Freq[6][107] = 332;
+ Big5Freq[12][25] = 331;
+ Big5Freq[17][109] = 330;
+ Big5Freq[7][76] = 329;
+ Big5Freq[15][15] = 328;
+ Big5Freq[4][14] = 327;
+ Big5Freq[23][88] = 326;
+ Big5Freq[18][2] = 325;
+ Big5Freq[6][88] = 324;
+ Big5Freq[16][84] = 323;
+ Big5Freq[12][48] = 322;
+ Big5Freq[7][68] = 321;
+ Big5Freq[5][50] = 320;
+ Big5Freq[13][54] = 319;
+ Big5Freq[7][98] = 318;
+ Big5Freq[11][6] = 317;
+ Big5Freq[9][80] = 316;
+ Big5Freq[16][41] = 315;
+ Big5Freq[7][43] = 314;
+ Big5Freq[28][117] = 313;
+ Big5Freq[3][51] = 312;
+ Big5Freq[7][3] = 311;
+ Big5Freq[20][81] = 310;
+ Big5Freq[4][2] = 309;
+ Big5Freq[11][16] = 308;
+ Big5Freq[10][4] = 307;
+ Big5Freq[10][119] = 306;
+ Big5Freq[6][142] = 305;
+ Big5Freq[18][51] = 304;
+ Big5Freq[8][144] = 303;
+ Big5Freq[10][65] = 302;
+ Big5Freq[11][64] = 301;
+ Big5Freq[11][130] = 300;
+ Big5Freq[9][92] = 299;
+ Big5Freq[18][29] = 298;
+ Big5Freq[18][78] = 297;
+ Big5Freq[18][151] = 296;
+ Big5Freq[33][127] = 295;
+ Big5Freq[35][113] = 294;
+ Big5Freq[10][155] = 293;
+ Big5Freq[3][76] = 292;
+ Big5Freq[36][123] = 291;
+ Big5Freq[13][143] = 290;
+ Big5Freq[5][135] = 289;
+ Big5Freq[23][116] = 288;
+ Big5Freq[6][101] = 287;
+ Big5Freq[14][74] = 286;
+ Big5Freq[7][153] = 285;
+ Big5Freq[3][101] = 284;
+ Big5Freq[9][74] = 283;
+ Big5Freq[3][156] = 282;
+ Big5Freq[4][147] = 281;
+ Big5Freq[9][12] = 280;
+ Big5Freq[18][133] = 279;
+ Big5Freq[4][0] = 278;
+ Big5Freq[7][155] = 277;
+ Big5Freq[9][144] = 276;
+ Big5Freq[23][49] = 275;
+ Big5Freq[5][89] = 274;
+ Big5Freq[10][11] = 273;
+ Big5Freq[3][110] = 272;
+ Big5Freq[3][40] = 271;
+ Big5Freq[29][115] = 270;
+ Big5Freq[9][100] = 269;
+ Big5Freq[21][67] = 268;
+ Big5Freq[23][145] = 267;
+ Big5Freq[10][47] = 266;
+ Big5Freq[4][31] = 265;
+ Big5Freq[4][81] = 264;
+ Big5Freq[22][62] = 263;
+ Big5Freq[4][28] = 262;
+ Big5Freq[27][39] = 261;
+ Big5Freq[27][54] = 260;
+ Big5Freq[32][46] = 259;
+ Big5Freq[4][76] = 258;
+ Big5Freq[26][15] = 257;
+ Big5Freq[12][154] = 256;
+ Big5Freq[9][150] = 255;
+ Big5Freq[15][17] = 254;
+ Big5Freq[5][129] = 253;
+ Big5Freq[10][40] = 252;
+ Big5Freq[13][37] = 251;
+ Big5Freq[31][104] = 250;
+ Big5Freq[3][152] = 249;
+ Big5Freq[5][22] = 248;
+ Big5Freq[8][48] = 247;
+ Big5Freq[4][74] = 246;
+ Big5Freq[6][17] = 245;
+ Big5Freq[30][82] = 244;
+ Big5Freq[4][116] = 243;
+ Big5Freq[16][42] = 242;
+ Big5Freq[5][55] = 241;
+ Big5Freq[4][64] = 240;
+ Big5Freq[14][19] = 239;
+ Big5Freq[35][82] = 238;
+ Big5Freq[30][139] = 237;
+ Big5Freq[26][152] = 236;
+ Big5Freq[32][32] = 235;
+ Big5Freq[21][102] = 234;
+ Big5Freq[10][131] = 233;
+ Big5Freq[9][128] = 232;
+ Big5Freq[3][87] = 231;
+ Big5Freq[4][51] = 230;
+ Big5Freq[10][15] = 229;
+ Big5Freq[4][150] = 228;
+ Big5Freq[7][4] = 227;
+ Big5Freq[7][51] = 226;
+ Big5Freq[7][157] = 225;
+ Big5Freq[4][146] = 224;
+ Big5Freq[4][91] = 223;
+ Big5Freq[7][13] = 222;
+ Big5Freq[17][116] = 221;
+ Big5Freq[23][21] = 220;
+ Big5Freq[5][106] = 219;
+ Big5Freq[14][100] = 218;
+ Big5Freq[10][152] = 217;
+ Big5Freq[14][89] = 216;
+ Big5Freq[6][138] = 215;
+ Big5Freq[12][157] = 214;
+ Big5Freq[10][102] = 213;
+ Big5Freq[19][94] = 212;
+ Big5Freq[7][74] = 211;
+ Big5Freq[18][128] = 210;
+ Big5Freq[27][111] = 209;
+ Big5Freq[11][57] = 208;
+ Big5Freq[3][131] = 207;
+ Big5Freq[30][23] = 206;
+ Big5Freq[30][126] = 205;
+ Big5Freq[4][36] = 204;
+ Big5Freq[26][124] = 203;
+ Big5Freq[4][19] = 202;
+ Big5Freq[9][152] = 201;
+
+ EUC_TWFreq[48][49] = 599;
+ EUC_TWFreq[35][65] = 598;
+ EUC_TWFreq[41][27] = 597;
+ EUC_TWFreq[35][0] = 596;
+ EUC_TWFreq[39][19] = 595;
+ EUC_TWFreq[35][42] = 594;
+ EUC_TWFreq[38][66] = 593;
+ EUC_TWFreq[35][8] = 592;
+ EUC_TWFreq[35][6] = 591;
+ EUC_TWFreq[35][66] = 590;
+ EUC_TWFreq[43][14] = 589;
+ EUC_TWFreq[69][80] = 588;
+ EUC_TWFreq[50][48] = 587;
+ EUC_TWFreq[36][71] = 586;
+ EUC_TWFreq[37][10] = 585;
+ EUC_TWFreq[60][52] = 584;
+ EUC_TWFreq[51][21] = 583;
+ EUC_TWFreq[40][2] = 582;
+ EUC_TWFreq[67][35] = 581;
+ EUC_TWFreq[38][78] = 580;
+ EUC_TWFreq[49][18] = 579;
+ EUC_TWFreq[35][23] = 578;
+ EUC_TWFreq[42][83] = 577;
+ EUC_TWFreq[79][47] = 576;
+ EUC_TWFreq[61][82] = 575;
+ EUC_TWFreq[38][7] = 574;
+ EUC_TWFreq[35][29] = 573;
+ EUC_TWFreq[37][77] = 572;
+ EUC_TWFreq[54][67] = 571;
+ EUC_TWFreq[38][80] = 570;
+ EUC_TWFreq[52][74] = 569;
+ EUC_TWFreq[36][37] = 568;
+ EUC_TWFreq[74][8] = 567;
+ EUC_TWFreq[41][83] = 566;
+ EUC_TWFreq[36][75] = 565;
+ EUC_TWFreq[49][63] = 564;
+ EUC_TWFreq[42][58] = 563;
+ EUC_TWFreq[56][33] = 562;
+ EUC_TWFreq[37][76] = 561;
+ EUC_TWFreq[62][39] = 560;
+ EUC_TWFreq[35][21] = 559;
+ EUC_TWFreq[70][19] = 558;
+ EUC_TWFreq[77][88] = 557;
+ EUC_TWFreq[51][14] = 556;
+ EUC_TWFreq[36][17] = 555;
+ EUC_TWFreq[44][51] = 554;
+ EUC_TWFreq[38][72] = 553;
+ EUC_TWFreq[74][90] = 552;
+ EUC_TWFreq[35][48] = 551;
+ EUC_TWFreq[35][69] = 550;
+ EUC_TWFreq[66][86] = 549;
+ EUC_TWFreq[57][20] = 548;
+ EUC_TWFreq[35][53] = 547;
+ EUC_TWFreq[36][87] = 546;
+ EUC_TWFreq[84][67] = 545;
+ EUC_TWFreq[70][56] = 544;
+ EUC_TWFreq[71][54] = 543;
+ EUC_TWFreq[60][70] = 542;
+ EUC_TWFreq[80][1] = 541;
+ EUC_TWFreq[39][59] = 540;
+ EUC_TWFreq[39][51] = 539;
+ EUC_TWFreq[35][44] = 538;
+ EUC_TWFreq[48][4] = 537;
+ EUC_TWFreq[55][24] = 536;
+ EUC_TWFreq[52][4] = 535;
+ EUC_TWFreq[54][26] = 534;
+ EUC_TWFreq[36][31] = 533;
+ EUC_TWFreq[37][22] = 532;
+ EUC_TWFreq[37][9] = 531;
+ EUC_TWFreq[46][0] = 530;
+ EUC_TWFreq[56][46] = 529;
+ EUC_TWFreq[47][93] = 528;
+ EUC_TWFreq[37][25] = 527;
+ EUC_TWFreq[39][8] = 526;
+ EUC_TWFreq[46][73] = 525;
+ EUC_TWFreq[38][48] = 524;
+ EUC_TWFreq[39][83] = 523;
+ EUC_TWFreq[60][92] = 522;
+ EUC_TWFreq[70][11] = 521;
+ EUC_TWFreq[63][84] = 520;
+ EUC_TWFreq[38][65] = 519;
+ EUC_TWFreq[45][45] = 518;
+ EUC_TWFreq[63][49] = 517;
+ EUC_TWFreq[63][50] = 516;
+ EUC_TWFreq[39][93] = 515;
+ EUC_TWFreq[68][20] = 514;
+ EUC_TWFreq[44][84] = 513;
+ EUC_TWFreq[66][34] = 512;
+ EUC_TWFreq[37][58] = 511;
+ EUC_TWFreq[39][0] = 510;
+ EUC_TWFreq[59][1] = 509;
+ EUC_TWFreq[47][8] = 508;
+ EUC_TWFreq[61][17] = 507;
+ EUC_TWFreq[53][87] = 506;
+ EUC_TWFreq[67][26] = 505;
+ EUC_TWFreq[43][46] = 504;
+ EUC_TWFreq[38][61] = 503;
+ EUC_TWFreq[45][9] = 502;
+ EUC_TWFreq[66][83] = 501;
+ EUC_TWFreq[43][88] = 500;
+ EUC_TWFreq[85][20] = 499;
+ EUC_TWFreq[57][36] = 498;
+ EUC_TWFreq[43][6] = 497;
+ EUC_TWFreq[86][77] = 496;
+ EUC_TWFreq[42][70] = 495;
+ EUC_TWFreq[49][78] = 494;
+ EUC_TWFreq[36][40] = 493;
+ EUC_TWFreq[42][71] = 492;
+ EUC_TWFreq[58][49] = 491;
+ EUC_TWFreq[35][20] = 490;
+ EUC_TWFreq[76][20] = 489;
+ EUC_TWFreq[39][25] = 488;
+ EUC_TWFreq[40][34] = 487;
+ EUC_TWFreq[39][76] = 486;
+ EUC_TWFreq[40][1] = 485;
+ EUC_TWFreq[59][0] = 484;
+ EUC_TWFreq[39][70] = 483;
+ EUC_TWFreq[46][14] = 482;
+ EUC_TWFreq[68][77] = 481;
+ EUC_TWFreq[38][55] = 480;
+ EUC_TWFreq[35][78] = 479;
+ EUC_TWFreq[84][44] = 478;
+ EUC_TWFreq[36][41] = 477;
+ EUC_TWFreq[37][62] = 476;
+ EUC_TWFreq[65][67] = 475;
+ EUC_TWFreq[69][66] = 474;
+ EUC_TWFreq[73][55] = 473;
+ EUC_TWFreq[71][49] = 472;
+ EUC_TWFreq[66][87] = 471;
+ EUC_TWFreq[38][33] = 470;
+ EUC_TWFreq[64][61] = 469;
+ EUC_TWFreq[35][7] = 468;
+ EUC_TWFreq[47][49] = 467;
+ EUC_TWFreq[56][14] = 466;
+ EUC_TWFreq[36][49] = 465;
+ EUC_TWFreq[50][81] = 464;
+ EUC_TWFreq[55][76] = 463;
+ EUC_TWFreq[35][19] = 462;
+ EUC_TWFreq[44][47] = 461;
+ EUC_TWFreq[35][15] = 460;
+ EUC_TWFreq[82][59] = 459;
+ EUC_TWFreq[35][43] = 458;
+ EUC_TWFreq[73][0] = 457;
+ EUC_TWFreq[57][83] = 456;
+ EUC_TWFreq[42][46] = 455;
+ EUC_TWFreq[36][0] = 454;
+ EUC_TWFreq[70][88] = 453;
+ EUC_TWFreq[42][22] = 452;
+ EUC_TWFreq[46][58] = 451;
+ EUC_TWFreq[36][34] = 450;
+ EUC_TWFreq[39][24] = 449;
+ EUC_TWFreq[35][55] = 448;
+ EUC_TWFreq[44][91] = 447;
+ EUC_TWFreq[37][51] = 446;
+ EUC_TWFreq[36][19] = 445;
+ EUC_TWFreq[69][90] = 444;
+ EUC_TWFreq[55][35] = 443;
+ EUC_TWFreq[35][54] = 442;
+ EUC_TWFreq[49][61] = 441;
+ EUC_TWFreq[36][67] = 440;
+ EUC_TWFreq[88][34] = 439;
+ EUC_TWFreq[35][17] = 438;
+ EUC_TWFreq[65][69] = 437;
+ EUC_TWFreq[74][89] = 436;
+ EUC_TWFreq[37][31] = 435;
+ EUC_TWFreq[43][48] = 434;
+ EUC_TWFreq[89][27] = 433;
+ EUC_TWFreq[42][79] = 432;
+ EUC_TWFreq[69][57] = 431;
+ EUC_TWFreq[36][13] = 430;
+ EUC_TWFreq[35][62] = 429;
+ EUC_TWFreq[65][47] = 428;
+ EUC_TWFreq[56][8] = 427;
+ EUC_TWFreq[38][79] = 426;
+ EUC_TWFreq[37][64] = 425;
+ EUC_TWFreq[64][64] = 424;
+ EUC_TWFreq[38][53] = 423;
+ EUC_TWFreq[38][31] = 422;
+ EUC_TWFreq[56][81] = 421;
+ EUC_TWFreq[36][22] = 420;
+ EUC_TWFreq[43][4] = 419;
+ EUC_TWFreq[36][90] = 418;
+ EUC_TWFreq[38][62] = 417;
+ EUC_TWFreq[66][85] = 416;
+ EUC_TWFreq[39][1] = 415;
+ EUC_TWFreq[59][40] = 414;
+ EUC_TWFreq[58][93] = 413;
+ EUC_TWFreq[44][43] = 412;
+ EUC_TWFreq[39][49] = 411;
+ EUC_TWFreq[64][2] = 410;
+ EUC_TWFreq[41][35] = 409;
+ EUC_TWFreq[60][22] = 408;
+ EUC_TWFreq[35][91] = 407;
+ EUC_TWFreq[78][1] = 406;
+ EUC_TWFreq[36][14] = 405;
+ EUC_TWFreq[82][29] = 404;
+ EUC_TWFreq[52][86] = 403;
+ EUC_TWFreq[40][16] = 402;
+ EUC_TWFreq[91][52] = 401;
+ EUC_TWFreq[50][75] = 400;
+ EUC_TWFreq[64][30] = 399;
+ EUC_TWFreq[90][78] = 398;
+ EUC_TWFreq[36][52] = 397;
+ EUC_TWFreq[55][87] = 396;
+ EUC_TWFreq[57][5] = 395;
+ EUC_TWFreq[57][31] = 394;
+ EUC_TWFreq[42][35] = 393;
+ EUC_TWFreq[69][50] = 392;
+ EUC_TWFreq[45][8] = 391;
+ EUC_TWFreq[50][87] = 390;
+ EUC_TWFreq[69][55] = 389;
+ EUC_TWFreq[92][3] = 388;
+ EUC_TWFreq[36][43] = 387;
+ EUC_TWFreq[64][10] = 386;
+ EUC_TWFreq[56][25] = 385;
+ EUC_TWFreq[60][68] = 384;
+ EUC_TWFreq[51][46] = 383;
+ EUC_TWFreq[50][0] = 382;
+ EUC_TWFreq[38][30] = 381;
+ EUC_TWFreq[50][85] = 380;
+ EUC_TWFreq[60][54] = 379;
+ EUC_TWFreq[73][6] = 378;
+ EUC_TWFreq[73][28] = 377;
+ EUC_TWFreq[56][19] = 376;
+ EUC_TWFreq[62][69] = 375;
+ EUC_TWFreq[81][66] = 374;
+ EUC_TWFreq[40][32] = 373;
+ EUC_TWFreq[76][31] = 372;
+ EUC_TWFreq[35][10] = 371;
+ EUC_TWFreq[41][37] = 370;
+ EUC_TWFreq[52][82] = 369;
+ EUC_TWFreq[91][72] = 368;
+ EUC_TWFreq[37][29] = 367;
+ EUC_TWFreq[56][30] = 366;
+ EUC_TWFreq[37][80] = 365;
+ EUC_TWFreq[81][56] = 364;
+ EUC_TWFreq[70][3] = 363;
+ EUC_TWFreq[76][15] = 362;
+ EUC_TWFreq[46][47] = 361;
+ EUC_TWFreq[35][88] = 360;
+ EUC_TWFreq[61][58] = 359;
+ EUC_TWFreq[37][37] = 358;
+ EUC_TWFreq[57][22] = 357;
+ EUC_TWFreq[41][23] = 356;
+ EUC_TWFreq[90][66] = 355;
+ EUC_TWFreq[39][60] = 354;
+ EUC_TWFreq[38][0] = 353;
+ EUC_TWFreq[37][87] = 352;
+ EUC_TWFreq[46][2] = 351;
+ EUC_TWFreq[38][56] = 350;
+ EUC_TWFreq[58][11] = 349;
+ EUC_TWFreq[48][10] = 348;
+ EUC_TWFreq[74][4] = 347;
+ EUC_TWFreq[40][42] = 346;
+ EUC_TWFreq[41][52] = 345;
+ EUC_TWFreq[61][92] = 344;
+ EUC_TWFreq[39][50] = 343;
+ EUC_TWFreq[47][88] = 342;
+ EUC_TWFreq[88][36] = 341;
+ EUC_TWFreq[45][73] = 340;
+ EUC_TWFreq[82][3] = 339;
+ EUC_TWFreq[61][36] = 338;
+ EUC_TWFreq[60][33] = 337;
+ EUC_TWFreq[38][27] = 336;
+ EUC_TWFreq[35][83] = 335;
+ EUC_TWFreq[65][24] = 334;
+ EUC_TWFreq[73][10] = 333;
+ EUC_TWFreq[41][13] = 332;
+ EUC_TWFreq[50][27] = 331;
+ EUC_TWFreq[59][50] = 330;
+ EUC_TWFreq[42][45] = 329;
+ EUC_TWFreq[55][19] = 328;
+ EUC_TWFreq[36][77] = 327;
+ EUC_TWFreq[69][31] = 326;
+ EUC_TWFreq[60][7] = 325;
+ EUC_TWFreq[40][88] = 324;
+ EUC_TWFreq[57][56] = 323;
+ EUC_TWFreq[50][50] = 322;
+ EUC_TWFreq[42][37] = 321;
+ EUC_TWFreq[38][82] = 320;
+ EUC_TWFreq[52][25] = 319;
+ EUC_TWFreq[42][67] = 318;
+ EUC_TWFreq[48][40] = 317;
+ EUC_TWFreq[45][81] = 316;
+ EUC_TWFreq[57][14] = 315;
+ EUC_TWFreq[42][13] = 314;
+ EUC_TWFreq[78][0] = 313;
+ EUC_TWFreq[35][51] = 312;
+ EUC_TWFreq[41][67] = 311;
+ EUC_TWFreq[64][23] = 310;
+ EUC_TWFreq[36][65] = 309;
+ EUC_TWFreq[48][50] = 308;
+ EUC_TWFreq[46][69] = 307;
+ EUC_TWFreq[47][89] = 306;
+ EUC_TWFreq[41][48] = 305;
+ EUC_TWFreq[60][56] = 304;
+ EUC_TWFreq[44][82] = 303;
+ EUC_TWFreq[47][35] = 302;
+ EUC_TWFreq[49][3] = 301;
+ EUC_TWFreq[49][69] = 300;
+ EUC_TWFreq[45][93] = 299;
+ EUC_TWFreq[60][34] = 298;
+ EUC_TWFreq[60][82] = 297;
+ EUC_TWFreq[61][61] = 296;
+ EUC_TWFreq[86][42] = 295;
+ EUC_TWFreq[89][60] = 294;
+ EUC_TWFreq[48][31] = 293;
+ EUC_TWFreq[35][75] = 292;
+ EUC_TWFreq[91][39] = 291;
+ EUC_TWFreq[53][19] = 290;
+ EUC_TWFreq[39][72] = 289;
+ EUC_TWFreq[69][59] = 288;
+ EUC_TWFreq[41][7] = 287;
+ EUC_TWFreq[54][13] = 286;
+ EUC_TWFreq[43][28] = 285;
+ EUC_TWFreq[36][6] = 284;
+ EUC_TWFreq[45][75] = 283;
+ EUC_TWFreq[36][61] = 282;
+ EUC_TWFreq[38][21] = 281;
+ EUC_TWFreq[45][14] = 280;
+ EUC_TWFreq[61][43] = 279;
+ EUC_TWFreq[36][63] = 278;
+ EUC_TWFreq[43][30] = 277;
+ EUC_TWFreq[46][51] = 276;
+ EUC_TWFreq[68][87] = 275;
+ EUC_TWFreq[39][26] = 274;
+ EUC_TWFreq[46][76] = 273;
+ EUC_TWFreq[36][15] = 272;
+ EUC_TWFreq[35][40] = 271;
+ EUC_TWFreq[79][60] = 270;
+ EUC_TWFreq[46][7] = 269;
+ EUC_TWFreq[65][72] = 268;
+ EUC_TWFreq[69][88] = 267;
+ EUC_TWFreq[47][18] = 266;
+ EUC_TWFreq[37][0] = 265;
+ EUC_TWFreq[37][49] = 264;
+ EUC_TWFreq[67][37] = 263;
+ EUC_TWFreq[36][91] = 262;
+ EUC_TWFreq[75][48] = 261;
+ EUC_TWFreq[75][63] = 260;
+ EUC_TWFreq[83][87] = 259;
+ EUC_TWFreq[37][44] = 258;
+ EUC_TWFreq[73][54] = 257;
+ EUC_TWFreq[51][61] = 256;
+ EUC_TWFreq[46][57] = 255;
+ EUC_TWFreq[55][21] = 254;
+ EUC_TWFreq[39][66] = 253;
+ EUC_TWFreq[47][11] = 252;
+ EUC_TWFreq[52][8] = 251;
+ EUC_TWFreq[82][81] = 250;
+ EUC_TWFreq[36][57] = 249;
+ EUC_TWFreq[38][54] = 248;
+ EUC_TWFreq[43][81] = 247;
+ EUC_TWFreq[37][42] = 246;
+ EUC_TWFreq[40][18] = 245;
+ EUC_TWFreq[80][90] = 244;
+ EUC_TWFreq[37][84] = 243;
+ EUC_TWFreq[57][15] = 242;
+ EUC_TWFreq[38][87] = 241;
+ EUC_TWFreq[37][32] = 240;
+ EUC_TWFreq[53][53] = 239;
+ EUC_TWFreq[89][29] = 238;
+ EUC_TWFreq[81][53] = 237;
+ EUC_TWFreq[75][3] = 236;
+ EUC_TWFreq[83][73] = 235;
+ EUC_TWFreq[66][13] = 234;
+ EUC_TWFreq[48][7] = 233;
+ EUC_TWFreq[46][35] = 232;
+ EUC_TWFreq[35][86] = 231;
+ EUC_TWFreq[37][20] = 230;
+ EUC_TWFreq[46][80] = 229;
+ EUC_TWFreq[38][24] = 228;
+ EUC_TWFreq[41][68] = 227;
+ EUC_TWFreq[42][21] = 226;
+ EUC_TWFreq[43][32] = 225;
+ EUC_TWFreq[38][20] = 224;
+ EUC_TWFreq[37][59] = 223;
+ EUC_TWFreq[41][77] = 222;
+ EUC_TWFreq[59][57] = 221;
+ EUC_TWFreq[68][59] = 220;
+ EUC_TWFreq[39][43] = 219;
+ EUC_TWFreq[54][39] = 218;
+ EUC_TWFreq[48][28] = 217;
+ EUC_TWFreq[54][28] = 216;
+ EUC_TWFreq[41][44] = 215;
+ EUC_TWFreq[51][64] = 214;
+ EUC_TWFreq[47][72] = 213;
+ EUC_TWFreq[62][67] = 212;
+ EUC_TWFreq[42][43] = 211;
+ EUC_TWFreq[61][38] = 210;
+ EUC_TWFreq[76][25] = 209;
+ EUC_TWFreq[48][91] = 208;
+ EUC_TWFreq[36][36] = 207;
+ EUC_TWFreq[80][32] = 206;
+ EUC_TWFreq[81][40] = 205;
+ EUC_TWFreq[37][5] = 204;
+ EUC_TWFreq[74][69] = 203;
+ EUC_TWFreq[36][82] = 202;
+ EUC_TWFreq[46][59] = 201;
+
+ GBKFreq[52][132] = 600;
+ GBKFreq[73][135] = 599;
+ GBKFreq[49][123] = 598;
+ GBKFreq[77][146] = 597;
+ GBKFreq[81][123] = 596;
+ GBKFreq[82][144] = 595;
+ GBKFreq[51][179] = 594;
+ GBKFreq[83][154] = 593;
+ GBKFreq[71][139] = 592;
+ GBKFreq[64][139] = 591;
+ GBKFreq[85][144] = 590;
+ GBKFreq[52][125] = 589;
+ GBKFreq[88][25] = 588;
+ GBKFreq[81][106] = 587;
+ GBKFreq[81][148] = 586;
+ GBKFreq[62][137] = 585;
+ GBKFreq[94][0] = 584;
+ GBKFreq[1][64] = 583;
+ GBKFreq[67][163] = 582;
+ GBKFreq[20][190] = 581;
+ GBKFreq[57][131] = 580;
+ GBKFreq[29][169] = 579;
+ GBKFreq[72][143] = 578;
+ GBKFreq[0][173] = 577;
+ GBKFreq[11][23] = 576;
+ GBKFreq[61][141] = 575;
+ GBKFreq[60][123] = 574;
+ GBKFreq[81][114] = 573;
+ GBKFreq[82][131] = 572;
+ GBKFreq[67][156] = 571;
+ GBKFreq[71][167] = 570;
+ GBKFreq[20][50] = 569;
+ GBKFreq[77][132] = 568;
+ GBKFreq[84][38] = 567;
+ GBKFreq[26][29] = 566;
+ GBKFreq[74][187] = 565;
+ GBKFreq[62][116] = 564;
+ GBKFreq[67][135] = 563;
+ GBKFreq[5][86] = 562;
+ GBKFreq[72][186] = 561;
+ GBKFreq[75][161] = 560;
+ GBKFreq[78][130] = 559;
+ GBKFreq[94][30] = 558;
+ GBKFreq[84][72] = 557;
+ GBKFreq[1][67] = 556;
+ GBKFreq[75][172] = 555;
+ GBKFreq[74][185] = 554;
+ GBKFreq[53][160] = 553;
+ GBKFreq[123][14] = 552;
+ GBKFreq[79][97] = 551;
+ GBKFreq[85][110] = 550;
+ GBKFreq[78][171] = 549;
+ GBKFreq[52][131] = 548;
+ GBKFreq[56][100] = 547;
+ GBKFreq[50][182] = 546;
+ GBKFreq[94][64] = 545;
+ GBKFreq[106][74] = 544;
+ GBKFreq[11][102] = 543;
+ GBKFreq[53][124] = 542;
+ GBKFreq[24][3] = 541;
+ GBKFreq[86][148] = 540;
+ GBKFreq[53][184] = 539;
+ GBKFreq[86][147] = 538;
+ GBKFreq[96][161] = 537;
+ GBKFreq[82][77] = 536;
+ GBKFreq[59][146] = 535;
+ GBKFreq[84][126] = 534;
+ GBKFreq[79][132] = 533;
+ GBKFreq[85][123] = 532;
+ GBKFreq[71][101] = 531;
+ GBKFreq[85][106] = 530;
+ GBKFreq[6][184] = 529;
+ GBKFreq[57][156] = 528;
+ GBKFreq[75][104] = 527;
+ GBKFreq[50][137] = 526;
+ GBKFreq[79][133] = 525;
+ GBKFreq[76][108] = 524;
+ GBKFreq[57][142] = 523;
+ GBKFreq[84][130] = 522;
+ GBKFreq[52][128] = 521;
+ GBKFreq[47][44] = 520;
+ GBKFreq[52][152] = 519;
+ GBKFreq[54][104] = 518;
+ GBKFreq[30][47] = 517;
+ GBKFreq[71][123] = 516;
+ GBKFreq[52][107] = 515;
+ GBKFreq[45][84] = 514;
+ GBKFreq[107][118] = 513;
+ GBKFreq[5][161] = 512;
+ GBKFreq[48][126] = 511;
+ GBKFreq[67][170] = 510;
+ GBKFreq[43][6] = 509;
+ GBKFreq[70][112] = 508;
+ GBKFreq[86][174] = 507;
+ GBKFreq[84][166] = 506;
+ GBKFreq[79][130] = 505;
+ GBKFreq[57][141] = 504;
+ GBKFreq[81][178] = 503;
+ GBKFreq[56][187] = 502;
+ GBKFreq[81][162] = 501;
+ GBKFreq[53][104] = 500;
+ GBKFreq[123][35] = 499;
+ GBKFreq[70][169] = 498;
+ GBKFreq[69][164] = 497;
+ GBKFreq[109][61] = 496;
+ GBKFreq[73][130] = 495;
+ GBKFreq[62][134] = 494;
+ GBKFreq[54][125] = 493;
+ GBKFreq[79][105] = 492;
+ GBKFreq[70][165] = 491;
+ GBKFreq[71][189] = 490;
+ GBKFreq[23][147] = 489;
+ GBKFreq[51][139] = 488;
+ GBKFreq[47][137] = 487;
+ GBKFreq[77][123] = 486;
+ GBKFreq[86][183] = 485;
+ GBKFreq[63][173] = 484;
+ GBKFreq[79][144] = 483;
+ GBKFreq[84][159] = 482;
+ GBKFreq[60][91] = 481;
+ GBKFreq[66][187] = 480;
+ GBKFreq[73][114] = 479;
+ GBKFreq[85][56] = 478;
+ GBKFreq[71][149] = 477;
+ GBKFreq[84][189] = 476;
+ GBKFreq[104][31] = 475;
+ GBKFreq[83][82] = 474;
+ GBKFreq[68][35] = 473;
+ GBKFreq[11][77] = 472;
+ GBKFreq[15][155] = 471;
+ GBKFreq[83][153] = 470;
+ GBKFreq[71][1] = 469;
+ GBKFreq[53][190] = 468;
+ GBKFreq[50][135] = 467;
+ GBKFreq[3][147] = 466;
+ GBKFreq[48][136] = 465;
+ GBKFreq[66][166] = 464;
+ GBKFreq[55][159] = 463;
+ GBKFreq[82][150] = 462;
+ GBKFreq[58][178] = 461;
+ GBKFreq[64][102] = 460;
+ GBKFreq[16][106] = 459;
+ GBKFreq[68][110] = 458;
+ GBKFreq[54][14] = 457;
+ GBKFreq[60][140] = 456;
+ GBKFreq[91][71] = 455;
+ GBKFreq[54][150] = 454;
+ GBKFreq[78][177] = 453;
+ GBKFreq[78][117] = 452;
+ GBKFreq[104][12] = 451;
+ GBKFreq[73][150] = 450;
+ GBKFreq[51][142] = 449;
+ GBKFreq[81][145] = 448;
+ GBKFreq[66][183] = 447;
+ GBKFreq[51][178] = 446;
+ GBKFreq[75][107] = 445;
+ GBKFreq[65][119] = 444;
+ GBKFreq[69][176] = 443;
+ GBKFreq[59][122] = 442;
+ GBKFreq[78][160] = 441;
+ GBKFreq[85][183] = 440;
+ GBKFreq[105][16] = 439;
+ GBKFreq[73][110] = 438;
+ GBKFreq[104][39] = 437;
+ GBKFreq[119][16] = 436;
+ GBKFreq[76][162] = 435;
+ GBKFreq[67][152] = 434;
+ GBKFreq[82][24] = 433;
+ GBKFreq[73][121] = 432;
+ GBKFreq[83][83] = 431;
+ GBKFreq[82][145] = 430;
+ GBKFreq[49][133] = 429;
+ GBKFreq[94][13] = 428;
+ GBKFreq[58][139] = 427;
+ GBKFreq[74][189] = 426;
+ GBKFreq[66][177] = 425;
+ GBKFreq[85][184] = 424;
+ GBKFreq[55][183] = 423;
+ GBKFreq[71][107] = 422;
+ GBKFreq[11][98] = 421;
+ GBKFreq[72][153] = 420;
+ GBKFreq[2][137] = 419;
+ GBKFreq[59][147] = 418;
+ GBKFreq[58][152] = 417;
+ GBKFreq[55][144] = 416;
+ GBKFreq[73][125] = 415;
+ GBKFreq[52][154] = 414;
+ GBKFreq[70][178] = 413;
+ GBKFreq[79][148] = 412;
+ GBKFreq[63][143] = 411;
+ GBKFreq[50][140] = 410;
+ GBKFreq[47][145] = 409;
+ GBKFreq[48][123] = 408;
+ GBKFreq[56][107] = 407;
+ GBKFreq[84][83] = 406;
+ GBKFreq[59][112] = 405;
+ GBKFreq[124][72] = 404;
+ GBKFreq[79][99] = 403;
+ GBKFreq[3][37] = 402;
+ GBKFreq[114][55] = 401;
+ GBKFreq[85][152] = 400;
+ GBKFreq[60][47] = 399;
+ GBKFreq[65][96] = 398;
+ GBKFreq[74][110] = 397;
+ GBKFreq[86][182] = 396;
+ GBKFreq[50][99] = 395;
+ GBKFreq[67][186] = 394;
+ GBKFreq[81][74] = 393;
+ GBKFreq[80][37] = 392;
+ GBKFreq[21][60] = 391;
+ GBKFreq[110][12] = 390;
+ GBKFreq[60][162] = 389;
+ GBKFreq[29][115] = 388;
+ GBKFreq[83][130] = 387;
+ GBKFreq[52][136] = 386;
+ GBKFreq[63][114] = 385;
+ GBKFreq[49][127] = 384;
+ GBKFreq[83][109] = 383;
+ GBKFreq[66][128] = 382;
+ GBKFreq[78][136] = 381;
+ GBKFreq[81][180] = 380;
+ GBKFreq[76][104] = 379;
+ GBKFreq[56][156] = 378;
+ GBKFreq[61][23] = 377;
+ GBKFreq[4][30] = 376;
+ GBKFreq[69][154] = 375;
+ GBKFreq[100][37] = 374;
+ GBKFreq[54][177] = 373;
+ GBKFreq[23][119] = 372;
+ GBKFreq[71][171] = 371;
+ GBKFreq[84][146] = 370;
+ GBKFreq[20][184] = 369;
+ GBKFreq[86][76] = 368;
+ GBKFreq[74][132] = 367;
+ GBKFreq[47][97] = 366;
+ GBKFreq[82][137] = 365;
+ GBKFreq[94][56] = 364;
+ GBKFreq[92][30] = 363;
+ GBKFreq[19][117] = 362;
+ GBKFreq[48][173] = 361;
+ GBKFreq[2][136] = 360;
+ GBKFreq[7][182] = 359;
+ GBKFreq[74][188] = 358;
+ GBKFreq[14][132] = 357;
+ GBKFreq[62][172] = 356;
+ GBKFreq[25][39] = 355;
+ GBKFreq[85][129] = 354;
+ GBKFreq[64][98] = 353;
+ GBKFreq[67][127] = 352;
+ GBKFreq[72][167] = 351;
+ GBKFreq[57][143] = 350;
+ GBKFreq[76][187] = 349;
+ GBKFreq[83][181] = 348;
+ GBKFreq[84][10] = 347;
+ GBKFreq[55][166] = 346;
+ GBKFreq[55][188] = 345;
+ GBKFreq[13][151] = 344;
+ GBKFreq[62][124] = 343;
+ GBKFreq[53][136] = 342;
+ GBKFreq[106][57] = 341;
+ GBKFreq[47][166] = 340;
+ GBKFreq[109][30] = 339;
+ GBKFreq[78][114] = 338;
+ GBKFreq[83][19] = 337;
+ GBKFreq[56][162] = 336;
+ GBKFreq[60][177] = 335;
+ GBKFreq[88][9] = 334;
+ GBKFreq[74][163] = 333;
+ GBKFreq[52][156] = 332;
+ GBKFreq[71][180] = 331;
+ GBKFreq[60][57] = 330;
+ GBKFreq[72][173] = 329;
+ GBKFreq[82][91] = 328;
+ GBKFreq[51][186] = 327;
+ GBKFreq[75][86] = 326;
+ GBKFreq[75][78] = 325;
+ GBKFreq[76][170] = 324;
+ GBKFreq[60][147] = 323;
+ GBKFreq[82][75] = 322;
+ GBKFreq[80][148] = 321;
+ GBKFreq[86][150] = 320;
+ GBKFreq[13][95] = 319;
+ GBKFreq[0][11] = 318;
+ GBKFreq[84][190] = 317;
+ GBKFreq[76][166] = 316;
+ GBKFreq[14][72] = 315;
+ GBKFreq[67][144] = 314;
+ GBKFreq[84][44] = 313;
+ GBKFreq[72][125] = 312;
+ GBKFreq[66][127] = 311;
+ GBKFreq[60][25] = 310;
+ GBKFreq[70][146] = 309;
+ GBKFreq[79][135] = 308;
+ GBKFreq[54][135] = 307;
+ GBKFreq[60][104] = 306;
+ GBKFreq[55][132] = 305;
+ GBKFreq[94][2] = 304;
+ GBKFreq[54][133] = 303;
+ GBKFreq[56][190] = 302;
+ GBKFreq[58][174] = 301;
+ GBKFreq[80][144] = 300;
+ GBKFreq[85][113] = 299;
+ }
+
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/encoding/FallbackZipEncoding.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/encoding/FallbackZipEncoding.java
new file mode 100644
index 0000000..bf8fabd
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/encoding/FallbackZipEncoding.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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.
+ */
+
+package top.niunaijun.blackobfuscator.core.utils.zip.encoding;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * A fallback ZipEncoding, which uses a java.io means to encode names.
+ *
+ *
This implementation is not favorable for encodings other than
+ * utf-8, because java.io encodes unmappable character as question
+ * marks leading to unreadable ZIP entries on some operating
+ * systems.
+ *
+ *
Furthermore this implementation is unable to tell, whether a
+ * given name can be safely encoded or not.
+ *
+ *
The methods of this class are reentrant.
+ */
+@SuppressWarnings("ConstantConditions")
+class FallbackZipEncoding implements ZipEncoding {
+ private final String charset;
+ private final DetectEncoding de;
+
+ /**
+ * Construct a fallback zip encoding, which uses the platform's
+ * default charset.
+ */
+ public FallbackZipEncoding() {
+ this.charset = null;
+ de = new DetectEncoding();
+ }
+
+ /**
+ * Construct a fallback zip encoding, which uses the given charset.
+ *
+ * @param charset The name of the charset or {@code null} for
+ * the platform's default character set.
+ */
+ public FallbackZipEncoding(final String charset) {
+ this.charset = charset;
+ de = charset != null ? null : new DetectEncoding();
+ }
+
+ public boolean canEncode(final String name) {
+ return true;
+ }
+
+ public ByteBuffer encode(final String name) throws IOException {
+ if (this.charset == null) { // i.e. use default charset, see no-args constructor
+ return ByteBuffer.wrap(name.getBytes(de.getEncode()));
+ } else {
+ return ByteBuffer.wrap(name.getBytes(this.charset));
+ }
+ }
+
+ public String decode(final byte[] data) throws IOException {
+ if (this.charset == null) {
+ de.update(data);
+ return new String(data, de.getEncode());
+ } else {
+ return new String(data, this.charset);
+ }
+ }
+
+ @Override
+ public String getEncoding() {
+ if (this.charset == null)
+ return de.getEncode().name();
+ else
+ return this.charset;
+ }
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/encoding/ZipEncoding.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/encoding/ZipEncoding.java
new file mode 100644
index 0000000..0a935ea
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/encoding/ZipEncoding.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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.
+ */
+
+package top.niunaijun.blackobfuscator.core.utils.zip.encoding;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * An interface for encoders that do a pretty encoding of ZIP
+ * filenames.
+ *
+ *
There are mostly two implementations, one that uses java.nio
+ * {@link java.nio.charset.Charset Charset} and one implementation,
+ * which copes with simple 8 bit charsets, because java-1.4 did not
+ * support Cp437 in java.nio.
+ *
+ *
The main reason for defining an own encoding layer comes from
+ * the problems with {@link String#getBytes(String)
+ * String.getBytes}, which encodes unknown characters as ASCII
+ * quotation marks ('?'). Quotation marks are per definition an
+ * invalid filename on some operating systems like Windows, which
+ * leads to ignored ZIP entries.
+ *
+ *
All implementations should implement this interface in a
+ * reentrant way.
+ */
+public interface ZipEncoding {
+
+ /**
+ * Check, whether the given string may be losslessly encoded using this
+ * encoding.
+ *
+ * @param name A filename or ZIP comment.
+ * @return Whether the given name may be encoded with out any losses.
+ */
+ boolean canEncode(String name);
+
+ /**
+ * Encode a filename or a comment to a byte array suitable for
+ * storing it to a serialized zip entry.
+ *
+ *
Examples for CP 437 (in pseudo-notation, right hand side is
+ * C-style notation):
+ *
+ * encode("\u20AC_for_Dollar.txt") = "%U20AC_for_Dollar.txt"
+ * encode("\u00D6lf\u00E4sser.txt") = "\231lf\204sser.txt"
+ *
+ *
+ * @param name A filename or ZIP comment.
+ * @return A byte buffer with a backing array containing the
+ * encoded name. Unmappable characters or malformed
+ * character sequences are mapped to a sequence of utf-16
+ * words encoded in the format %Uxxxx
. It is
+ * assumed, that the byte buffer is positioned at the
+ * beinning of the encoded result, the byte buffer has a
+ * backing array and the limit of the byte buffer points
+ * to the end of the encoded result.
+ * @throws IOException
+ */
+ ByteBuffer encode(String name) throws IOException;
+
+ /**
+ * @param data The byte values to decode.
+ * @return The decoded string.
+ * @throws IOException
+ */
+ String decode(byte[] data) throws IOException;
+
+ String getEncoding();
+
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/encoding/ZipEncodingHelper.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/encoding/ZipEncodingHelper.java
new file mode 100644
index 0000000..50a5cee
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/encoding/ZipEncodingHelper.java
@@ -0,0 +1,32 @@
+package top.niunaijun.blackobfuscator.core.utils.zip.encoding;
+
+public class ZipEncodingHelper {
+ public static final ZipEncoding UTF8_ZIP_ENCODING = new FallbackZipEncoding("UTF-8");
+
+ /**
+ * name of the encoding UTF-8
+ */
+ static final String UTF8 = "UTF8";
+
+ /**
+ * variant name of the encoding UTF-8 used for comparisons.
+ */
+ private static final String UTF_DASH_8 = "utf-8";
+
+ /**
+ * Whether a given encoding - or the platform's default encoding
+ * if the parameter is null - is UTF-8.
+ */
+ public static boolean isUTF8(String encoding) {
+ if (encoding == null) {
+ // check platform's default encoding
+ encoding = System.getProperty("file.encoding");
+ }
+ return UTF8.equalsIgnoreCase(encoding)
+ || UTF_DASH_8.equalsIgnoreCase(encoding);
+ }
+
+ public static ZipEncoding getZipEncoding(String encoding) {
+ return new FallbackZipEncoding(encoding);
+ }
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/AbstractUnicodeExtraField.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/AbstractUnicodeExtraField.java
new file mode 100644
index 0000000..b787a64
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/AbstractUnicodeExtraField.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.
+ *
+ */
+
+package top.niunaijun.blackobfuscator.core.utils.zip.extrafield;
+
+import top.niunaijun.blackobfuscator.core.utils.zip.ZipLong;
+import top.niunaijun.blackobfuscator.core.utils.zip.ZipShort;
+
+import java.io.UnsupportedEncodingException;
+import java.util.zip.CRC32;
+import java.util.zip.ZipException;
+
+/**
+ * A common base class for Unicode extra information extra fields.
+ */
+public abstract class AbstractUnicodeExtraField implements ZipExtraField {
+ private long nameCRC32;
+ private byte[] unicodeName;
+ private byte[] data;
+
+ protected AbstractUnicodeExtraField() {
+ }
+
+ /**
+ * Assemble as unicode extension from the name/comment and
+ * encoding of the orginal zip entry.
+ *
+ * @param text The file name or comment.
+ * @param bytes The encoded of the filename or comment in the zip
+ * file.
+ * @param off The offset of the encoded filename or comment in
+ * bytes
.
+ * @param len The length of the encoded filename or commentin
+ * bytes
.
+ */
+ protected AbstractUnicodeExtraField(String text, byte[] bytes, int off,
+ int len) {
+ CRC32 crc32 = new CRC32();
+ crc32.update(bytes, off, len);
+ nameCRC32 = crc32.getValue();
+
+ try {
+ unicodeName = text.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("FATAL: UTF-8 encoding not supported.",
+ e);
+ }
+ }
+
+ /**
+ * Assemble as unicode extension from the name/comment and
+ * encoding of the orginal zip entry.
+ *
+ * @param text The file name or comment.
+ * @param bytes The encoded of the filename or comment in the zip
+ * file.
+ */
+ protected AbstractUnicodeExtraField(String text, byte[] bytes) {
+
+ this(text, bytes, 0, bytes.length);
+ }
+
+ private void assembleData() {
+ if (unicodeName == null) {
+ return;
+ }
+
+ data = new byte[5 + unicodeName.length];
+ // version 1
+ data[0] = 0x01;
+ System.arraycopy(ZipLong.getBytes(nameCRC32), 0, data, 1, 4);
+ System.arraycopy(unicodeName, 0, data, 5, unicodeName.length);
+ }
+
+ /**
+ * @return The CRC32 checksum of the filename or comment as
+ * encoded in the central directory of the zip file.
+ */
+ public long getNameCRC32() {
+ return nameCRC32;
+ }
+
+ /**
+ * @param nameCRC32 The CRC32 checksum of the filename as encoded
+ * in the central directory of the zip file to set.
+ */
+ public void setNameCRC32(long nameCRC32) {
+ this.nameCRC32 = nameCRC32;
+ data = null;
+ }
+
+ /**
+ * @return The utf-8 encoded name.
+ */
+ public byte[] getUnicodeName() {
+ return unicodeName;
+ }
+
+ /**
+ * @param unicodeName The utf-8 encoded name to set.
+ */
+ public void setUnicodeName(byte[] unicodeName) {
+ this.unicodeName = unicodeName;
+ data = null;
+ }
+
+ public byte[] getCentralDirectoryData() {
+ if (data == null) {
+ this.assembleData();
+ }
+ return data;
+ }
+
+ public ZipShort getCentralDirectoryLength() {
+ if (data == null) {
+ assembleData();
+ }
+ return new ZipShort(data.length);
+ }
+
+ public byte[] getLocalFileDataData() {
+ return getCentralDirectoryData();
+ }
+
+ public ZipShort getLocalFileDataLength() {
+ return getCentralDirectoryLength();
+ }
+
+ public void parseFromLocalFileData(byte[] buffer, int offset, int length)
+ throws ZipException {
+
+ if (length < 5) {
+ throw new ZipException("UniCode path extra data must have at least"
+ + " 5 bytes.");
+ }
+
+ int version = buffer[offset];
+
+ if (version != 0x01) {
+ throw new ZipException("Unsupported version [" + version
+ + "] for UniCode path extra data.");
+ }
+
+ nameCRC32 = ZipLong.getValue(buffer, offset + 1);
+ unicodeName = new byte[length - 5];
+ System.arraycopy(buffer, offset + 5, unicodeName, 0, length - 5);
+ data = null;
+ }
+
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/AsiExtraField.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/AsiExtraField.java
new file mode 100644
index 0000000..5b57661
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/AsiExtraField.java
@@ -0,0 +1,367 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.
+ *
+ */
+
+package top.niunaijun.blackobfuscator.core.utils.zip.extrafield;
+
+import top.niunaijun.blackobfuscator.core.utils.zip.UnixStat;
+import top.niunaijun.blackobfuscator.core.utils.zip.ZipLong;
+import top.niunaijun.blackobfuscator.core.utils.zip.ZipShort;
+
+import java.util.zip.CRC32;
+import java.util.zip.ZipException;
+
+
+/**
+ * Adds Unix file permission and UID/GID fields as well as symbolic
+ * link handling.
+ *
+ *
This class uses the ASi extra field in the format:
+ *
+ * Value Size Description
+ * ----- ---- -----------
+ * (Unix3) 0x756e Short tag for this extra block type
+ * TSize Short total data size for this block
+ * CRC Long CRC-32 of the remaining data
+ * Mode Short file permissions
+ * SizDev Long symlink'd size OR major/minor dev num
+ * UID Short user ID
+ * GID Short group ID
+ * (var.) variable symbolic link filename
+ *
+ * taken from appnote.iz (Info-ZIP note, 981119) found at ftp://ftp.uu.net/pub/archiving/zip/doc/
+ *
+ *
+ *
Short is two bytes and Long is four bytes in big endian byte and
+ * word order, device numbers are currently not supported.
+ */
+public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable {
+
+ private static final ZipShort HEADER_ID = new ZipShort(0x756E);
+ private static final int WORD = 4;
+ /**
+ * Standard Unix stat(2) file mode.
+ *
+ * @since 1.1
+ */
+ private int mode = 0;
+ /**
+ * User ID.
+ *
+ * @since 1.1
+ */
+ private int uid = 0;
+ /**
+ * Group ID.
+ *
+ * @since 1.1
+ */
+ private int gid = 0;
+ /**
+ * File this entry points to, if it is a symbolic link.
+ *
+ *
empty string - if entry is not a symbolic link.
+ *
+ * @since 1.1
+ */
+ private String link = "";
+ /**
+ * Is this an entry for a directory?
+ *
+ * @since 1.1
+ */
+ private boolean dirFlag = false;
+
+ /**
+ * Instance used to calculate checksums.
+ *
+ * @since 1.1
+ */
+ private CRC32 crc = new CRC32();
+
+ /**
+ * Constructor for AsiExtraField.
+ */
+ public AsiExtraField() {
+ }
+
+ /**
+ * The Header-ID.
+ *
+ * @return the value for the header id for this extrafield
+ * @since 1.1
+ */
+ public ZipShort getHeaderId() {
+ return HEADER_ID;
+ }
+
+ /**
+ * Length of the extra field in the local file data - without
+ * Header-ID or length specifier.
+ *
+ * @return a ZipShort
for the length of the data of this extra field
+ * @since 1.1
+ */
+ public ZipShort getLocalFileDataLength() {
+ return new ZipShort(WORD // CRC
+ + 2 // Mode
+ + WORD // SizDev
+ + 2 // UID
+ + 2 // GID
+ + getLinkedFile().getBytes().length);
+ }
+
+ /**
+ * Delegate to local file data.
+ *
+ * @return the centralDirectory length
+ * @since 1.1
+ */
+ public ZipShort getCentralDirectoryLength() {
+ return getLocalFileDataLength();
+ }
+
+ /**
+ * The actual data to put into local file data - without Header-ID
+ * or length specifier.
+ *
+ * @return get the data
+ * @since 1.1
+ */
+ public byte[] getLocalFileDataData() {
+ // CRC will be added later
+ byte[] data = new byte[getLocalFileDataLength().getValue() - WORD];
+ System.arraycopy(ZipShort.getBytes(getMode()), 0, data, 0, 2);
+
+ byte[] linkArray = getLinkedFile().getBytes();
+ // CheckStyle:MagicNumber OFF
+ System.arraycopy(ZipLong.getBytes(linkArray.length),
+ 0, data, 2, WORD);
+
+ System.arraycopy(ZipShort.getBytes(getUserId()),
+ 0, data, 6, 2);
+ System.arraycopy(ZipShort.getBytes(getGroupId()),
+ 0, data, 8, 2);
+
+ System.arraycopy(linkArray, 0, data, 10, linkArray.length);
+ // CheckStyle:MagicNumber ON
+
+ crc.reset();
+ crc.update(data);
+ long checksum = crc.getValue();
+
+ byte[] result = new byte[data.length + WORD];
+ System.arraycopy(ZipLong.getBytes(checksum), 0, result, 0, WORD);
+ System.arraycopy(data, 0, result, WORD, data.length);
+ return result;
+ }
+
+ /**
+ * Delegate to local file data.
+ *
+ * @return the local file data
+ * @since 1.1
+ */
+ public byte[] getCentralDirectoryData() {
+ return getLocalFileDataData();
+ }
+
+ /**
+ * Set the user id.
+ *
+ * @param uid the user id
+ * @since 1.1
+ */
+ public void setUserId(int uid) {
+ this.uid = uid;
+ }
+
+ /**
+ * Get the user id.
+ *
+ * @return the user id
+ * @since 1.1
+ */
+ public int getUserId() {
+ return uid;
+ }
+
+ /**
+ * Set the group id.
+ *
+ * @param gid the group id
+ * @since 1.1
+ */
+ public void setGroupId(int gid) {
+ this.gid = gid;
+ }
+
+ /**
+ * Get the group id.
+ *
+ * @return the group id
+ * @since 1.1
+ */
+ public int getGroupId() {
+ return gid;
+ }
+
+ /**
+ * Indicate that this entry is a symbolic link to the given filename.
+ *
+ * @param name Name of the file this entry links to, empty String
+ * if it is not a symbolic link.
+ * @since 1.1
+ */
+ public void setLinkedFile(String name) {
+ link = name;
+ mode = getMode(mode);
+ }
+
+ /**
+ * Name of linked file
+ *
+ * @return name of the file this entry links to if it is a
+ * symbolic link, the empty string otherwise.
+ * @since 1.1
+ */
+ public String getLinkedFile() {
+ return link;
+ }
+
+ /**
+ * Is this entry a symbolic link?
+ *
+ * @return true if this is a symbolic link
+ * @since 1.1
+ */
+ public boolean isLink() {
+ return getLinkedFile().length() != 0;
+ }
+
+ /**
+ * File mode of this file.
+ *
+ * @param mode the file mode
+ * @since 1.1
+ */
+ public void setMode(int mode) {
+ this.mode = getMode(mode);
+ }
+
+ /**
+ * File mode of this file.
+ *
+ * @return the file mode
+ * @since 1.1
+ */
+ public int getMode() {
+ return mode;
+ }
+
+ /**
+ * Indicate whether this entry is a directory.
+ *
+ * @param dirFlag if true, this entry is a directory
+ * @since 1.1
+ */
+ public void setDirectory(boolean dirFlag) {
+ this.dirFlag = dirFlag;
+ mode = getMode(mode);
+ }
+
+ /**
+ * Is this entry a directory?
+ *
+ * @return true if this entry is a directory
+ * @since 1.1
+ */
+ public boolean isDirectory() {
+ return dirFlag && !isLink();
+ }
+
+ /**
+ * Populate data from this array as if it was in local file data.
+ *
+ * @param data an array of bytes
+ * @param offset the start offset
+ * @param length the number of bytes in the array from offset
+ * @throws ZipException on error
+ * @since 1.1
+ */
+ public void parseFromLocalFileData(byte[] data, int offset, int length)
+ throws ZipException {
+
+ long givenChecksum = ZipLong.getValue(data, offset);
+ byte[] tmp = new byte[length - WORD];
+ System.arraycopy(data, offset + WORD, tmp, 0, length - WORD);
+ crc.reset();
+ crc.update(tmp);
+ long realChecksum = crc.getValue();
+ if (givenChecksum != realChecksum) {
+ throw new ZipException("bad CRC checksum "
+ + Long.toHexString(givenChecksum)
+ + " instead of "
+ + Long.toHexString(realChecksum));
+ }
+
+ int newMode = ZipShort.getValue(tmp, 0);
+ // CheckStyle:MagicNumber OFF
+ byte[] linkArray = new byte[(int) ZipLong.getValue(tmp, 2)];
+ uid = ZipShort.getValue(tmp, 6);
+ gid = ZipShort.getValue(tmp, 8);
+
+ if (linkArray.length == 0) {
+ link = "";
+ } else {
+ System.arraycopy(tmp, 10, linkArray, 0, linkArray.length);
+ link = new String(linkArray);
+ }
+ // CheckStyle:MagicNumber ON
+ setDirectory((newMode & DIR_FLAG) != 0);
+ setMode(newMode);
+ }
+
+ /**
+ * Get the file mode for given permissions with the correct file type.
+ *
+ * @param mode the mode
+ * @return the type with the mode
+ * @since 1.1
+ */
+ protected int getMode(int mode) {
+ int type = FILE_FLAG;
+ if (isLink()) {
+ type = LINK_FLAG;
+ } else if (isDirectory()) {
+ type = DIR_FLAG;
+ }
+ return type | (mode & PERM_MASK);
+ }
+
+ public Object clone() {
+ try {
+ AsiExtraField cloned = (AsiExtraField) super.clone();
+ cloned.crc = new CRC32();
+ return cloned;
+ } catch (CloneNotSupportedException cnfe) {
+ // impossible
+ throw new RuntimeException(cnfe);
+ }
+ }
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/CentralDirectoryParsingZipExtraField.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/CentralDirectoryParsingZipExtraField.java
new file mode 100644
index 0000000..b7770a2
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/CentralDirectoryParsingZipExtraField.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.
+ *
+ */
+
+package top.niunaijun.blackobfuscator.core.utils.zip.extrafield;
+
+import java.util.zip.ZipException;
+
+/**
+ * {@link ZipExtraField ZipExtraField} that knows how to parse central
+ * directory data.
+ *
+ * @since Ant 1.8.0
+ */
+public interface CentralDirectoryParsingZipExtraField extends ZipExtraField {
+ /**
+ * Populate data from this array as if it was in central directory data.
+ *
+ * @param data an array of bytes
+ * @param offset the start offset
+ * @param length the number of bytes in the array from offset
+ * @throws ZipException on error
+ */
+ void parseFromCentralDirectoryData(byte[] data, int offset, int length)
+ throws ZipException;
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/ExtraFieldUtils.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/ExtraFieldUtils.java
new file mode 100644
index 0000000..b7a63bd
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/ExtraFieldUtils.java
@@ -0,0 +1,324 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.
+ *
+ */
+
+package top.niunaijun.blackobfuscator.core.utils.zip.extrafield;
+
+import top.niunaijun.blackobfuscator.core.utils.zip.JarMarker;
+import top.niunaijun.blackobfuscator.core.utils.zip.ZipShort;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipException;
+
+
+/**
+ * ZipExtraField related methods
+ */
+// CheckStyle:HideUtilityClassConstructorCheck OFF (bc)
+public class ExtraFieldUtils {
+
+ private static final int WORD = 4;
+
+ /**
+ * Static registry of known extra fields.
+ *
+ * @since 1.1
+ */
+ private static final Map implementations;
+
+ static {
+ implementations = new HashMap<>();
+ register(AsiExtraField.class);
+ register(JarMarker.class);
+ register(UnicodePathExtraField.class);
+ register(UnicodeCommentExtraField.class);
+ }
+
+ /**
+ * Register a ZipExtraField implementation.
+ *
+ *
The given class must have a no-arg constructor and implement
+ * the {@link ZipExtraField ZipExtraField interface}.
+ *
+ * @param c the class to register
+ * @since 1.1
+ */
+ public static void register(Class c) {
+ try {
+ ZipExtraField ze = (ZipExtraField) c.newInstance();
+ implementations.put(ze.getHeaderId(), c);
+ } catch (ClassCastException cc) {
+ throw new RuntimeException(c + " doesn\'t implement ZipExtraField");
+ } catch (InstantiationException ie) {
+ throw new RuntimeException(c + " is not a concrete class");
+ } catch (IllegalAccessException ie) {
+ throw new RuntimeException(c + "\'s no-arg constructor is not public");
+ }
+ }
+
+ /**
+ * Create an instance of the approriate ExtraField, falls back to
+ * {@link UnrecognizedExtraField UnrecognizedExtraField}.
+ *
+ * @param headerId the header identifier
+ * @return an instance of the appropiate ExtraField
+ * @throws InstantiationException if unable to instantiate the class
+ * @throws IllegalAccessException if not allowed to instatiate the class
+ * @since 1.1
+ */
+ public static ZipExtraField createExtraField(ZipShort headerId)
+ throws InstantiationException, IllegalAccessException {
+ Class c = implementations.get(headerId);
+ if (c != null) {
+ return (ZipExtraField) c.newInstance();
+ }
+ UnrecognizedExtraField u = new UnrecognizedExtraField();
+ u.setHeaderId(headerId);
+ return u;
+ }
+
+ /**
+ * Split the array into ExtraFields and populate them with the
+ * given data as local file data, throwing an exception if the
+ * data cannot be parsed.
+ *
+ * @param data an array of bytes as it appears in local file data
+ * @return an array of ExtraFields
+ * @throws ZipException on error
+ */
+ public static ZipExtraField[] parse(byte[] data) throws ZipException {
+ return parse(data, true, UnparseableExtraField.THROW);
+ }
+
+ /**
+ * Split the array into ExtraFields and populate them with the
+ * given data, throwing an exception if the data cannot be parsed.
+ *
+ * @param data an array of bytes
+ * @param local whether data originates from the local file data
+ * or the central directory
+ * @return an array of ExtraFields
+ * @throws ZipException on error
+ * @since 1.1
+ */
+ public static ZipExtraField[] parse(byte[] data, boolean local)
+ throws ZipException {
+ return parse(data, local, UnparseableExtraField.THROW);
+ }
+
+ /**
+ * Split the array into ExtraFields and populate them with the
+ * given data.
+ *
+ * @param data an array of bytes
+ * @param local whether data originates from the local file data
+ * or the central directory
+ * @param onUnparseableData what to do if the extra field data
+ * cannot be parsed.
+ * @return an array of ExtraFields
+ * @throws ZipException on error
+ * @since Ant 1.8.1
+ */
+ public static ZipExtraField[] parse(byte[] data, boolean local,
+ UnparseableExtraField onUnparseableData)
+ throws ZipException {
+ List v = new ArrayList<>();
+ int start = 0;
+ LOOP:
+ while (start <= data.length - WORD) {
+ ZipShort headerId = new ZipShort(data, start);
+ int length = (new ZipShort(data, start + 2)).getValue();
+ if (start + WORD + length > data.length) {
+ switch (onUnparseableData.getKey()) {
+ case UnparseableExtraField.THROW_KEY:
+ throw new ZipException("bad extra field starting at "
+ + start + ". Block length of "
+ + length + " bytes exceeds remaining"
+ + " data of "
+ + (data.length - start - WORD)
+ + " bytes.");
+ case UnparseableExtraField.READ_KEY:
+ UnparseableExtraFieldData field =
+ new UnparseableExtraFieldData();
+ if (local) {
+ field.parseFromLocalFileData(data, start,
+ data.length - start);
+ } else {
+ field.parseFromCentralDirectoryData(data, start,
+ data.length - start);
+ }
+ v.add(field);
+ /*FALLTHROUGH*/
+ case UnparseableExtraField.SKIP_KEY:
+ // since we cannot parse the data we must assume
+ // the extra field consumes the whole rest of the
+ // available data
+ break LOOP;
+ default:
+ throw new ZipException("unknown UnparseableExtraField key: "
+ + onUnparseableData.getKey());
+ }
+ }
+ try {
+ ZipExtraField ze = createExtraField(headerId);
+ if (local
+ || !(ze instanceof CentralDirectoryParsingZipExtraField)) {
+ ze.parseFromLocalFileData(data, start + WORD, length);
+ } else {
+ ((CentralDirectoryParsingZipExtraField) ze)
+ .parseFromCentralDirectoryData(data, start + WORD,
+ length);
+ }
+ v.add(ze);
+ } catch (InstantiationException ie) {
+ throw new ZipException(ie.getMessage());
+ } catch (IllegalAccessException iae) {
+ throw new ZipException(iae.getMessage());
+ }
+ start += (length + WORD);
+ }
+
+ ZipExtraField[] result = new ZipExtraField[v.size()];
+ return v.toArray(result);
+ }
+
+ /**
+ * Merges the local file data fields of the given ZipExtraFields.
+ *
+ * @param data an array of ExtraFiles
+ * @return an array of bytes
+ * @since 1.1
+ */
+ public static byte[] mergeLocalFileDataData(ZipExtraField[] data) {
+ final boolean lastIsUnparseableHolder = data.length > 0
+ && data[data.length - 1] instanceof UnparseableExtraFieldData;
+ int regularExtraFieldCount =
+ lastIsUnparseableHolder ? data.length - 1 : data.length;
+
+ int sum = WORD * regularExtraFieldCount;
+ for (ZipExtraField aData : data) {
+ sum += aData.getLocalFileDataLength().getValue();
+ }
+
+ byte[] result = new byte[sum];
+ int start = 0;
+ for (int i = 0; i < regularExtraFieldCount; i++) {
+ System.arraycopy(data[i].getHeaderId().getBytes(),
+ 0, result, start, 2);
+ System.arraycopy(data[i].getLocalFileDataLength().getBytes(),
+ 0, result, start + 2, 2);
+ byte[] local = data[i].getLocalFileDataData();
+ System.arraycopy(local, 0, result, start + WORD, local.length);
+ start += (local.length + WORD);
+ }
+ if (lastIsUnparseableHolder) {
+ byte[] local = data[data.length - 1].getLocalFileDataData();
+ System.arraycopy(local, 0, result, start, local.length);
+ }
+ return result;
+ }
+
+ /**
+ * Merges the central directory fields of the given ZipExtraFields.
+ *
+ * @param data an array of ExtraFields
+ * @return an array of bytes
+ * @since 1.1
+ */
+ public static byte[] mergeCentralDirectoryData(ZipExtraField[] data) {
+ final boolean lastIsUnparseableHolder = data.length > 0
+ && data[data.length - 1] instanceof UnparseableExtraFieldData;
+ int regularExtraFieldCount =
+ lastIsUnparseableHolder ? data.length - 1 : data.length;
+
+ int sum = WORD * regularExtraFieldCount;
+ for (ZipExtraField aData : data) {
+ sum += aData.getCentralDirectoryLength().getValue();
+ }
+ byte[] result = new byte[sum];
+ int start = 0;
+ for (int i = 0; i < regularExtraFieldCount; i++) {
+ System.arraycopy(data[i].getHeaderId().getBytes(),
+ 0, result, start, 2);
+ System.arraycopy(data[i].getCentralDirectoryLength().getBytes(),
+ 0, result, start + 2, 2);
+ byte[] local = data[i].getCentralDirectoryData();
+ System.arraycopy(local, 0, result, start + WORD, local.length);
+ start += (local.length + WORD);
+ }
+ if (lastIsUnparseableHolder) {
+ byte[] local = data[data.length - 1].getCentralDirectoryData();
+ System.arraycopy(local, 0, result, start, local.length);
+ }
+ return result;
+ }
+
+ /**
+ * "enum" for the possible actions to take if the extra field
+ * cannot be parsed.
+ */
+ public static final class UnparseableExtraField {
+ /**
+ * Key for "throw an exception" action.
+ */
+ public static final int THROW_KEY = 0;
+ /**
+ * Key for "skip" action.
+ */
+ public static final int SKIP_KEY = 1;
+ /**
+ * Key for "read" action.
+ */
+ public static final int READ_KEY = 2;
+
+ /**
+ * Throw an exception if field cannot be parsed.
+ */
+ public static final UnparseableExtraField THROW
+ = new UnparseableExtraField(THROW_KEY);
+
+ /**
+ * Skip the extra field entirely and don't make its data
+ * available - effectively removing the extra field data.
+ */
+ public static final UnparseableExtraField SKIP
+ = new UnparseableExtraField(SKIP_KEY);
+
+ /**
+ * Read the extra field data into an instance of {@link
+ * UnparseableExtraFieldData UnparseableExtraFieldData}.
+ */
+ public static final UnparseableExtraField READ
+ = new UnparseableExtraField(READ_KEY);
+
+ private final int key;
+
+ private UnparseableExtraField(int k) {
+ key = k;
+ }
+
+ /**
+ * Key of the action to take.
+ */
+ public int getKey() {
+ return key;
+ }
+ }
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/UnicodeCommentExtraField.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/UnicodeCommentExtraField.java
new file mode 100644
index 0000000..9255ca5
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/UnicodeCommentExtraField.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.
+ *
+ */
+
+package top.niunaijun.blackobfuscator.core.utils.zip.extrafield;
+
+
+import top.niunaijun.blackobfuscator.core.utils.zip.ZipShort;
+
+/**
+ * Info-ZIP Unicode Comment Extra Field (0x6375):
+ *
+ *
Stores the UTF-8 version of the file comment as stored in the
+ * central directory header.
+ *
+ *
+ * Value Size Description
+ * ----- ---- -----------
+ * (UCom) 0x6375 Short tag for this extra block type ("uc")
+ * TSize Short total data size for this block
+ * Version 1 byte version of this extra field, currently 1
+ * ComCRC32 4 bytes Comment Field CRC32 Checksum
+ * UnicodeCom Variable UTF-8 version of the entry comment
+ *
+ */
+public class UnicodeCommentExtraField extends AbstractUnicodeExtraField {
+
+ public static final ZipShort UCOM_ID = new ZipShort(0x6375);
+
+ public UnicodeCommentExtraField() {
+ }
+
+ /**
+ * Assemble as unicode comment extension from the name given as
+ * text as well as the encoded bytes actually written to the archive.
+ *
+ * @param text The file name
+ * @param bytes the bytes actually written to the archive
+ * @param off The offset of the encoded comment in bytes
.
+ * @param len The length of the encoded comment or comment in
+ * bytes
.
+ */
+ public UnicodeCommentExtraField(String text, byte[] bytes, int off,
+ int len) {
+ super(text, bytes, off, len);
+ }
+
+ /**
+ * Assemble as unicode comment extension from the comment given as
+ * text as well as the bytes actually written to the archive.
+ *
+ * @param comment The file comment
+ * @param bytes the bytes actually written to the archive
+ */
+ public UnicodeCommentExtraField(String comment, byte[] bytes) {
+ super(comment, bytes);
+ }
+
+ public ZipShort getHeaderId() {
+ return UCOM_ID;
+ }
+
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/UnicodePathExtraField.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/UnicodePathExtraField.java
new file mode 100644
index 0000000..307b146
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/UnicodePathExtraField.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.
+ *
+ */
+
+package top.niunaijun.blackobfuscator.core.utils.zip.extrafield;
+
+import top.niunaijun.blackobfuscator.core.utils.zip.ZipShort;
+
+/**
+ * Info-ZIP Unicode Path Extra Field (0x7075):
+ *
+ *
Stores the UTF-8 version of the file name field as stored in the
+ * local header and central directory header.
+ *
+ *
+ * Value Size Description
+ * ----- ---- -----------
+ * (UPath) 0x7075 Short tag for this extra block type ("up")
+ * TSize Short total data size for this block
+ * Version 1 byte version of this extra field, currently 1
+ * NameCRC32 4 bytes File Name Field CRC32 Checksum
+ * UnicodeName Variable UTF-8 version of the entry File Name
+ *
+ */
+public class UnicodePathExtraField extends AbstractUnicodeExtraField {
+
+ public static final ZipShort UPATH_ID = new ZipShort(0x7075);
+
+ public UnicodePathExtraField() {
+ }
+
+ /**
+ * Assemble as unicode path extension from the name given as
+ * text as well as the encoded bytes actually written to the archive.
+ *
+ * @param text The file name
+ * @param bytes the bytes actually written to the archive
+ * @param off The offset of the encoded filename in bytes
.
+ * @param len The length of the encoded filename or comment in
+ * bytes
.
+ */
+ public UnicodePathExtraField(String text, byte[] bytes, int off, int len) {
+ super(text, bytes, off, len);
+ }
+
+ /**
+ * Assemble as unicode path extension from the name given as
+ * text as well as the encoded bytes actually written to the archive.
+ *
+ * @param name The file name
+ * @param bytes the bytes actually written to the archive
+ */
+ public UnicodePathExtraField(String name, byte[] bytes) {
+ super(name, bytes);
+ }
+
+ public ZipShort getHeaderId() {
+ return UPATH_ID;
+ }
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/UnparseableExtraFieldData.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/UnparseableExtraFieldData.java
new file mode 100644
index 0000000..e197fb9
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/UnparseableExtraFieldData.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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.
+ */
+package top.niunaijun.blackobfuscator.core.utils.zip.extrafield;
+
+import top.niunaijun.blackobfuscator.core.utils.zip.ZipShort;
+import top.niunaijun.blackobfuscator.core.utils.zip.ZipUtil;
+
+/**
+ * Wrapper for extra field data that doesn't conform to the recommended format of header-tag + size + data.
+ *
+ *
The header-id is artificial (and not listed as a know ID in
+ * the .ZIP File Format Specification).
+ * Since it isn't used anywhere except to satisfy the
+ * ZipExtraField contract it shouldn't matter anyway.
+ *
+ * @see .ZIP File Format Specification
+ * @since Ant 1.8.1
+ */
+public final class UnparseableExtraFieldData
+ implements CentralDirectoryParsingZipExtraField {
+
+ private static final ZipShort HEADER_ID = new ZipShort(0xACC1);
+
+ private byte[] localFileData;
+ private byte[] centralDirectoryData;
+
+ /**
+ * The Header-ID.
+ *
+ * @return a completely arbitrary value that should be ignored.
+ */
+ public ZipShort getHeaderId() {
+ return HEADER_ID;
+ }
+
+ /**
+ * Length of the complete extra field in the local file data.
+ *
+ * @return The LocalFileDataLength value
+ */
+ public ZipShort getLocalFileDataLength() {
+ return new ZipShort(localFileData == null ? 0 : localFileData.length);
+ }
+
+ /**
+ * Length of the complete extra field in the central directory.
+ *
+ * @return The CentralDirectoryLength value
+ */
+ public ZipShort getCentralDirectoryLength() {
+ return centralDirectoryData == null
+ ? getLocalFileDataLength()
+ : new ZipShort(centralDirectoryData.length);
+ }
+
+ /**
+ * The actual data to put into local file data.
+ *
+ * @return The LocalFileDataData value
+ */
+ public byte[] getLocalFileDataData() {
+ return ZipUtil.copy(localFileData);
+ }
+
+ /**
+ * The actual data to put into central directory.
+ *
+ * @return The CentralDirectoryData value
+ */
+ public byte[] getCentralDirectoryData() {
+ return centralDirectoryData == null
+ ? getLocalFileDataData() : ZipUtil.copy(centralDirectoryData);
+ }
+
+ /**
+ * Populate data from this array as if it was in local file data.
+ *
+ * @param buffer the buffer to read data from
+ * @param offset offset into buffer to read data
+ * @param length the length of data
+ */
+ public void parseFromLocalFileData(byte[] buffer, int offset, int length) {
+ localFileData = new byte[length];
+ System.arraycopy(buffer, offset, localFileData, 0, length);
+ }
+
+ /**
+ * Populate data from this array as if it was in central directory data.
+ *
+ * @param buffer the buffer to read data from
+ * @param offset offset into buffer to read data
+ * @param length the length of data
+ */
+ public void parseFromCentralDirectoryData(byte[] buffer, int offset,
+ int length) {
+ centralDirectoryData = new byte[length];
+ System.arraycopy(buffer, offset, centralDirectoryData, 0, length);
+ if (localFileData == null) {
+ parseFromLocalFileData(buffer, offset, length);
+ }
+ }
+
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/UnrecognizedExtraField.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/UnrecognizedExtraField.java
new file mode 100644
index 0000000..530f51e
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/UnrecognizedExtraField.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.
+ *
+ */
+
+package top.niunaijun.blackobfuscator.core.utils.zip.extrafield;
+
+import top.niunaijun.blackobfuscator.core.utils.zip.ZipShort;
+import top.niunaijun.blackobfuscator.core.utils.zip.ZipUtil;
+
+/**
+ * Simple placeholder for all those extra fields we don't want to deal
+ * with.
+ *
+ *
Assumes local file data and central directory entries are
+ * identical - unless told the opposite.
+ */
+public class UnrecognizedExtraField
+ implements CentralDirectoryParsingZipExtraField {
+
+ /**
+ * The Header-ID.
+ *
+ * @since 1.1
+ */
+ private ZipShort headerId;
+
+ /**
+ * Set the header id.
+ *
+ * @param headerId the header id to use
+ */
+ public void setHeaderId(ZipShort headerId) {
+ this.headerId = headerId;
+ }
+
+ /**
+ * Get the header id.
+ *
+ * @return the header id
+ */
+ public ZipShort getHeaderId() {
+ return headerId;
+ }
+
+ /**
+ * Extra field data in local file data - without
+ * Header-ID or length specifier.
+ *
+ * @since 1.1
+ */
+ private byte[] localData;
+
+ /**
+ * Set the extra field data in the local file data -
+ * without Header-ID or length specifier.
+ *
+ * @param data the field data to use
+ */
+ public void setLocalFileDataData(byte[] data) {
+ localData = ZipUtil.copy(data);
+ }
+
+ /**
+ * Get the length of the local data.
+ *
+ * @return the length of the local data
+ */
+ public ZipShort getLocalFileDataLength() {
+ return new ZipShort(localData.length);
+ }
+
+ /**
+ * Get the local data.
+ *
+ * @return the local data
+ */
+ public byte[] getLocalFileDataData() {
+ return ZipUtil.copy(localData);
+ }
+
+ /**
+ * Extra field data in central directory - without
+ * Header-ID or length specifier.
+ *
+ * @since 1.1
+ */
+ private byte[] centralData;
+
+ /**
+ * Set the extra field data in central directory.
+ *
+ * @param data the data to use
+ */
+ public void setCentralDirectoryData(byte[] data) {
+ centralData = ZipUtil.copy(data);
+ }
+
+ /**
+ * Get the central data length.
+ * If there is no central data, get the local file data length.
+ *
+ * @return the central data length
+ */
+ public ZipShort getCentralDirectoryLength() {
+ if (centralData != null) {
+ return new ZipShort(centralData.length);
+ }
+ return getLocalFileDataLength();
+ }
+
+ /**
+ * Get the central data.
+ *
+ * @return the central data if present, else return the local file data
+ */
+ public byte[] getCentralDirectoryData() {
+ if (centralData != null) {
+ return ZipUtil.copy(centralData);
+ }
+ return getLocalFileDataData();
+ }
+
+ /**
+ * @param data the array of bytes.
+ * @param offset the source location in the data array.
+ * @param length the number of bytes to use in the data array.
+ * @see ZipExtraField#parseFromLocalFileData(byte[], int, int)
+ */
+ public void parseFromLocalFileData(byte[] data, int offset, int length) {
+ byte[] tmp = new byte[length];
+ System.arraycopy(data, offset, tmp, 0, length);
+ setLocalFileDataData(tmp);
+ }
+
+ /**
+ * @param data the array of bytes.
+ * @param offset the source location in the data array.
+ * @param length the number of bytes to use in the data array.
+ */
+ public void parseFromCentralDirectoryData(byte[] data, int offset,
+ int length) {
+ byte[] tmp = new byte[length];
+ System.arraycopy(data, offset, tmp, 0, length);
+ setCentralDirectoryData(tmp);
+ if (localData == null) {
+ setLocalFileDataData(tmp);
+ }
+ }
+
+}
diff --git a/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/ZipExtraField.java b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/ZipExtraField.java
new file mode 100644
index 0000000..bfa80fb
--- /dev/null
+++ b/plugin/src/main/java/top/niunaijun/blackobfuscator/core/utils/zip/extrafield/ZipExtraField.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.
+ *
+ */
+
+package top.niunaijun.blackobfuscator.core.utils.zip.extrafield;
+
+import top.niunaijun.blackobfuscator.core.utils.zip.ZipShort;
+
+import java.util.zip.ZipException;
+
+
+/**
+ * General format of extra field data.
+ *
+ *
Extra fields usually appear twice per file, once in the local
+ * file data and once in the central directory. Usually they are the
+ * same, but they don't have to be. {@link
+ * java.util.zip.ZipOutputStream java.util.zip.ZipOutputStream} will
+ * only use the local file data in both places.
+ */
+public interface ZipExtraField {
+
+ /**
+ * The Header-ID.
+ *
+ * @return the header id
+ * @since 1.1
+ */
+ ZipShort getHeaderId();
+
+ /**
+ * Length of the extra field in the local file data - without
+ * Header-ID or length specifier.
+ *
+ * @return the length of the field in the local file data
+ * @since 1.1
+ */
+ ZipShort getLocalFileDataLength();
+
+ /**
+ * Length of the extra field in the central directory - without
+ * Header-ID or length specifier.
+ *
+ * @return the length of the field in the central directory
+ * @since 1.1
+ */
+ ZipShort getCentralDirectoryLength();
+
+ /**
+ * The actual data to put into local file data - without Header-ID
+ * or length specifier.
+ *
+ * @return the data
+ * @since 1.1
+ */
+ byte[] getLocalFileDataData();
+
+ /**
+ * The actual data to put into central directory - without Header-ID or
+ * length specifier.
+ *
+ * @return the data
+ * @since 1.1
+ */
+ byte[] getCentralDirectoryData();
+
+ /**
+ * Populate data from this array as if it was in local file data.
+ *
+ * @param data an array of bytes
+ * @param offset the start offset
+ * @param length the number of bytes in the array from offset
+ * @throws ZipException on error
+ * @since 1.1
+ */
+ void parseFromLocalFileData(byte[] data, int offset, int length)
+ throws ZipException;
+}
diff --git a/plugin/src/main/resources/META-INF/gradle-plugins/top.niunaijun.blackobfuscator.properties b/plugin/src/main/resources/META-INF/gradle-plugins/top.niunaijun.blackobfuscator.properties
new file mode 100644
index 0000000..ed1b3da
--- /dev/null
+++ b/plugin/src/main/resources/META-INF/gradle-plugins/top.niunaijun.blackobfuscator.properties
@@ -0,0 +1 @@
+implementation-class=top.niunaijun.blackobfuscator.ObfPlugin
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..1d1f6d6
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,3 @@
+rootProject.name = "BlackObfuscator-ASPlugin"
+include ':app'
+include ':plugin'