Initial commit

main
Potato Sus 2023-01-29 09:06:35 -03:00
commit a45ffb98c8
49 changed files with 2976 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

34
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,34 @@
name: Build
on: [ pull_request, push ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
java: [ '8', '17' ]
name: Java ${{ matrix.Java }} build
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Ensure genuine gradle
uses: gradle/wrapper-validation-action@v1
- name: Setup Java
uses: actions/setup-java@v2
with:
distribution: 'zulu'
java-version: ${{ matrix.java }}
name: Gradle Build
check-latest: true
- name: Make gradle wrapper executable
run: chmod +x ./gradlew
- name: Run "clean" task
run: ./gradlew clean
- name: Run "build" task
run: ./gradlew build --no-daemon
- name: Capture artifacts
uses: actions/upload-artifact@v2
with:
name: zProtect Internal Preview (Java ${{ matrix.java }})
path: build/libs/*

22
.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
# Eclipse
bin
*.launch
.settings
.metadata
.classpath
.project
# IDEA
out
*.ipr
*.iws
*.iml
.idea
# Gradle
build
.gradle
# Other
eclipse
run

69
.onedev-buildspec.yml Normal file
View File

@ -0,0 +1,69 @@
version: 15
jobs:
- name: Build
steps:
- !CheckoutStep
name: Checkout
cloneCredential: !DefaultCredential {}
withLfs: false
withSubmodules: false
condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL
- !CommandStep
name: Detect Version
runInContainer: true
image: '@script:builtin:gradle:determine-docker-image@'
interpreter: !DefaultInterpreter
commands:
- echo "Detecting project version (may require some time while downloading gradle
dependencies)..."
- 'echo $(gradle properties | grep ^version: | grep -v unspecified | cut -c10-)
> buildVersion'
useTTY: false
condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL
- !SetBuildVersionStep
name: Set Version
buildVersion: '@file:buildVersion@'
condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL
- !CommandStep
name: Post Webhook
runInContainer: true
image: prontotools/alpine-git-curl
interpreter: !DefaultInterpreter
commands:
- user=$(git log --format=%an -n 1 @commit_hash@)
- commit_hash=@commit_hash@
- commit_message=$(git log --format=%B -n 1 @commit_hash@)
- 'curl -H ''Content-Type: application/json'' \'
- "\t-d \"{\\\"avatar_url\\\": \\\"https://test.it-snek.com/static/media/logo.c0ab911818705a32c78c.png\\\
\", \\\"username\\\": \\\"zGitBot\\\", \\\"embeds\\\": [{\\\"author\\\": {\\\
\"name\\\": \\\"${user}\\\"},\\\"title\\\": \\\"@project_name@/@branch@\\\"\
, \\\"description\\\": \\\"${commit_hash:0:7}: ${commit_message}\\\"}]}\"\
\ \\"
- "\t'https://discord.com/api/webhooks/904058376504348702/v7Otrf48So0S8g67YkH8Xnh6g7EN9T8grSuvY3IHGksjhP6V7AYb5FG0zKr33mtZSFmq'"
useTTY: false
condition: ALWAYS
- !CommandStep
name: Gradle Build
runInContainer: true
image: '@script:builtin:gradle:determine-docker-image@'
interpreter: !DefaultInterpreter
commands:
- gradle build --no-daemon
useTTY: false
condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL
- !PublishArtifactStep
name: Publish Artifact
artifacts: build/libs/*
condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL
triggers:
- !BranchUpdateTrigger {}
- !PullRequestUpdateTrigger {}
retryCondition: never
maxRetries: 3
retryDelay: 30
cpuRequirement: 500
memoryRequirement: 256
caches:
- key: gradle-cache
path: /home/gradle/.gradle
timeout: 3600

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "automatic"
}

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Potato Sus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

61
build.gradle.kts Normal file
View File

@ -0,0 +1,61 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
java
kotlin("jvm") version "1.7.20"
kotlin("plugin.serialization") version "1.6.10"
id("com.github.johnrengelman.shadow") version "7.1.2"
}
group = "dev.zprotect"
version = "0.2"
repositories {
mavenCentral()
maven {
url = uri("https://oss.sonatype.org/content/groups/public/")
}
}
dependencies {
implementation("org.json:json:20211205")
implementation("org.ow2.asm:asm:9.2")
implementation("org.ow2.asm:asm-tree:9.2")
implementation("org.ow2.asm:asm-commons:9.2")
implementation("org.ow2.asm:asm-util:9.2")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
}
tasks {
build {
dependsOn(shadowJar)
}
register<Jar>("standaloneJar") {
manifest {
attributes["Main-Class"] = "dev.zprotect.obfuscator.StandaloneKt"
}
}
named<ShadowJar>("shadowJar") {
manifest {
attributes["Main-Class"] = "dev.zprotect.obfuscator.MainKt"
}
minimize()
}
withType<Jar> {
manifest {
attributes["Main-Class"] = "dev.zprotect.obfuscator.MainKt"
}
}
withType<KotlinCompile> {
kotlinOptions {
jvmTarget = "1.8"
}
}
}

1
gradle.properties Normal file
View File

@ -0,0 +1 @@
kotlin.code.style=official

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for 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='"-Xmx64m" "-Xms64m"'
# 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 or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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=`expr $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"
exec "$JAVACMD" "$@"

104
gradlew.bat vendored Normal file
View File

@ -0,0 +1,104 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto 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

1
settings.gradle.kts Normal file
View File

@ -0,0 +1 @@
rootProject.name = "zProtect"

View File

@ -0,0 +1,23 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.MethodNode
fun ClassNode.hasAccess(access: Int) = this.access and access == 0
fun FieldNode.hasAccess(access: Int) = this.access and access == 0
fun MethodNode.hasAccess(access: Int) = this.access and access == 0

View File

@ -0,0 +1,31 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator
import dev.zprotect.obfuscator.Obfuscator.info
import java.io.File
import kotlin.system.exitProcess
import java.util.Collections
import java.util.UUID
fun main(args: Array<String>) {
if (args.size != 1) {
Obfuscator.info("Missing argument: Config file")
exitProcess(-1)
}
Obfuscator.info("Visit https://zprotect.dev for more information.\n")
Obfuscator.run(File(args[0]))
}

View File

@ -0,0 +1,306 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator
import dev.zprotect.obfuscator.api.ClassPath
import dev.zprotect.obfuscator.api.Transformer
import dev.zprotect.obfuscator.api.interfaces.IBytecode
import dev.zprotect.obfuscator.api.settings.ArrayConfig
import dev.zprotect.obfuscator.api.settings.BooleanConfig
import dev.zprotect.obfuscator.api.settings.Config
import dev.zprotect.obfuscator.api.settings.StringConfig
import dev.zprotect.obfuscator.transformers.antitamper.AntiDebug
import dev.zprotect.obfuscator.transformers.decompiler.BadAnnotationCrasher
import dev.zprotect.obfuscator.transformers.decompiler.DecompilerCrasher
import dev.zprotect.obfuscator.transformers.flow.Flow
import dev.zprotect.obfuscator.transformers.naming.ClassRenamer
import dev.zprotect.obfuscator.transformers.naming.FieldRenamer
import dev.zprotect.obfuscator.transformers.naming.LocalVariableRenamer
import dev.zprotect.obfuscator.transformers.naming.MethodRenamer
import dev.zprotect.obfuscator.transformers.optimization.*
import dev.zprotect.obfuscator.transformers.poolers.NumberPooler
import dev.zprotect.obfuscator.transformers.shrinking.*
import dev.zprotect.obfuscator.transformers.shufflers.ShuffleFields
import dev.zprotect.obfuscator.transformers.shufflers.ShuffleMethods
import org.objectweb.asm.ClassReader
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.StandardOpenOption
import java.time.Instant
import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.jar.Manifest
import kotlin.random.Random
object Obfuscator {
private val inputJar = StringConfig("inputJar")
private val outputJar = StringConfig("outputJar")
private val exclusions = ArrayConfig("exclusions")
private val libraries = ArrayConfig("libraries")
private val watermark = BooleanConfig("watermark")
private lateinit var ioMode: IOMode
private val files: MutableMap<String, ByteArray> = HashMap()
private val classNodes: MutableMap<String, ClassNode> = HashMap()
private lateinit var manifest: Manifest
@JvmStatic
fun info(msg: String) {
println("[${Instant.now()}] $msg")
}
fun run(config: File) {
val startTime = System.currentTimeMillis()
Config.read(config)
if (inputJar.value == null || outputJar.value == null) {
info("Error: Input file or output file is not specified!")
return
}
val inputFile = File(inputJar.value!!)
if (!inputFile.exists()) {
info("Input file not found!")
return
}
openFile(inputFile)
ClassPath.load(getLibraries())
arrayListOf(
// Flow
Flow,
// Shrinking
RemoveInnerClasses,
LocalVariableRemover,
LineNumberRemover,
SourceDebugRemover,
SourceFileRemover,
// Shuffling
ShuffleFields,
ShuffleMethods,
// Number
NumberPooler,
// Optimization
KotlinMetadataRemover,
HideClassMembers,
RemoveSignatures,
FinalRemover,
InsnRemover,
EnumOptimiser,
NOPInsnRemover,
// Naming
MethodRenamer,
FieldRenamer,
ClassRenamer,
LocalVariableRenamer,
// Reversing
DecompilerCrasher,
AntiDebug,
BadAnnotationCrasher,
).forEach(Transformer::run)
saveFile()
info("zProtect has finished obfuscating in " + (System.currentTimeMillis() - startTime) + " milliseconds.")
}
private fun openFile(file: File) {
if (file.name.endsWith(".jar")) {
ioMode = IOMode.JAR
openJar(JarFile(file))
} else if (file.name.endsWith(".class")) {
ioMode = IOMode.CLASS
openClass(file)
}
}
private fun openJar(jar: JarFile) {
val entries = jar.entries()
while (entries.hasMoreElements()) {
val entry = entries.nextElement()
val bytes: ByteArray = IBytecode.readBytes(jar.getInputStream(entry))
if (!entry.name.endsWith(".class")) {
files[entry.name] = bytes
} else {
val classNode = ClassNode()
ClassReader(bytes).accept(classNode, ClassReader.EXPAND_FRAMES)
classNodes[classNode.name] = classNode
ClassPath.get(classNode)
}
}
manifest = jar.manifest
}
private fun openClass(file: File) {
val bytes: ByteArray = IBytecode.readBytes(file.inputStream())
val classNode = ClassNode()
ClassReader(bytes).accept(classNode, ClassReader.EXPAND_FRAMES)
classNodes[classNode.name] = classNode
}
private fun saveFile() {
when (ioMode) {
IOMode.JAR -> saveJar()
IOMode.CLASS -> saveClass()
}
}
private fun saveJar() {
/*
not P
TODO: Figure out why CRC corrupter doesn't work with JarOutputStream
Even when we set ZipOutputStream and use the function it does not work...
Here is the function:
fun corruptCRC32(zipOutputStream: ZipOutputStream?) {
try {
val field: Field = ZipOutputStream::class.java.getDeclaredField("crc")
field.isAccessible = true
field.set(zipOutputStream, object : CRC32() {
override fun getValue(): Long {
return -0x21523f22
}
})
} catch (ignored: NoSuchFieldException) {
} catch (ignored: IllegalAccessException) {}
}
FIX THIS PLEASE...
*/
val byteArrayOutputStream = ByteArrayOutputStream()
manifest.write(byteArrayOutputStream)
files["META-INF/MANIFEST.MF"] = byteArrayOutputStream.toByteArray()
var location: String = outputJar.value!!
if (!location.endsWith(".jar")) location += ".jar"
val jarPath = Paths.get(location)
Files.deleteIfExists(jarPath)
val outJar = JarOutputStream(
Files.newOutputStream(
jarPath,
StandardOpenOption.CREATE,
StandardOpenOption.CREATE_NEW,
StandardOpenOption.WRITE
)
)
classNodes.values.forEach { classNode ->
outJar.putNextEntry(JarEntry(classNode.name + ".class"))
outJar.write(IBytecode.writeBytes(classNode))
outJar.closeEntry()
}
files.entries.forEach { (key, value) ->
outJar.putNextEntry(JarEntry(key))
outJar.write(value)
outJar.closeEntry()
}
// Our unique identifier so we can check if a program has been obfuscated with zProtect or not.
// Set a comment in jar, can be seen once opening a jar in a zip viewer.
if (watermark.value == true) {
outJar.setComment("보호에 의해 난독화되고 오늘 보호 난독화 장치를 구입하십시오.")
// Dummy class.
val dummy = ClassNode()
dummy.visit(
Opcodes.V1_5,
Opcodes.ACC_PUBLIC,
"보호에 의해 난독화되고 오늘 보호 난독화 장치를 구입하십시오.",
null,
"java/lang/Object",
null
)
dummy.visitMethod(Random.nextInt(100), "\u0001", "(\u0001/)L\u0001/;", null, null)
try {
outJar.putNextEntry(JarEntry(dummy.name + ".class"))
outJar.write(IBytecode.toByteArrayDefault(dummy))
info("Applied transformer: Watermark")
} catch (e: IOException) {
e.printStackTrace()
}
}
outJar.close()
}
private fun saveClass() {
var location: String = outputJar.value!!
if (!location.endsWith(".class")) location += ".class"
val classPath = Paths.get(location)
Files.deleteIfExists(classPath)
val outputStream = Files.newOutputStream(
classPath,
StandardOpenOption.CREATE,
StandardOpenOption.CREATE_NEW,
StandardOpenOption.WRITE
)
outputStream.write(IBytecode.writeBytes(classNodes.entries.iterator().next().value))
outputStream.close()
}
fun getClassNodes(): MutableMap<String, ClassNode> {
return classNodes
}
fun getFiles(): MutableMap<String, ByteArray> {
return files
}
fun getManifest(): Manifest {
return manifest
}
fun getExclusions(): List<String> { // TODO: Caching for faster performance.
val exclusionsCache: MutableList<String> = mutableListOf()
if (exclusions.value == null) return exclusionsCache
for (i in 0 until exclusions.value!!.length()) exclusionsCache.add(exclusions.value!!.getString(i))
return exclusionsCache
}
fun getLibraries(): List<String> { // TODO: Caching for faster performance.
val librariesCache: MutableList<String> = mutableListOf()
if (libraries.value == null) return librariesCache
for (i in 0 until libraries.value!!.length()) librariesCache.add(libraries.value!!.getString(i))
return librariesCache
}
enum class IOMode {
CLASS, JAR;
}
}

View File

@ -0,0 +1,116 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.api
import dev.zprotect.obfuscator.api.interfaces.IBytecode
import org.objectweb.asm.ClassReader
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.Type
import java.util.jar.JarFile
object ClassPath : HashMap<String, ClassEntry>() {
override fun get(key: String): ClassEntry? = super.get(key) ?: add(get(Class.forName(key.replace("/", "."))))
fun get(classNode: ClassNode): ClassEntry = super.get(classNode.name) ?: add(ClassNodeEntry(classNode))
fun get(clazz: Class<*>): ClassEntry = super.get(Type.getInternalName(clazz)) ?: add(ReflectionClassEntry(clazz))
private fun add(classEntry: ClassEntry): ClassEntry = classEntry.apply { put(classEntry.getName(), classEntry) }
fun load(paths: List<String>) = paths.forEach { load(it) }
fun load(path: String) {
val jar = JarFile(path)
val entries = jar.entries()
while (entries.hasMoreElements()) {
val entry = entries.nextElement()
if (entry.name.endsWith(".class")) {
val bytes: ByteArray = IBytecode.readBytes(jar.getInputStream(entry))
val classNode = ClassNode()
ClassReader(bytes).accept(classNode, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG)
put(classNode.name, ClassNodeEntry(classNode))
}
}
}
}
abstract class ClassEntry {
abstract fun getName(): String
abstract fun getSuper(): String?
abstract fun getAccess(): Int
abstract fun getInterfaces(): MutableList<String>
abstract fun getFields(): MutableList<FieldEntry>
abstract fun getMethods(): MutableList<MethodEntry>
}
data class MethodEntry(val owner: ClassEntry, val name: String, val desc: String)
data class FieldEntry(val owner: ClassEntry, val name: String, val desc: String)
class ClassNodeEntry(private val classNode: ClassNode) : ClassEntry() {
override fun getName(): String = classNode.name
override fun getSuper(): String? = classNode.superName
override fun getAccess(): Int = classNode.access
override fun getInterfaces(): MutableList<String> = classNode.interfaces
override fun getFields(): MutableList<FieldEntry> = exploreFields
private val exploreFields: MutableList<FieldEntry> by lazy {
val list = mutableListOf<FieldEntry>()
for (fieldNode in classNode.fields) {
list.add(FieldEntry(this, fieldNode.name, fieldNode.desc))
}
list
}
override fun getMethods(): MutableList<MethodEntry> = exploreMethods
private val exploreMethods: MutableList<MethodEntry> by lazy {
val list = mutableListOf<MethodEntry>()
for (methodNode in classNode.methods) {
list.add(MethodEntry(this, methodNode.name, methodNode.desc))
}
list
}
}
class ReflectionClassEntry(private val clazz: Class<*>) : ClassEntry() {
override fun getName(): String = Type.getInternalName(clazz)
override fun getSuper(): String? = if (clazz.superclass == null) null else Type.getInternalName(clazz.superclass)
?: if (getName() != "java/lang/Object") "java/lang/Object" else null
override fun getAccess(): Int = clazz.modifiers
override fun getInterfaces(): MutableList<String> = exploreInterfaces
private val exploreInterfaces: MutableList<String> by lazy {
val list = mutableListOf<String>()
for (interfaces in clazz.interfaces) {
list.add(Type.getInternalName(interfaces))
}
list
}
override fun getFields(): MutableList<FieldEntry> = exploreFields
private val exploreFields: MutableList<FieldEntry> by lazy {
val list = mutableListOf<FieldEntry>()
for (field in clazz.declaredFields) {
list.add(FieldEntry(this, field.name, Type.getDescriptor(field.type)))
}
list
}
override fun getMethods(): MutableList<MethodEntry> = exploreMethods
private val exploreMethods: MutableList<MethodEntry> by lazy {
val list = mutableListOf<MethodEntry>()
for (method in clazz.declaredMethods) {
list.add(MethodEntry(this, method.name, Type.getMethodDescriptor(method)))
}
list
}
}

View File

@ -0,0 +1,101 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.api
import dev.zprotect.obfuscator.Obfuscator
import dev.zprotect.obfuscator.Obfuscator.info
import dev.zprotect.obfuscator.api.settings.BooleanConfig
import dev.zprotect.obfuscator.api.interfaces.INode
import dev.zprotect.obfuscator.api.interfaces.IBytecode
import org.objectweb.asm.commons.ClassRemapper
import org.objectweb.asm.commons.SimpleRemapper
import org.objectweb.asm.tree.ClassNode
import java.util.jar.Manifest
import java.util.stream.Collectors
abstract class Transformer(val name: String, val description: String) : IBytecode, INode {
private val setting = BooleanConfig(name)
val classMap: MutableMap<String, ClassNode>
get() = Obfuscator.getClassNodes()
val classes: List<ClassNode>
get() = ArrayList(classMap.values)
val filesMap: MutableMap<String, ByteArray>
get() = Obfuscator.getFiles()
val files: List<String>
get() = ArrayList(filesMap.keys)
val manifest: Manifest
get() = Obfuscator.getManifest()
fun run() {
if (setting.value != null && setting.value!!) {
obfuscate()
info("Applied transformer: $name")
}
}
protected abstract fun obfuscate()
fun applyRemap(remap: Map<String?, String?>?) {
val remapper = SimpleRemapper(remap)
for (node in classes) {
val copy = ClassNode()
val adapter = ClassRemapper(copy, remapper)
node.accept(adapter)
classMap.remove(node.name)
classMap[node.name] = copy
}
}
fun getImplementations(target: ClassNode): List<ClassNode> {
return classes.stream().filter { cn -> cn.interfaces.contains(target.name) }.collect(Collectors.toList())
}
fun getExtensions(target: ClassNode): List<ClassNode> {
val extensions: MutableList<ClassNode> = mutableListOf()
classes.stream()
.filter { classNode -> classNode.superName == target.name }
.forEach { classNode ->
extensions.add(classNode)
extensions.addAll(getExtensions(classNode))
}
return extensions
}
fun getImplementations(target: ClassEntry): List<ClassEntry> {
val implementations = mutableListOf<ClassEntry>()
for (classNode in classes.stream().filter { classNode -> classNode.interfaces.contains(target.getName()) }) {
implementations.add(ClassPath.get(classNode))
implementations.addAll(getImplementations(ClassPath.get(classNode)))
}
return implementations
}
fun getExtensions(target: ClassEntry): List<ClassEntry> {
val extensions = mutableListOf<ClassEntry>()
classes.stream()
.filter { classNode -> classNode.superName == target.getName() }
.forEach { classNode ->
extensions.add(ClassPath.get(classNode))
extensions.addAll(getExtensions(ClassPath.get(classNode)))
}
return extensions
}
}

View File

@ -0,0 +1,18 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.api
enum class TransformerPriority {
HIGHEST, HIGH, NORMAL, LOW, LOWEST
}

View File

@ -0,0 +1,59 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.api.encryption
import java.security.SecureRandom
object Dictionary {
private val RANDOM = SecureRandom()
private val ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
private var num = 0
fun getNewName(): String {
return getInAlphabet(num++)
}
fun reset() {
num = 0
}
private fun getInAlphabet(i: Int): String {
return if (i < 0) "" else getInAlphabet(i / 52 - 1) + (toAscii(i % 52)).toChar()
}
private fun toAscii(i: Int): Int {
return if (i < 26) i + 97 else i + 39
}
fun genRandomString(): String {
val length = RANDOM.nextInt(15 - 5) + 5
val stringBuilder = StringBuilder()
for (i in 0 until length) {
val letter = RANDOM.nextInt(ALPHABET.length)
stringBuilder.append(ALPHABET[letter])
}
return stringBuilder.toString()
}
fun generateString(length: Int): String {
val length = length
val stringBuilder = StringBuilder()
for (i in 0 until length) {
val letter = RANDOM.nextInt(ALPHABET.length)
stringBuilder.append(ALPHABET[letter])
}
return stringBuilder.toString()
}
}

View File

@ -0,0 +1,243 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.api.insn
import org.objectweb.asm.Handle
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.Type
import org.objectweb.asm.tree.*
class InsnBuilder {
val insnList = InsnList()
operator fun InsnList.unaryPlus() = insnList.add(this)
operator fun AbstractInsnNode.unaryPlus() = insnList.add(this)
fun Int.insn() = InsnNode(this)
fun insn(opcode: Int) = +InsnNode(opcode)
fun nop() = insn(NOP)
fun aconst_null() = insn(ACONST_NULL)
fun ldc(int: Int) = +getIntInsn(int)
fun ldc(long: Long) = +getLongInsn(long)
fun ldc(float: Float) = +getFloatInsn(float)
fun ldc(double: Double) = +getDoubleInsn(double)
fun ldc(string: String) = +LdcInsnNode(string)
fun ldc(type: Type) = +LdcInsnNode(type)
fun ldc(handle: Handle) = +LdcInsnNode(handle)
fun istore(`var`: Int) = +VarInsnNode(ISTORE, `var`)
fun iload(`var`: Int) = +VarInsnNode(ILOAD, `var`)
fun lstore(`var`: Int) = +VarInsnNode(LSTORE, `var`)
fun lload(`var`: Int) = +VarInsnNode(LLOAD, `var`)
fun fstore(`var`: Int) = +VarInsnNode(FSTORE, `var`)
fun fload(`var`: Int) = +VarInsnNode(FLOAD, `var`)
fun dstore(`var`: Int) = +VarInsnNode(DSTORE, `var`)
fun dload(`var`: Int) = +VarInsnNode(DLOAD, `var`)
fun astore(`var`: Int) = +VarInsnNode(ASTORE, `var`)
fun aload(`var`: Int) = +VarInsnNode(ALOAD, `var`)
fun iastore() = insn(IASTORE)
fun iaload() = insn(IALOAD)
fun lastore() = insn(LASTORE)
fun laload() = insn(LALOAD)
fun fastore() = insn(FASTORE)
fun faload() = insn(FALOAD)
fun dastore() = insn(DASTORE)
fun daload() = insn(DALOAD)
fun aastore() = insn(AASTORE)
fun aaload() = insn(AALOAD)
fun bastore() = insn(BASTORE)
fun baload() = insn(BALOAD)
fun castore() = insn(CASTORE)
fun caload() = insn(CALOAD)
fun sastore() = insn(SASTORE)
fun saload() = insn(SALOAD)
fun pop() = insn(POP)
fun pop2() = insn(POP2)
fun dup() = insn(DUP)
fun dup_x1() = insn(DUP_X1)
fun dup_x2() = insn(DUP_X2)
fun dup2() = insn(DUP2)
fun dup2_x1() = insn(DUP2_X1)
fun dup2_x2() = insn(DUP2_X2)
fun swap() = insn(SWAP)
fun iadd() = insn(IADD)
fun isub() = insn(ISUB)
fun imul() = insn(IMUL)
fun idiv() = insn(IDIV)
fun irem() = insn(IREM)
fun ineg() = insn(INEG)
fun ishl() = insn(ISHL)
fun ishr() = insn(ISHR)
fun iushr() = insn(IUSHR)
fun iand() = insn(IAND)
fun ior() = insn(IOR)
fun ixor() = insn(IXOR)
fun iinc(`var`: Int, incr: Int) = +IincInsnNode(`var`, incr)
fun ladd() = insn(LADD)
fun lsub() = insn(LSUB)
fun lmul() = insn(LMUL)
fun ldiv() = insn(LDIV)
fun lrem() = insn(LREM)
fun lneg() = insn(LNEG)
fun lshl() = insn(LSHL)
fun lshr() = insn(LSHR)
fun lushr() = insn(LUSHR)
fun lor() = insn(LOR)
fun land() = insn(LAND)
fun lxor() = insn(LXOR)
fun fadd() = insn(FADD)
fun fsub() = insn(FSUB)
fun fmul() = insn(FMUL)
fun fdiv() = insn(FDIV)
fun frem() = insn(FREM)
fun fneg() = insn(FNEG)
fun dadd() = insn(DADD)
fun dsub() = insn(DSUB)
fun dmul() = insn(DMUL)
fun ddiv() = insn(DDIV)
fun drem() = insn(DREM)
fun dneg() = insn(DNEG)
fun i2l() = insn(I2L)
fun i2f() = insn(I2F)
fun i2d() = insn(I2D)
fun i2b() = insn(I2B)
fun i2c() = insn(I2C)
fun i2s() = insn(I2S)
fun l2i() = insn(L2I)
fun l2f() = insn(L2F)
fun l2d() = insn(L2D)
fun f2i() = insn(F2I)
fun f2l() = insn(F2L)
fun f2d() = insn(F2D)
fun d2i() = insn(D2I)
fun d2l() = insn(D2L)
fun d2f() = insn(D2F)
fun lcmp() = insn(LCMP)
fun fcmpl() = insn(FCMPL)
fun fcmpg() = insn(FCMPG)
fun dcmpl() = insn(DCMPL)
fun dcmpg() = insn(DCMPG)
fun goto(label: LabelNode) = +JumpInsnNode(GOTO, label)
fun jsr(label: LabelNode) = +JumpInsnNode(JSR, label)
fun ifeq(label: LabelNode) = +JumpInsnNode(IFEQ, label)
fun ifne(label: LabelNode) = +JumpInsnNode(IFNE, label)
fun iflt(label: LabelNode) = +JumpInsnNode(IFLT, label)
fun ifle(label: LabelNode) = +JumpInsnNode(IFLE, label)
fun ifge(label: LabelNode) = +JumpInsnNode(IFGE, label)
fun ifgt(label: LabelNode) = +JumpInsnNode(IFGT, label)
fun if_icmplt(label: LabelNode) = +JumpInsnNode(IF_ICMPLT, label)
fun if_icmple(label: LabelNode) = +JumpInsnNode(IF_ICMPLE, label)
fun if_icmpge(label: LabelNode) = +JumpInsnNode(IF_ICMPGE, label)
fun if_icmpgt(label: LabelNode) = +JumpInsnNode(IF_ICMPGT, label)
fun if_icmpeq(label: LabelNode) = +JumpInsnNode(IF_ICMPEQ, label)
fun if_icmpne(label: LabelNode) = +JumpInsnNode(IF_ICMPNE, label)
fun if_acmpeq(label: LabelNode) = +JumpInsnNode(IF_ACMPEQ, label)
fun ifnull(label: LabelNode) = +JumpInsnNode(IFNULL, label)
fun ifnonnull(label: LabelNode) = +JumpInsnNode(IFNONNULL, label)
fun lookupswitch(defaultLabel: LabelNode, lookup: Pair<IntArray, Array<LabelNode>>) =
+LookupSwitchInsnNode(defaultLabel, lookup.first, lookup.second)
fun getstatic(owner: String, name: String, desc: String) = +FieldInsnNode(GETSTATIC, owner, name, desc)
fun putstatic(owner: String, name: String, desc: String) = +FieldInsnNode(PUTSTATIC, owner, name, desc)
fun getfield(owner: String, name: String, desc: String) = +FieldInsnNode(GETFIELD, owner, name, desc)
fun putfield(owner: String, name: String, desc: String) = +FieldInsnNode(PUTFIELD, owner, name, desc)
fun invokevirtual(owner: String, name: String, desc: String, `interface`: Boolean = false) =
+MethodInsnNode(INVOKEVIRTUAL, owner, name, desc, `interface`)
fun invokespecial(owner: String, name: String, desc: String, `interface`: Boolean = false) =
+MethodInsnNode(INVOKESPECIAL, owner, name, desc, `interface`)
fun invokestatic(owner: String, name: String, desc: String, `interface`: Boolean = false) =
+MethodInsnNode(INVOKESTATIC, owner, name, desc, `interface`)
fun invokeinterface(owner: String, name: String, desc: String, `interface`: Boolean = false) =
+MethodInsnNode(INVOKEINTERFACE, owner, name, desc, `interface`)
fun new(type: String) = +TypeInsnNode(NEW, type)
fun newarray(type: Int) = +IntInsnNode(NEWARRAY, type)
fun anewarray(desc: String) = +TypeInsnNode(ANEWARRAY, desc)
fun newboolarray() = newarray(T_BOOLEAN)
fun newchararray() = newarray(T_CHAR)
fun newbytearray() = newarray(T_BYTE)
fun newshortarray() = newarray(T_SHORT)
fun newintarray() = newarray(T_INT)
fun newlongarray() = newarray(T_LONG)
fun newfloatarray() = newarray(T_FLOAT)
fun newdoublearray() = newarray(T_DOUBLE)
fun arraylength() = insn(ARRAYLENGTH)
fun athrow() = insn(ATHROW)
fun checkcast(descriptor: String) = +TypeInsnNode(CHECKCAST, descriptor)
fun instanceof(descriptor: String) = +TypeInsnNode(INSTANCEOF, descriptor)
fun ireturn() = insn(IRETURN)
fun lreturn() = insn(LRETURN)
fun freturn() = insn(FRETURN)
fun dreturn() = insn(DRETURN)
fun areturn() = insn(ARETURN)
fun _return() = insn(RETURN)
fun frame(type: Int, numLocal: Int, local: Array<Any>?, numStack: Int, stack: Array<Any>?) =
+FrameNode(type, numLocal, local, numStack, stack)
fun getIntInsn(value: Int) =
when (value) {
in -1..5 -> InsnNode(value + 3)
in Byte.MIN_VALUE..Byte.MAX_VALUE -> IntInsnNode(BIPUSH, value)
in Short.MIN_VALUE..Short.MAX_VALUE -> IntInsnNode(SIPUSH, value)
else -> LdcInsnNode(value)
}
fun getLongInsn(value: Long) =
when (value) {
in 0..1 -> InsnNode((value + 9).toInt())
else -> LdcInsnNode(value)
}
fun getFloatInsn(value: Float) =
when {
value % 1 == 0f && value in 0f..2f -> InsnNode((value + 11).toInt())
else -> LdcInsnNode(value)
}
fun getDoubleInsn(value: Double) =
when {
value % 1 == 0.0 && value in 0.0..1.0 -> InsnNode((value + 14).toInt())
else -> LdcInsnNode(value)
}
}
fun insnBuilder(builder: InsnBuilder.() -> Unit) = InsnBuilder().also(builder).insnList

View File

@ -0,0 +1,72 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.api.interfaces
import dev.zprotect.obfuscator.Obfuscator
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodNode
import java.io.ByteArrayOutputStream
import java.io.InputStream
interface IBytecode {
companion object : IBytecode
fun isExcluded(name: String): Boolean {
val path = getPath(name)
for (exclusion in Obfuscator.getExclusions()) {
if (path.contains(exclusion)) return true
}
return false
}
fun getPath(name: String): String {
if (!name.contains("/")) return ""
val reversedString = name.reversed()
val path = reversedString.substring(reversedString.indexOf("/"))
return path.reversed()
}
fun getMethod(classNode: ClassNode, method: String): MethodNode? {
classNode.methods.forEach { methodNode ->
if (methodNode.name == method) return methodNode
}
return null
}
fun readBytes(inputStream: InputStream): ByteArray {
val bytes: ByteArray
val byteArrayOutputStream = ByteArrayOutputStream()
val buf = ByteArray(256)
var n: Int
while (inputStream.read(buf).also { n = it } != -1) byteArrayOutputStream.write(buf, 0, n)
bytes = byteArrayOutputStream.toByteArray()
return bytes
}
fun writeBytes(classNode: ClassNode): ByteArray {
val writer = ClassWriter(ClassWriter.COMPUTE_MAXS)
writer.newUTF8("Obfuscated with zProtect, visit https://zprotect.dev for more information.")
classNode.accept(writer)
return writer.toByteArray()
}
fun toByteArrayDefault(classNode: ClassNode): ByteArray? {
val classWriter = ClassWriter(ClassWriter.COMPUTE_MAXS)
classNode.accept(classWriter)
return classWriter.toByteArray()
}
}

View File

@ -0,0 +1,64 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.api.interfaces
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.InsnNode
import org.objectweb.asm.tree.IntInsnNode
import org.objectweb.asm.tree.LdcInsnNode
interface INode {
companion object : INode
fun getLdcInt(int: Int): AbstractInsnNode {
if (int <= 32767 && int >= -32768) {
return IntInsnNode(SIPUSH, int)
} else if (int <= 127 && int >= -128) {
return IntInsnNode(BIPUSH, int)
}
return when (int) {
-1 -> InsnNode(ICONST_M1)
0 -> InsnNode(ICONST_0)
1 -> InsnNode(ICONST_1)
2 -> InsnNode(ICONST_2)
3 -> InsnNode(ICONST_3)
4 -> InsnNode(ICONST_4)
5 -> InsnNode(ICONST_5)
else -> LdcInsnNode(int)
}
}
fun getIntFromAin(ain: AbstractInsnNode): Int {
return when (ain.opcode) {
ICONST_M1 -> -1
ICONST_0 -> 0
ICONST_1 -> 1
ICONST_2 -> 2
ICONST_3 -> 3
ICONST_4 -> 4
ICONST_5 -> 5
SIPUSH -> (ain as IntInsnNode).operand
BIPUSH -> (ain as IntInsnNode).operand
LDC -> (ain as LdcInsnNode).cst as Int
else -> -2
}
}
fun isReturn(ain: AbstractInsnNode): Boolean {
return ain.opcode in IRETURN..RETURN
}
}

View File

@ -0,0 +1,62 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.api.settings
import org.json.JSONArray
import org.json.JSONObject
import org.json.JSONTokener
import java.io.File
import java.io.FileInputStream
open class Config<T>(private val name: String, private val type: Type, private val read: (JSONObject) -> T) {
var value: T? = null
companion object {
private lateinit var READ_OBJECT: JSONObject
private val TO_READ: ArrayList<Config<*>> = arrayListOf()
private val IS_INITIALIZED: Boolean
get() = ::READ_OBJECT.isInitialized
fun read(file: File?) {
READ_OBJECT = JSONObject(JSONTokener(FileInputStream(file)))
TO_READ.forEach { it.read(READ_OBJECT) }
}
}
init {
if (IS_INITIALIZED) read(READ_OBJECT)
else TO_READ.add(this)
}
private fun read(jsonObject: JSONObject) {
value = if (jsonObject.has(name)) read.invoke(jsonObject) else null
}
enum class Type {
ARRAY, STRING, BOOLEAN
}
}
class ArrayConfig(name: String?) : Config<JSONArray?>(name!!, Type.ARRAY, {
it.getJSONArray(name)
})
class StringConfig(name: String?) : Config<String?>(name!!, Type.STRING, {
it.getString(name)
})
class BooleanConfig(name: String?) : Config<Boolean?>(name!!, Type.BOOLEAN, {
it.getBoolean(name)
})

View File

@ -0,0 +1,102 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.antitamper
import dev.zprotect.obfuscator.api.Transformer
import dev.zprotect.obfuscator.api.encryption.Dictionary
import dev.zprotect.obfuscator.api.insn.insnBuilder
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.tree.LabelNode
import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.MethodNode
object AntiDebug : Transformer("AntiDebug", "Blocks debugging options on terminal.") {
private val debugTypes = arrayOf("-Xbootclasspath", "-Xdebug", "-agentlib", "-Xrunjdwp:", /*"-verbose"*/)
override fun obfuscate() {
// Have fun. :)
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
classNode.methods.find { it.name.equals("<clinit>") }.also {
val methodNode = makeMethod()
it?.instructions?.insertBefore(
it.instructions.last,
MethodInsnNode(INVOKESTATIC, classNode.name, methodNode.name, methodNode.desc, false)
)
classNode.methods.add(methodNode)
}
}
}
// TODO: Fix crashing with minecraft... ???
private fun makeMethod(): MethodNode {
val method = MethodNode()
with(method) {
access = ACC_PRIVATE or ACC_STATIC
name = Dictionary.generateString(8)
desc = "()V"
signature = null
exceptions = null
maxStack = 2
instructions = insnBuilder {
invokestatic(
"java/lang/management/ManagementFactory",
"getRuntimeMXBean",
"()Ljava/lang/management/RuntimeMXBean;",
false
)
invokeinterface("java/lang/management/RuntimeMXBean", "getInputArguments", "()Ljava/util/List;", true)
invokeinterface("java/util/List", "iterator", "()Ljava/util/Iterator;", true)
astore(1)
val label1 = LabelNode()
+label1
frame(F_APPEND, 1, arrayOf("java/util/Iterator"), 0, null)
aload(1)
invokeinterface("java/util/Iterator", "hasNext", "()Z", true)
val label2 = LabelNode()
ifeq(label2)
aload(1)
invokeinterface("java/util/Iterator", "next", "()Ljava/lang/Object;", true)
checkcast("java/lang/String")
astore(2)
+LabelNode()
aload(2)
ldc("-javaagent:") // -agentpath when?
invokevirtual("java/lang/String", "startsWith", "(Ljava/lang/String;)Z", false)
val label4 = LabelNode()
debugTypes.forEach {
ifne(label4)
aload(2)
ldc(it)
invokevirtual("java/lang/String", "startsWith", "(Ljava/lang/String;)Z", false)
}
val label5 = LabelNode()
ifeq(label5)
+label4
frame(F_APPEND, 1, arrayOf("java/lang/String"), 0, null)
insn(ICONST_0)
invokestatic("java/lang/System", "exit", "(I)V", false)
+label5
frame(F_CHOP, 1, null, 0, null)
goto(label1)
+label2
frame(F_CHOP, 1, null, 0, null)
_return()
}
}
return method
}
}

View File

@ -0,0 +1,61 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.decompiler
import dev.zprotect.obfuscator.api.Transformer
import joptsimple.internal.Strings
import org.objectweb.asm.tree.AnnotationNode
object BadAnnotationCrasher :
Transformer(
"BadAnnotationCrasher",
"Generates invisible annotations which will cause Procyon to be very slow."
) {
private val string: String = Strings.repeat('\n', 40) // I think making this any higher is a bad idea...
override fun obfuscate() {
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
if (classNode.invisibleAnnotations == null)
classNode.invisibleAnnotations = arrayListOf()
classNode.invisibleAnnotations.add(getAnnotationNode())
classNode.fields.forEach { fieldNode ->
if (fieldNode.invisibleAnnotations == null) {
fieldNode.invisibleAnnotations = arrayListOf()
fieldNode.invisibleAnnotations.add(getAnnotationNode())
}
}
classNode.methods.forEach { methodNode ->
if (methodNode.invisibleAnnotations == null) {
methodNode.invisibleAnnotations = arrayListOf()
methodNode.invisibleAnnotations.add(getAnnotationNode())
}
}
}
}
private fun getAnnotationNode(): AnnotationNode? {
return AnnotationNode(string)
}
}

View File

@ -0,0 +1,55 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.decompiler
import dev.zprotect.obfuscator.api.Transformer
import org.objectweb.asm.Handle
import org.objectweb.asm.Label
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.*
/* Use noverify for now, I would suggest not using this transformer and use
BadAnnotationCrasher instead even though it's weaker and more stupid.
*/
object DecompilerCrasher : Transformer("DecompilerCrasher", "Fucks the decompiler.") {
override fun obfuscate() {
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
// Just don't code in minecraft...
//if (classNode.name.contains("Mixin")) return@forEach
classNode.methods.filterNot { it.name == "<clinit>" }.forEach { method ->
if (method.instructions.first != null) {
val label = LabelNode(Label())
val insnList = InsnList().apply {
//add(emitIntPush(0))
add(JumpInsnNode(Opcodes.IFNE, label))
add(InvokeDynamicInsnNode("i am sad your java got yeeted", "()V", Handle(6, "a", "a", "(IIIIIIIIIIIIIIIIIII)I")))
add(InsnNode(Opcodes.ACONST_NULL))
add(TypeInsnNode(Opcodes.CHECKCAST, "give up and buy zprotect"))
// TODO: Push to stack lol
add(InsnNode(Opcodes.LDC)) // prob do trick idk
// if you push to stack the bytecode would probably be kinda valid ngl lmao
//add(InsnNode(Opcodes.POP)) Causes crash when you run the program...
add(label)
}
method.instructions.insertBefore(method.instructions.first, insnList)
}
}
}
}
}

View File

@ -0,0 +1,57 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.flow
import dev.zprotect.obfuscator.api.Transformer
import dev.zprotect.obfuscator.api.encryption.Dictionary
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.tree.FieldInsnNode
import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.InsnNode
import org.objectweb.asm.tree.JumpInsnNode
object Flow : Transformer("Flow", "Adds fake jumps, and such to code.") {
override fun obfuscate() {
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
var hasField = false
val fieldName = Dictionary.generateString(8)
classNode.methods.forEach { methodNode ->
methodNode.instructions.filter { it.opcode == GOTO }.forEach {
hasField = true
with(methodNode) {
instructions.insertBefore(it, FieldInsnNode(GETSTATIC, classNode.name, fieldName, "Z"))
instructions.insert(it, InsnNode(ATHROW))
instructions.insert(it, InsnNode(ACONST_NULL))
instructions.set(it, JumpInsnNode(IFEQ, (it as JumpInsnNode).label))
}
}
}
checkCondition(hasField) {
classNode.fields.add(FieldNode(ACC_PUBLIC + ACC_STATIC + ACC_FINAL, fieldName, "Z", null, null))
}
}
}
fun checkCondition(condition: Boolean, runnable: Runnable) {
if (!condition) {
return
}
runnable.run()
}
}

View File

@ -0,0 +1,42 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.naming
import dev.zprotect.obfuscator.api.Transformer
import dev.zprotect.obfuscator.api.encryption.Dictionary
import dev.zprotect.obfuscator.api.settings.StringConfig
object ClassRenamer : Transformer("ClassRenamer", "Renames class names.") {
private val path = StringConfig("ClassRenamerPath")
override fun obfuscate() {
val remap: MutableMap<String?, String?> = HashMap()
val prefix = if (path.value == null) "" else path.value
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
val name = prefix + Dictionary.getNewName()
remap[classNode.name] = name
if (classNode.name.replace("/", ".") == manifest.mainAttributes.getValue("Main-Class"))
manifest.mainAttributes.putValue("Main-Class", name.replace("/", "."))
}
applyRemap(remap)
Dictionary.reset()
}
}

View File

@ -0,0 +1,57 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.naming
import dev.zprotect.obfuscator.api.ClassEntry
import dev.zprotect.obfuscator.api.ClassPath
import dev.zprotect.obfuscator.api.Transformer
import dev.zprotect.obfuscator.api.encryption.Dictionary
import org.objectweb.asm.tree.FieldNode
import java.util.*
// TODO: Choose if we want to use the regular Dictionary or use another format.
object FieldRenamer : Transformer("FieldRenamer", "Renames field names.") {
override fun obfuscate() {
val remap: MutableMap<String?, String?> = mutableMapOf()
val fieldMap: MutableMap<FieldNode, ClassEntry> = mutableMapOf()
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
classNode.fields
.forEach { fieldNode -> fieldMap[fieldNode] = ClassPath.get(classNode) }
}
for ((fieldNode, owner) in fieldMap.entries) {
val name = Dictionary.getNewName()
val stack = Stack<ClassEntry>()
stack.add(owner)
while (!stack.empty()) {
val classEntry = stack.pop()
remap[classEntry.getName() + "." + fieldNode.name] = name
stack.addAll(getExtensions(classEntry))
stack.addAll(getImplementations(classEntry))
}
}
applyRemap(remap)
Dictionary.reset()
}
}

View File

@ -0,0 +1,49 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.naming
import dev.zprotect.obfuscator.api.Transformer
import dev.zprotect.obfuscator.api.encryption.Dictionary
import org.objectweb.asm.tree.LocalVariableNode
object LocalVariableRenamer : Transformer("LocalVariableRenamer", "Obfuscates local variables.") {
var hashMap = HashMap<String, Boolean>()
override fun obfuscate() {
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
classNode.methods.stream()
.forEach { methodNode ->
//if (methodNode.localVariables == null) return@forEach
for (i in 0 until methodNode.localVariables.size) {
val localVariableNode: LocalVariableNode = methodNode.localVariables.get(i)
methodNode.localVariables.set(
i,
LocalVariableNode(
Dictionary.getNewName(),
localVariableNode.desc,
null,
localVariableNode.start,
localVariableNode.end,
i
)
)
hashMap[localVariableNode.desc] = java.lang.Boolean.TRUE
}
}
}
}
}

View File

@ -0,0 +1,80 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.naming
import dev.zprotect.obfuscator.api.ClassEntry
import dev.zprotect.obfuscator.api.ClassPath
import dev.zprotect.obfuscator.api.Transformer
import dev.zprotect.obfuscator.api.encryption.Dictionary
import dev.zprotect.obfuscator.hasAccess
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.tree.MethodNode
import java.util.*
object MethodRenamer : Transformer("MethodRenamer", "Renames method names.") {
private val badNames = arrayListOf("main")
override fun obfuscate() {
val remap: MutableMap<String?, String?> = mutableMapOf()
val methodMap: MutableMap<MethodNode, ClassEntry> = mutableMapOf()
// bruh classNode.access and ACC_ANNOTATION == 0 && classNode.access and ACC_ENUM == 0 && classNode.access and ACC_ABSTRACT == 0
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) && classNode.hasAccess(ACC_ANNOTATION or ACC_ENUM or ACC_ABSTRACT) }
.forEach { classNode ->
classNode.methods.stream()
.filter { methodNode -> !badNames.contains(methodNode.name) && !methodNode.name.startsWith("<") && methodNode.hasAccess(ACC_NATIVE) }
.forEach { methodNode -> methodMap[methodNode] = ClassPath.get(classNode) }
}
methods@
for ((methodNode, owner) in methodMap.entries) {
val stack = Stack<ClassEntry>()
stack.add(owner)
while (stack.isNotEmpty()) {
val classEntry = stack.pop()
if (classEntry != owner && classEntry.getMethods()
.findLast { method -> method.name == methodNode.name && method.desc == methodNode.desc } != null
)
continue@methods
val parent = if (classEntry.getSuper() == null) null else ClassPath[classEntry.getSuper()]
if (parent != null) stack.push(parent)
classEntry.getInterfaces().forEach { inter: String ->
val interfNode = ClassPath[inter]
if (interfNode != null) stack.push(interfNode)
}
}
val name = Dictionary.getNewName()
stack.add(owner)
while (stack.isNotEmpty()) {
val classEntry = stack.pop()
remap[classEntry.getName() + "." + methodNode.name + methodNode.desc] = name
stack.addAll(getExtensions(classEntry))
stack.addAll(getImplementations(classEntry))
}
}
applyRemap(remap)
Dictionary.reset()
}
}

View File

@ -0,0 +1,55 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.optimization
import dev.zprotect.obfuscator.api.Transformer
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.tree.MethodInsnNode
object EnumOptimiser : Transformer("EnumOptimiser", "Removes clone call and return array.") {
override fun obfuscate() {
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
if (classNode.access.hasAccess(ACC_ENUM)) {
val desc = "[L${classNode.name};"
val valuesMethod = classNode.methods.firstOrNull {
it.name == "values"
&&
it.desc == "()$desc"
&&
it.instructions.size() >= 4
} ?: return@forEach
for (insnNode in valuesMethod.instructions) {
if (insnNode is MethodInsnNode) {
if (
insnNode.opcode == INVOKEVIRTUAL
&&
insnNode.name == "clone"
) {
if (insnNode.next.opcode == CHECKCAST) {
valuesMethod.instructions.remove(insnNode.next)
}
valuesMethod.instructions.remove(insnNode)
}
}
}
}
}
}
private fun Int.hasAccess(access: Int) = this and access != 0
}

View File

@ -0,0 +1,33 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.optimization
import dev.zprotect.obfuscator.api.Transformer
import org.objectweb.asm.Opcodes.ACC_FINAL
import org.objectweb.asm.tree.FieldNode
object FinalRemover : Transformer("FinalRemover", "Removes final.") {
override fun obfuscate() {
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
for (i in 0 until classNode.fields.size) {
val fieldNode: FieldNode = classNode.fields.get(i)
if (fieldNode.access or ACC_FINAL !== 0) fieldNode.access = fieldNode.access and ACC_FINAL.inv()
classNode.fields.set(i, fieldNode)
}
}
}
}

View File

@ -0,0 +1,45 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.optimization
import dev.zprotect.obfuscator.api.Transformer
import org.objectweb.asm.Opcodes
//private const val ANNOTATION_REF = "java/lang/annotation/Annotation"
object HideClassMembers : Transformer("HideClassMembers", "Mark classes as synthetic to hide them from bad decompilers.") {
override fun obfuscate() {
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
/*if (!classNode.editable) return
classNode.modified = true
if (!classNode.dependencies.contains(ANNOTATION_REF)) {
classNode.access = classNode.access or Opcodes.ACC_SYNTHETIC
}*/
classNode.methods.forEach { methodNode ->
methodNode.access = methodNode.access or Opcodes.ACC_SYNTHETIC or Opcodes.ACC_BRIDGE
}
classNode.fields.forEach { fieldNode ->
fieldNode.access = fieldNode.access or Opcodes.ACC_SYNTHETIC
}
}
}
}

View File

@ -0,0 +1,100 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.optimization
import dev.zprotect.obfuscator.api.Transformer
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.*
object InsnRemover : Transformer("InsnRemover", "Removes the instructions const_. and tableswitch.") {
override fun obfuscate() {
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
classNode.methods.forEach { methodNode ->
val newInsnSet = InsnList()
insn@ for (abstractInsnNode in methodNode.instructions.toArray()) {
if (abstractInsnNode is TableSwitchInsnNode) {
val labelNodeStart = LabelNode()
val clearStack = InsnNode(Opcodes.POP)
val jumpInsnNodeStart = JumpInsnNode(Opcodes.GOTO, abstractInsnNode.dflt)
val labelNodeEnd = LabelNode()
val jumpInsnNodeEnd = JumpInsnNode(Opcodes.GOTO, labelNodeEnd)
newInsnSet.add(jumpInsnNodeEnd)
newInsnSet.add(labelNodeStart)
newInsnSet.add(clearStack)
newInsnSet.add(jumpInsnNodeStart)
newInsnSet.add(labelNodeEnd)
newInsnSet.add(InsnNode(Opcodes.DUP))
newInsnSet.add(LdcInsnNode(-abstractInsnNode.min))
newInsnSet.add(InsnNode(Opcodes.IADD))
newInsnSet.add(JumpInsnNode(Opcodes.IFLT, labelNodeStart))
newInsnSet.add(InsnNode(Opcodes.DUP))
newInsnSet.add(LdcInsnNode(-abstractInsnNode.max))
newInsnSet.add(InsnNode(Opcodes.IADD))
newInsnSet.add(JumpInsnNode(Opcodes.IFGT, labelNodeStart))
newInsnSet.add(InsnNode(Opcodes.DUP))
newInsnSet.add(LdcInsnNode(-abstractInsnNode.min))
newInsnSet.add(InsnNode(Opcodes.IADD))
var labelIndex = 0
for (label in abstractInsnNode.labels) {
val nextBranch = LabelNode()
newInsnSet.add(InsnNode(Opcodes.DUP))
newInsnSet.add(JumpInsnNode(Opcodes.IFNE, nextBranch))
newInsnSet.add(InsnNode(Opcodes.POP))
newInsnSet.add(InsnNode(Opcodes.POP))
newInsnSet.add(JumpInsnNode(Opcodes.GOTO, label))
newInsnSet.add(nextBranch)
if (labelIndex + 1 != abstractInsnNode.labels.size) {
newInsnSet.add(LdcInsnNode(-1))
newInsnSet.add(InsnNode(Opcodes.IADD))
}
labelIndex++
}
newInsnSet.add(InsnNode(Opcodes.POP))
newInsnSet.add(JumpInsnNode(Opcodes.GOTO, labelNodeStart))
} else {
when (abstractInsnNode.opcode) {
Opcodes.ICONST_M1, Opcodes.ICONST_0, Opcodes.ICONST_1, Opcodes.ICONST_2, Opcodes.ICONST_3, Opcodes.ICONST_4, Opcodes.ICONST_5 -> {
newInsnSet.add(LdcInsnNode(abstractInsnNode.opcode - 3))
continue@insn
}
Opcodes.LCONST_0, Opcodes.LCONST_1 -> {
newInsnSet.add(LdcInsnNode(abstractInsnNode.opcode - 9L))
continue@insn
}
Opcodes.FCONST_0, Opcodes.FCONST_1, Opcodes.FCONST_2 -> {
newInsnSet.add(LdcInsnNode(abstractInsnNode.opcode - 11f))
continue@insn
}
Opcodes.DCONST_0, Opcodes.DCONST_1 -> {
newInsnSet.add(LdcInsnNode(abstractInsnNode.opcode - 14.0))
continue@insn
}
}
}
newInsnSet.add(abstractInsnNode)
}
methodNode.instructions = newInsnSet
}
}
}
}

View File

@ -0,0 +1,61 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.optimization
import dev.zprotect.obfuscator.api.Transformer
object KotlinMetadataRemover : Transformer("KotlinMetadataRemover", "Removes Kotlin Metadata.") {
override fun obfuscate() {
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
if (classNode.visibleAnnotations != null) {
classNode.visibleAnnotations =
classNode.visibleAnnotations.filter { it.desc != "Lkotlin/Metadata;" }
}
val annotations = arrayOf(
"Lorg/jetbrains/annotations/NotNull;",
"Lorg/jetbrains/annotations/JvmName;",
"Lorg/jetbrains/annotations/Nullable;"
)
classNode.methods.forEach { method ->
if (method.invisibleAnnotations != null) {
method.invisibleAnnotations =
method.invisibleAnnotations.filterNot { annotations.contains(it.desc) }
}
if (method.visibleAnnotations != null) {
method.visibleAnnotations =
method.visibleAnnotations.filterNot { annotations.contains(it.desc) }
}
}
classNode.fields.forEach { field ->
if (field.invisibleAnnotations != null) {
field.invisibleAnnotations =
field.invisibleAnnotations.filterNot { annotations.contains(it.desc) }
}
if (field.visibleAnnotations != null) {
field.visibleAnnotations = field.visibleAnnotations.filterNot { annotations.contains(it.desc) }
}
}
}
}
}

View File

@ -0,0 +1,31 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.optimization
import dev.zprotect.obfuscator.api.Transformer
import org.objectweb.asm.Opcodes.NOP
object NOPInsnRemover : Transformer("NOPInsnRemover", "Removes extended type information.") {
override fun obfuscate() {
classes.stream()
.filter { cn -> !isExcluded(cn.name) }
.forEach { cn ->
cn.methods.forEach { mn ->
mn.instructions.removeAll { ain -> ain.opcode == NOP }
}
}
}
}

View File

@ -0,0 +1,30 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.optimization
import dev.zprotect.obfuscator.api.Transformer
object RemoveSignatures : Transformer("RemoveSignatures", "Removes extended type information.") {
override fun obfuscate() {
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
classNode.signature = null
classNode.methods.forEach { methodNode ->
methodNode.signature = null
}
}
}
}

View File

@ -0,0 +1,101 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.poolers
import dev.zprotect.obfuscator.api.Transformer
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.*
object NumberPooler : Transformer("NumberPooler", "Moves numbers into an array.") {
private const val numberPoolerArray = "¤¶§†!|~ÇüéäçêïîìæÆ¢£¥Pƒáíúñ¿¬½¼¡«»¦ßµ±°•·²€„…‡‰ŠŒ˜™šœŸ¨©®¯³´¸¹¾Ð×ØÞãðõ÷øüþ"
override fun obfuscate() {
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
val numberMap: MutableMap<AbstractInsnNode, MethodNode> = mutableMapOf()
classNode.methods.forEach { methodNode ->
methodNode.instructions.forEach { abstractInsnNode ->
if ((abstractInsnNode is LdcInsnNode && abstractInsnNode.cst is Int) || (abstractInsnNode.opcode == Opcodes.BIPUSH || abstractInsnNode.opcode == Opcodes.SIPUSH)
|| (abstractInsnNode.opcode in Opcodes.ICONST_M1..Opcodes.ICONST_5)
) {
numberMap[abstractInsnNode] = methodNode
}
}
}
if (numberMap.isNotEmpty()) {
classNode.fields.add(
FieldNode(
(if (classNode.access and Opcodes.ACC_INTERFACE != 0) Opcodes.ACC_PUBLIC else Opcodes.ACC_PRIVATE) or (if (classNode.version > Opcodes.V1_8) 0 else Opcodes.ACC_FINAL) or Opcodes.ACC_STATIC,
numberPoolerArray,
"[I",
null,
null
)
)
var clinit = getMethod(classNode, "<clinit>")
if (clinit == null) {
clinit = MethodNode(Opcodes.ACC_STATIC, "<clinit>", "()V", null, arrayOf<String>())
classNode.methods.add(clinit)
}
if (clinit.instructions == null) clinit.instructions = InsnList()
val arrayInstructions = InsnList().apply {
add(getLdcInt(numberMap.size))
add(IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_INT))
for ((index, abstractInsnNode) in numberMap.keys.withIndex()) {
add(InsnNode(Opcodes.DUP))
add(getLdcInt(index))
add(getLdcInt(getIntFromAin(abstractInsnNode)))
add(InsnNode(Opcodes.IASTORE))
numberMap[abstractInsnNode]!!.instructions.insert(abstractInsnNode, InsnList().apply {
add(
FieldInsnNode(
Opcodes.GETSTATIC,
classNode.name,
numberPoolerArray,
"[I"
)
)
add(getLdcInt(index))
add(InsnNode(Opcodes.IALOAD))
})
numberMap[abstractInsnNode]!!.instructions.remove(abstractInsnNode)
}
add(
FieldInsnNode(
Opcodes.PUTSTATIC,
classNode.name,
numberPoolerArray,
"[I"
)
)
}
if (clinit.instructions == null || clinit.instructions.first == null) {
clinit.instructions.add(arrayInstructions)
clinit.instructions.add(InsnNode(Opcodes.RETURN))
} else {
clinit.instructions.insertBefore(clinit.instructions.first, arrayInstructions)
}
}
}
}
}

View File

@ -0,0 +1,97 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.poolers
import dev.zprotect.obfuscator.api.Transformer
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.*
object StringPooler : Transformer("StringPooler", "Moves strings into an array.") {
private const val stringPoolerArray = "¤¶§†!|~ÇüéäçêïîìæÆ¢£¥Pƒáíúñ¿¬½¼¡«»¦ßµ±°•·²€„…‡‰ŠŒ˜™šœŸ¨©®¯³´¸¹¾Ð×ØÞãðõ÷øüþp"
override fun obfuscate() {
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
val ldcInsnNodeMap: MutableMap<LdcInsnNode, MethodNode> = mutableMapOf()
classNode.methods.forEach { methodNode ->
methodNode.instructions.forEach { abstractInsnNode ->
if (abstractInsnNode is LdcInsnNode && abstractInsnNode.cst is String) ldcInsnNodeMap[abstractInsnNode] = methodNode
}
}
if (ldcInsnNodeMap.isNotEmpty()) {
classNode.fields.add(
FieldNode(
(if (classNode.access and Opcodes.ACC_INTERFACE != 0) Opcodes.ACC_PUBLIC else Opcodes.ACC_PRIVATE) or (if (classNode.version > Opcodes.V1_8) 0 else Opcodes.ACC_FINAL) or Opcodes.ACC_STATIC,
stringPoolerArray,
"[Ljava/lang/String;",
null,
null
)
)
var clinit = getMethod(classNode, "<clinit>")
if (clinit == null) {
clinit = MethodNode(Opcodes.ACC_STATIC, "<clinit>", "()V", null, arrayOf<String>())
classNode.methods.add(clinit)
}
if (clinit.instructions == null) clinit.instructions = InsnList()
val arrayInstructions = InsnList().apply {
add(getLdcInt(ldcInsnNodeMap.size)) // set array length
add(TypeInsnNode(Opcodes.ANEWARRAY, "java/lang/String")) // create array
for ((index, ldc) in ldcInsnNodeMap.keys.withIndex()) {
add(InsnNode(Opcodes.DUP))
add(getLdcInt(index))
add(LdcInsnNode(ldc.cst as String))
add(InsnNode(Opcodes.AASTORE))
ldcInsnNodeMap[ldc]!!.instructions.insert(ldc, InsnList().apply {
add(
FieldInsnNode(
Opcodes.GETSTATIC,
classNode.name,
stringPoolerArray,
"[Ljava/lang/String;"
)
)
add(getLdcInt(index))
add(InsnNode(Opcodes.AALOAD))
})
ldcInsnNodeMap[ldc]!!.instructions.remove(ldc)
}
add(
FieldInsnNode(
Opcodes.PUTSTATIC,
classNode.name,
stringPoolerArray,
"[Ljava/lang/String;"
)
)
}
if (clinit.instructions == null || clinit.instructions.first == null) {
clinit.instructions.add(arrayInstructions)
clinit.instructions.add(InsnNode(Opcodes.RETURN))
} else {
clinit.instructions.insertBefore(clinit.instructions.first, arrayInstructions)
}
}
}
}
}

View File

@ -0,0 +1,29 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.shrinking
import dev.zprotect.obfuscator.api.Transformer
import org.objectweb.asm.tree.LineNumberNode
object LineNumberRemover : Transformer("LineNumberRemover", "Removes line numbers so StackTraces show (Unknown) on errors.") {
override fun obfuscate() {
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
classNode.methods.forEach { methodNode ->
methodNode.instructions.removeAll { abstractInsnNode -> abstractInsnNode is LineNumberNode }
}
}
}
}

View File

@ -0,0 +1,32 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.shrinking
import dev.zprotect.obfuscator.api.Transformer
object LocalVariableRemover : Transformer("LocalVariableRemover", "Removes local variables.") {
override fun obfuscate() {
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
classNode.methods.forEach { methodNode ->
if (methodNode.localVariables != null) {
methodNode.localVariables = null
}
}
}
}
}

View File

@ -0,0 +1,30 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.shrinking
import dev.zprotect.obfuscator.api.Transformer
object RemoveInnerClasses : Transformer("RemoveInnerClasses", "Removes inner classes.") {
override fun obfuscate() {
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
classNode.outerClass = null
classNode.outerMethod = null
classNode.outerMethodDesc = null
classNode.innerClasses.clear()
}
}
}

View File

@ -0,0 +1,27 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.shrinking
import dev.zprotect.obfuscator.api.Transformer
object SourceDebugRemover : Transformer("SourceDebugRemover", "Removes the SourceDebug attribute.") {
override fun obfuscate() {
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
classNode.sourceDebug = null
}
}
}

View File

@ -0,0 +1,27 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.shrinking
import dev.zprotect.obfuscator.api.Transformer
object SourceFileRemover : Transformer("SourceFileRemover", "Removes the SourceFile attribute.") {
override fun obfuscate() {
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
classNode.sourceFile = null
}
}
}

View File

@ -0,0 +1,27 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.shufflers
import dev.zprotect.obfuscator.api.Transformer
object ShuffleFields : Transformer("ShuffleFields", "Shuffles around fields.") {
override fun obfuscate() {
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
classNode.fields.shuffle()
}
}
}

View File

@ -0,0 +1,27 @@
/**
* Copyright (C) 2022 zProtect
*
* This software is NOT free software.
* You may NOT distribute it or modify it without explicit permission.
*
* This software has been created in the hope that it will be useful,
* but WITHOUT ANY WARRANTY. zProtect is NOT liable for ANY damages caused by unlawful distribution
* or usage of any zProtect source code or products.
*
* You should have received a copy of the zProtect PU (Prohibited Usage) License along with this program.
*/
package dev.zprotect.obfuscator.transformers.shufflers
import dev.zprotect.obfuscator.api.Transformer
object ShuffleMethods : Transformer("ShuffleMethods", "Shuffles around methods.") {
override fun obfuscate() {
classes.stream()
.filter { classNode -> !isExcluded(classNode.name) }
.forEach { classNode ->
classNode.methods.shuffle()
}
}
}

View File

@ -0,0 +1,118 @@
/*
The MIT License
Copyright (c) 2004-2013 Paul R. Holser, Jr.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package joptsimple.internal;
import java.util.Iterator;
import java.util.List;
import static java.lang.System.getProperty;
import static java.util.Arrays.asList;
/**
* @author Paul Holser
*/
public final class Strings {
public static final String EMPTY = "";
public static final String SINGLE_QUOTE = "'";
public static final String LINE_SEPARATOR = getProperty("line.separator");
private Strings() {
throw new UnsupportedOperationException();
}
/**
* Gives a string consisting of the given character repeated the given number of times.
*
* @param ch the character to repeat
* @param count how many times to repeat the character
* @return the resultant string
*/
public static String repeat(char ch, int count) {
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < count; ++i)
buffer.append(ch);
return buffer.toString();
}
/**
* Tells whether the given string is either {@code} or consists solely of whitespace characters.
*
* @param target string to check
* @return {@code true} if the target string is null or empty
*/
public static boolean isNullOrEmpty(String target) {
return target == null || EMPTY.equals(target);
}
/**
* Gives a string consisting of a given string prepended and appended with surrounding characters.
*
* @param target a string
* @param begin character to prepend
* @param end character to append
* @return the surrounded string
*/
public static String surround(String target, char begin, char end) {
return begin + target + end;
}
/**
* Gives a string consisting of the elements of a given array of strings, each separated by a given separator
* string.
*
* @param pieces the strings to join
* @param separator the separator
* @return the joined string
*/
public static String join(String[] pieces, String separator) {
return join(asList(pieces), separator);
}
/**
* Gives a string consisting of the string representations of the elements of a given array of objects,
* each separated by a given separator string.
*
* @param pieces the elements whose string representations are to be joined
* @param separator the separator
* @return the joined string
*/
public static String join(List pieces, String separator) {
StringBuilder buffer = new StringBuilder();
for (Iterator iter = pieces.iterator(); iter.hasNext(); ) {
buffer.append(iter.next());
if (iter.hasNext())
buffer.append(separator);
}
return buffer.toString();
}
}